Mercurial > libervia-backend
comparison src/core/log.py @ 1006:325fd230c15d
core (log): added advanced feature to basic backend (colors/formatting/level and logger filtering)
author | Goffi <goffi@goffi.org> |
---|---|
date | Mon, 05 May 2014 18:58:34 +0200 |
parents | b4af31a8a4f2 |
children | a7d33c7a8277 |
comparison
equal
deleted
inserted
replaced
1005:b4af31a8a4f2 | 1006:325fd230c15d |
---|---|
25 | 25 |
26 _backend = None | 26 _backend = None |
27 _loggers = {} | 27 _loggers = {} |
28 _handlers = {} | 28 _handlers = {} |
29 | 29 |
30 class Filtered(Exception): | |
31 pass | |
30 | 32 |
31 class Logger(object): | 33 class Logger(object): |
32 """ High level logging class """ | 34 """High level logging class""" |
35 fmt = None | |
36 filter_name = None | |
37 post_treat = None | |
33 | 38 |
34 def __init__(self, name): | 39 def __init__(self, name): |
35 self._name = name | 40 self._name = name |
36 | 41 |
42 def log(self, level, message): | |
43 """Print message | |
44 | |
45 @param level: one of C.LOG_LEVELS | |
46 @param message: message to format and print | |
47 """ | |
48 try: | |
49 formatted = self.format(level, message) | |
50 if self.post_treat is None: | |
51 print formatted | |
52 else: | |
53 print self.post_treat(level, formatted) | |
54 except Filtered: | |
55 pass | |
56 | |
57 def format(self, level, message): | |
58 """Format message according to Logger.fmt | |
59 | |
60 @param level: one of C.LOG_LEVELS | |
61 @param message: message to format | |
62 @return: formatted message | |
63 | |
64 @raise: Filtered the message must not be logged | |
65 """ | |
66 if self.fmt is None and self.filter_name is None: | |
67 return message | |
68 record = {'name': self._name, | |
69 'message': message, | |
70 'levelname': level, | |
71 } | |
72 try: | |
73 if not self.filter_name.dictFilter(record): | |
74 raise Filtered | |
75 except AttributeError: | |
76 if self.filter_name is not None: | |
77 raise ValueError("Bad filter: filters must have a .filter method") | |
78 return self.fmt % record | |
79 | |
37 def debug(self, msg): | 80 def debug(self, msg): |
38 print msg | 81 self.log(C.LOG_LVL_DEBUG, msg) |
39 | 82 |
40 def info(self, msg): | 83 def info(self, msg): |
41 print msg | 84 self.log(C.LOG_LVL_INFO, msg) |
42 | 85 |
43 def warning(self, msg): | 86 def warning(self, msg): |
44 print msg | 87 self.log(C.LOG_LVL_WARNING, msg) |
45 | 88 |
46 def error(self, msg): | 89 def error(self, msg): |
47 print msg | 90 self.log(C.LOG_LVL_ERROR, msg) |
48 | 91 |
49 def critical(self, msg): | 92 def critical(self, msg): |
50 print msg | 93 self.log(C.LOG_LVL_CRITICAL, msg) |
51 | 94 |
52 | 95 |
53 class FilterName(object): | 96 class FilterName(object): |
97 """Filter on logger name according to a regex""" | |
54 | 98 |
55 def __init__(self, name_re): | 99 def __init__(self, name_re): |
56 """Initialise name filter | 100 """Initialise name filter |
57 | 101 |
58 @param name_re: regular expression used to filter names (using search and not match) | 102 @param name_re: regular expression used to filter names (using search and not match) |
64 def filter(self, record): | 108 def filter(self, record): |
65 if self.name_re.search(record.name) is not None: | 109 if self.name_re.search(record.name) is not None: |
66 return 1 | 110 return 1 |
67 return 0 | 111 return 0 |
68 | 112 |
113 def dictFilter(self, dict_record): | |
114 """Filter using a dictionary record | |
115 | |
116 @param dict_record: dictionary with at list a key "name" with logger name | |
117 @return: True if message should be logged | |
118 """ | |
119 class LogRecord(object): | |
120 pass | |
121 log_record = LogRecord() | |
122 log_record.name = dict_record['name'] | |
123 return self.filter(log_record) == 1 | |
124 | |
125 | |
126 def _ansiColors(level, message): | |
127 """Colorise message depending on level for terminals | |
128 | |
129 @param level: one of C.LOG_LEVELS | |
130 @param message: formatted message to log | |
131 @return: message with ANSI escape codes for coloration | |
132 """ | |
133 if level == C.LOG_LVL_DEBUG: | |
134 out = (C.ANSI_FG_CYAN, message, C.ANSI_RESET) | |
135 elif level == C.LOG_LVL_WARNING: | |
136 out = (C.ANSI_FG_YELLOW, message, C.ANSI_RESET) | |
137 elif level == C.LOG_LVL_ERROR: | |
138 out = (C.ANSI_FG_RED, | |
139 C.ANSI_BLINK, | |
140 r'/!\ ', | |
141 C.ANSI_BLINK_OFF, | |
142 message, | |
143 C.ANSI_RESET) | |
144 elif level == C.LOG_LVL_CRITICAL: | |
145 out = (C.ANSI_BOLD, | |
146 C.ANSI_FG_RED, | |
147 'Guru Meditation ', | |
148 C.ANSI_NORMAL_WEIGHT, | |
149 message, | |
150 C.ANSI_RESET) | |
151 else: | |
152 out = message | |
153 return ''.join(out) | |
154 | |
69 def _manageOutputs(outputs_raw): | 155 def _manageOutputs(outputs_raw): |
70 """ Parse output option in a backend agnostic way, and fill _backend consequently | 156 """ Parse output option in a backend agnostic way, and fill _handlers consequently |
71 | 157 |
72 @param outputs_raw: output option as enterred in environment variable or in configuration | 158 @param outputs_raw: output option as enterred in environment variable or in configuration |
73 """ | 159 """ |
74 if not outputs_raw: | 160 if not outputs_raw: |
75 return | 161 return |
141 super(SatFormatter, self).__init__(fmt, datefmt) | 227 super(SatFormatter, self).__init__(fmt, datefmt) |
142 | 228 |
143 def format(self, record): | 229 def format(self, record): |
144 s = super(SatFormatter, self).format(record) | 230 s = super(SatFormatter, self).format(record) |
145 if with_color: | 231 if with_color: |
146 if record.levelno == logging.DEBUG: | 232 s = _ansiColors(record.levelname, s) |
147 fmt = (C.ANSI_FG_CYAN, s, C.ANSI_RESET) | |
148 elif record.levelno == logging.WARNING: | |
149 fmt = (C.ANSI_FG_YELLOW, s, C.ANSI_RESET) | |
150 elif record.levelno == logging.ERROR: | |
151 fmt = (C.ANSI_FG_RED, | |
152 C.ANSI_BLINK, | |
153 r'/!\ ', | |
154 C.ANSI_BLINK_OFF, | |
155 s, | |
156 C.ANSI_RESET) | |
157 elif record.levelno == logging.CRITICAL: | |
158 fmt = (C.ANSI_BOLD, | |
159 C.ANSI_FG_RED, | |
160 'Guru Meditation ', | |
161 C.ANSI_NORMAL_WEIGHT, | |
162 s, | |
163 C.ANSI_RESET) | |
164 else: | |
165 fmt = s | |
166 s = ''.join(fmt) | |
167 | |
168 return s | 233 return s |
169 | 234 |
170 root_logger = logging.getLogger() | 235 root_logger = logging.getLogger() |
171 if len(root_logger.handlers) == 0: | 236 if len(root_logger.handlers) == 0: |
172 _manageOutputs(output) | 237 _manageOutputs(output) |
176 if handler == C.LOG_OPT_OUTPUT_DEFAULT: | 241 if handler == C.LOG_OPT_OUTPUT_DEFAULT: |
177 hdlr = logging.StreamHandler() | 242 hdlr = logging.StreamHandler() |
178 elif handler == C.LOG_OPT_OUTPUT_MEMORY: | 243 elif handler == C.LOG_OPT_OUTPUT_MEMORY: |
179 import logging.handlers | 244 import logging.handlers |
180 hdlr = logging.handlers.BufferingHandler(options) | 245 hdlr = logging.handlers.BufferingHandler(options) |
246 _handlers[handler] = hdlr # we keep a reference to the handler to read the buffer later | |
181 elif handler == C.LOG_OPT_OUTPUT_FILE: | 247 elif handler == C.LOG_OPT_OUTPUT_FILE: |
182 import os.path | 248 import os.path |
183 hdlr = logging.FileHandler(os.path.expanduser(options)) | 249 hdlr = logging.FileHandler(os.path.expanduser(options)) |
184 else: | 250 else: |
185 raise ValueError("Unknown handler type") | 251 raise ValueError("Unknown handler type") |
189 if name_filter is not None: | 255 if name_filter is not None: |
190 hdlr.addFilter(name_filter) | 256 hdlr.addFilter(name_filter) |
191 else: | 257 else: |
192 root_logger.warning(u"Handlers already set on root logger") | 258 root_logger.warning(u"Handlers already set on root logger") |
193 | 259 |
260 def _configureBasic(level=None, fmt=None, output=None, logger=None, colors=False, force_colors=False): | |
261 """Configure basic backend | |
262 @param level: same as _configureStdLogging.level | |
263 @param fmt: same as _configureStdLogging.fmt | |
264 @param output: not implemented yet TODO | |
265 @param logger: same as _configureStdLogging.logger | |
266 @param colors: same as _configureStdLogging.colors | |
267 @param force_colors: same as _configureStdLogging.force_colors | |
268 """ | |
269 if level is not None: | |
270 # we deactivate methods below level | |
271 level_idx = C.LOG_LEVELS.index(level) | |
272 def dev_null(self, msg): | |
273 pass | |
274 for _level in C.LOG_LEVELS[:level_idx]: | |
275 setattr(Logger, _level.lower(), dev_null) | |
276 if fmt is not None: | |
277 if fmt != '%(message)s': # %(message)s is the same as None | |
278 Logger.fmt = fmt | |
279 if output is not None: | |
280 if output != C.LOG_OPT_OUTPUT_SEP + C.LOG_OPT_OUTPUT_DEFAULT: | |
281 # TODO: manage other outputs | |
282 raise NotImplementedError("Basic backend only manage default output yet") | |
283 | |
284 if logger: | |
285 Logger.filter_name = FilterName(logger) | |
286 | |
287 if colors: | |
288 import sys | |
289 if force_colors or sys.stdout.isatty(): | |
290 # we need colors | |
291 Logger.post_treat = lambda self, level, message: _ansiColors(level, message) | |
292 elif force_colors: | |
293 raise ValueError("force_colors can't be used if colors is False") | |
294 | |
194 def configure(backend=C.LOG_BACKEND_STANDARD, **options): | 295 def configure(backend=C.LOG_BACKEND_STANDARD, **options): |
195 """Configure logging bejaviour | 296 """Configure logging behaviour |
196 @param backend: can be: | 297 @param backend: can be: |
197 C.LOG_BACKEND_STANDARD: use standard logging module | 298 C.LOG_BACKEND_STANDARD: use standard logging module |
198 C.LOG_BACKEND_TWISTED: use twisted logging module (with standard logging observer) | 299 C.LOG_BACKEND_TWISTED: use twisted logging module (with standard logging observer) |
199 C.LOG_BACKEND_BASIC: use a basic print based logging | 300 C.LOG_BACKEND_BASIC: use a basic print based logging |
200 """ | 301 """ |
222 warning = logging.warning | 323 warning = logging.warning |
223 error = logging.error | 324 error = logging.error |
224 critical = logging.critical | 325 critical = logging.critical |
225 | 326 |
226 elif backend == C.LOG_BACKEND_BASIC: | 327 elif backend == C.LOG_BACKEND_BASIC: |
227 pass | 328 _configureBasic(**options) |
228 | 329 |
229 else: | 330 else: |
230 raise ValueError("unknown backend") | 331 raise ValueError("unknown backend") |
231 | 332 |
232 def _parseOptions(options): | 333 def _parseOptions(options): |