]>
Commit | Line | Data |
---|---|---|
d486dd0d JF |
1 | #!/usr/bin/env python |
2 | # | |
3 | # Copyright 2016-2017 Cumulus Networks, Inc. All rights reserved. | |
4 | # Author: Julien Fortin, julien@cumulusnetworks.com | |
5 | # | |
6 | ||
7 | import sys | |
8 | import json | |
9 | import struct | |
10 | import select | |
11 | import logging | |
12 | import logging.handlers | |
13 | ||
14 | from cStringIO import StringIO | |
15 | ||
16 | ||
17 | class Log: | |
18 | LOGGER_NAME = sys.argv[0].split('/')[-1] | |
19 | ||
20 | def __init__(self): | |
21 | """ | |
22 | - On start the daemon will log on syslog. | |
23 | - For each client commands we might need to adjust the target | |
24 | (stderr/stdout): | |
25 | if -v --verbose or -d --debug are provided we override | |
26 | sys.stdout and sys.stderr with string buffers to be able to send | |
27 | back the content of these buffer on the UNIX socket back to the | |
28 | client. | |
29 | if -l or --syslog we make sure to use syslog. | |
30 | """ | |
31 | ||
32 | self.stdout_buffer = None | |
33 | self.stderr_buffer = None | |
34 | ||
35 | self.root = logging.getLogger() | |
36 | self.root.name = Log.LOGGER_NAME | |
37 | self.root.setLevel(logging.INFO) | |
38 | ||
39 | self.root_info = self.root.info | |
40 | self.root_debug = self.root.debug | |
41 | self.root_error = self.root.error | |
42 | self.root_warning = self.root.warning | |
43 | self.root_critical = self.root.critical | |
44 | ||
45 | self.root.info = self.info | |
46 | self.root.debug = self.debug | |
47 | self.root.error = self.error | |
48 | self.root.warning = self.warning | |
49 | self.root.critical = self.critical | |
50 | ||
51 | logging.addLevelName(logging.CRITICAL, 'critical') | |
52 | logging.addLevelName(logging.WARNING, 'warning') | |
53 | logging.addLevelName(logging.ERROR, 'error') | |
54 | logging.addLevelName(logging.DEBUG, 'debug') | |
55 | logging.addLevelName(logging.INFO, 'info') | |
56 | ||
57 | self.syslog = True | |
58 | self.socket = None | |
59 | ||
60 | # syslog | |
61 | facility = logging.handlers.SysLogHandler.LOG_DAEMON | |
62 | address = '/dev/log' | |
63 | format = '%(name)s: %(levelname)s: %(message)s' | |
64 | ||
72ba4569 JF |
65 | try: |
66 | self.syslog_handler = logging.handlers.SysLogHandler(address=address, facility=facility) | |
67 | self.syslog_handler.setFormatter(logging.Formatter(format)) | |
68 | except Exception as e: | |
69 | sys.stderr.write("warning: syslogs: %s\n" % str(e)) | |
70 | self.syslog_handler = None | |
d486dd0d JF |
71 | |
72 | # console | |
73 | format = '%(levelname)s: %(message)s' | |
74 | self.console_handler = logging.StreamHandler(sys.stderr) | |
75 | self.console_handler.setFormatter(logging.Formatter(format)) | |
76 | ||
72ba4569 | 77 | if self.syslog_handler and self.LOGGER_NAME[-1] == 'd': |
d486dd0d JF |
78 | self.update_current_logger(syslog=True, verbose=True, debug=False) |
79 | else: | |
80 | self.update_current_logger(syslog=False, verbose=False, debug=False) | |
81 | ||
82 | def update_current_logger(self, syslog, verbose, debug): | |
83 | self.syslog = syslog | |
84 | self.root.setLevel(self.get_log_level(verbose=verbose, debug=debug)) | |
72ba4569 | 85 | self.root.handlers = [self.syslog_handler if self.syslog and self.syslog_handler else self.console_handler] |
d486dd0d JF |
86 | self.flush() |
87 | ||
88 | def flush(self): | |
89 | if self.socket: | |
90 | result = dict() | |
91 | stdout = self._flush_buffer('stdout', self.stdout_buffer, result) | |
92 | stderr = self._flush_buffer('stderr', self.stderr_buffer, result) | |
93 | if stdout or stderr: | |
94 | try: | |
95 | self.tx_data(json.dumps(result)) | |
96 | self.redirect_stdouput() | |
97 | except select.error as e: | |
98 | # haven't seen the case yet | |
99 | self.socket = None | |
100 | self.update_current_logger(syslog=True, verbose=True) | |
101 | self.critical(str(e)) | |
102 | exit(84) | |
103 | self.console_handler.flush() | |
72ba4569 JF |
104 | |
105 | if self.syslog_handler: | |
106 | self.syslog_handler.flush() | |
d486dd0d JF |
107 | |
108 | def tx_data(self, data, socket=None): | |
109 | socket_obj = socket if socket else self.socket | |
110 | ready = select.select([], [socket_obj], []) | |
111 | if ready and ready[1] and ready[1][0] == socket_obj: | |
112 | frmt = "=%ds" % len(data) | |
113 | packed_msg = struct.pack(frmt, data) | |
114 | packed_hdr = struct.pack('=I', len(packed_msg)) | |
115 | socket_obj.sendall(packed_hdr) | |
116 | socket_obj.sendall(packed_msg) | |
117 | ||
118 | def set_socket(self, socket): | |
119 | self.socket = socket | |
120 | self.redirect_stdouput() | |
121 | ||
122 | def redirect_stdouput(self): | |
123 | self.stdout_buffer = sys.stdout = StringIO() | |
124 | self.stderr_buffer = self.console_handler.stream = sys.stderr = StringIO() | |
125 | ||
126 | def error(self, msg, *args, **kwargs): | |
127 | self.root_error(msg, *args, **kwargs) | |
128 | self.flush() | |
129 | ||
130 | def critical(self, msg, *args, **kwargs): | |
131 | self.root_critical(msg, *args, **kwargs) | |
132 | self.flush() | |
133 | ||
134 | def warning(self, msg, *args, **kwargs): | |
135 | self.root_warning(msg, *args, **kwargs) | |
136 | self.flush() | |
137 | ||
138 | def info(self, msg, *args, **kwargs): | |
139 | self.root_info(msg, *args, **kwargs) | |
140 | self.flush() | |
141 | ||
142 | def debug(self, msg, *args, **kwargs): | |
143 | self.root_debug(msg, *args, **kwargs) | |
144 | self.flush() | |
145 | ||
146 | def get_current_log_level(self): | |
147 | return self.root.level | |
148 | ||
149 | def is_syslog(self): return self.syslog | |
150 | ||
151 | @staticmethod | |
152 | def get_log_level(verbose=False, debug=False): | |
153 | log_level = logging.WARNING | |
154 | if debug: | |
155 | log_level = logging.DEBUG | |
156 | elif verbose: | |
157 | log_level = logging.INFO | |
158 | return log_level | |
159 | ||
160 | @staticmethod | |
161 | def _flush_buffer(stream, buff, dictionary): | |
162 | if buff: | |
163 | data = buff.getvalue() | |
164 | if data: | |
165 | dictionary[stream] = data | |
166 | return True | |
167 | ||
168 | ||
169 | log = Log() |