Mercurial > libervia-backend
comparison src/core/log.py @ 1007:a7d33c7a8277
core (log): refactoring + twisted backend:
- configuration is now done with classes, so inheritance is possible
- twisted backend now use twisted log methods
- log not produced by SàT directly in twisted backend are captured by an observer (twistedObserver) and sent back to SàT logging chain, with the "twisted" logger.
- \\default output use normal twistd output, except in debug mode where it add an stdout output in addition to the log file.
- when log_colors = True, colors are enabled only where it's possible (tty out), when log_colors=force colors are always enabled.
- fixed some bad log.xxx calls
author | Goffi <goffi@goffi.org> |
---|---|
date | Mon, 05 May 2014 18:58:34 +0200 |
parents | 325fd230c15d |
children | d70d4fe5c5f8 |
comparison
equal
deleted
inserted
replaced
1006:325fd230c15d | 1007:a7d33c7a8277 |
---|---|
25 | 25 |
26 _backend = None | 26 _backend = None |
27 _loggers = {} | 27 _loggers = {} |
28 _handlers = {} | 28 _handlers = {} |
29 | 29 |
30 | |
30 class Filtered(Exception): | 31 class Filtered(Exception): |
31 pass | 32 pass |
32 | 33 |
34 | |
33 class Logger(object): | 35 class Logger(object): |
34 """High level logging class""" | 36 """High level logging class""" |
35 fmt = None | 37 fmt = None # format option as given by user (e.g. SAT_LOG_LOGGER) |
36 filter_name = None | 38 filter_name = None # filter to call |
37 post_treat = None | 39 post_treat = None |
38 | 40 |
39 def __init__(self, name): | 41 def __init__(self, name): |
40 self._name = name | 42 if isinstance(name, Logger): |
43 self.copy(name) | |
44 else: | |
45 self._name = name | |
46 | |
47 def copy(self, other): | |
48 """Copy values from other Logger""" | |
49 self.fmt = other.fmt | |
50 self.Filter_name = other.fmt | |
51 self.post_treat = other.post_treat | |
52 self._name = other._name | |
53 | |
54 def out(self, message, level=None): | |
55 """Actually log the message | |
56 | |
57 @param message: formatted message | |
58 """ | |
59 print message | |
41 | 60 |
42 def log(self, level, message): | 61 def log(self, level, message): |
43 """Print message | 62 """Print message |
44 | 63 |
45 @param level: one of C.LOG_LEVELS | 64 @param level: one of C.LOG_LEVELS |
46 @param message: message to format and print | 65 @param message: message to format and print |
47 """ | 66 """ |
48 try: | 67 try: |
49 formatted = self.format(level, message) | 68 formatted = self.format(level, message) |
50 if self.post_treat is None: | 69 if self.post_treat is None: |
51 print formatted | 70 self.out(formatted, level) |
52 else: | 71 else: |
53 print self.post_treat(level, formatted) | 72 self.out(self.post_treat(level, formatted), level) |
54 except Filtered: | 73 except Filtered: |
55 pass | 74 pass |
56 | 75 |
57 def format(self, level, message): | 76 def format(self, level, message): |
58 """Format message according to Logger.fmt | 77 """Format message according to Logger.fmt |
59 | 78 |
60 @param level: one of C.LOG_LEVELS | 79 @param level: one of C.LOG_LEVELS |
61 @param message: message to format | 80 @param message: message to format |
62 @return: formatted message | 81 @return: formatted message |
63 | 82 |
64 @raise: Filtered the message must not be logged | 83 @raise: Filtered when the message must not be logged |
65 """ | 84 """ |
66 if self.fmt is None and self.filter_name is None: | 85 if self.fmt is None and self.filter_name is None: |
67 return message | 86 return message |
68 record = {'name': self._name, | 87 record = {'name': self._name, |
69 'message': message, | 88 'message': message, |
73 if not self.filter_name.dictFilter(record): | 92 if not self.filter_name.dictFilter(record): |
74 raise Filtered | 93 raise Filtered |
75 except AttributeError: | 94 except AttributeError: |
76 if self.filter_name is not None: | 95 if self.filter_name is not None: |
77 raise ValueError("Bad filter: filters must have a .filter method") | 96 raise ValueError("Bad filter: filters must have a .filter method") |
78 return self.fmt % record | 97 return self.fmt % record if self.fmt is not None else message |
79 | 98 |
80 def debug(self, msg): | 99 def debug(self, msg): |
81 self.log(C.LOG_LVL_DEBUG, msg) | 100 self.log(C.LOG_LVL_DEBUG, msg) |
82 | 101 |
83 def info(self, msg): | 102 def info(self, msg): |
89 def error(self, msg): | 108 def error(self, msg): |
90 self.log(C.LOG_LVL_ERROR, msg) | 109 self.log(C.LOG_LVL_ERROR, msg) |
91 | 110 |
92 def critical(self, msg): | 111 def critical(self, msg): |
93 self.log(C.LOG_LVL_CRITICAL, msg) | 112 self.log(C.LOG_LVL_CRITICAL, msg) |
113 | |
114 | |
115 class TwistedLogger(Logger): | |
116 colors = True | |
117 force_colors = False | |
118 | |
119 def __init__(self, *args, **kwargs): | |
120 super(TwistedLogger, self).__init__(*args, **kwargs) | |
121 from twisted.python import log | |
122 self.twisted_log = log | |
123 | |
124 def out(self, message, level=None): | |
125 """Actually log the message | |
126 | |
127 @param message: formatted message | |
128 """ | |
129 self.twisted_log.msg(message.encode('utf-8', 'ignore'), sat_logged=True, level=level) | |
94 | 130 |
95 | 131 |
96 class FilterName(object): | 132 class FilterName(object): |
97 """Filter on logger name according to a regex""" | 133 """Filter on logger name according to a regex""" |
98 | 134 |
150 C.ANSI_RESET) | 186 C.ANSI_RESET) |
151 else: | 187 else: |
152 out = message | 188 out = message |
153 return ''.join(out) | 189 return ''.join(out) |
154 | 190 |
155 def _manageOutputs(outputs_raw): | 191 |
156 """ Parse output option in a backend agnostic way, and fill _handlers consequently | 192 class Configure(object): |
157 | 193 LOGGER_CLASS = None |
158 @param outputs_raw: output option as enterred in environment variable or in configuration | 194 |
159 """ | 195 def __init__(self, level=None, fmt=None, output=None, logger=None, colors=False, force_colors=False): |
160 if not outputs_raw: | 196 """Configure backend |
161 return | 197 @param level: one of C.LOG_LEVELS |
162 outputs = outputs_raw.split(C.LOG_OPT_OUTPUT_SEP) | 198 @param fmt: format string, pretty much as in std logging. Accept the following keywords (maybe more depending on backend): |
163 global _handlers | 199 - "message" |
164 if len(outputs) == 1: | 200 - "levelname" |
165 _handlers[C.LOG_OPT_OUTPUT_FILE] = outputs.pop() | 201 - "name" (logger name) |
166 | 202 @param logger: if set, use it as a regular expression to filter on logger name. |
167 for output in outputs: | 203 Use search to match expression, so ^ or $ can be necessary. |
168 if not output: | 204 @param colors: if True use ANSI colors to show log levels |
169 continue | 205 @param force_colors: if True ANSI colors are used even if stdout is not a tty |
170 if output[-1] == ')': | 206 """ |
171 # we have options | 207 self.preTreatment() |
172 opt_begin = output.rfind('(') | 208 self.configureLevel(level) |
173 options = output[opt_begin+1:-1] | 209 self.configureFormat(fmt) |
174 output = output[:opt_begin] | 210 self.configureOutput(output) |
211 self.configureLogger(logger) | |
212 self.configureColors(colors, force_colors) | |
213 self.postTreatment() | |
214 self.updateCurrentLogger() | |
215 | |
216 def updateCurrentLogger(self): | |
217 """update existing logger to the class needed for this backend""" | |
218 if self.LOGGER_CLASS is None: | |
219 return | |
220 for name, logger in _loggers.items(): | |
221 _loggers[name] = self.LOGGER_CLASS(logger) | |
222 | |
223 def preTreatment(self): | |
224 pass | |
225 | |
226 def configureLevel(self, level): | |
227 pass | |
228 | |
229 def configureFormat(self, fmt): | |
230 pass | |
231 | |
232 def configureOutput(self, output): | |
233 pass | |
234 | |
235 def configureLogger(self, logger): | |
236 pass | |
237 | |
238 def configureColors(self, colors, force_colors): | |
239 pass | |
240 | |
241 def postTreatment(self): | |
242 pass | |
243 | |
244 def manageOutputs(self, outputs_raw): | |
245 """ Parse output option in a backend agnostic way, and fill _handlers consequently | |
246 | |
247 @param outputs_raw: output option as enterred in environment variable or in configuration | |
248 """ | |
249 if not outputs_raw: | |
250 return | |
251 outputs = outputs_raw.split(C.LOG_OPT_OUTPUT_SEP) | |
252 global _handlers | |
253 if len(outputs) == 1: | |
254 _handlers[C.LOG_OPT_OUTPUT_FILE] = [outputs.pop()] | |
255 | |
256 for output in outputs: | |
257 if not output: | |
258 continue | |
259 if output[-1] == ')': | |
260 # we have options | |
261 opt_begin = output.rfind('(') | |
262 options = output[opt_begin+1:-1] | |
263 output = output[:opt_begin] | |
264 else: | |
265 options = None | |
266 | |
267 if output not in (C.LOG_OPT_OUTPUT_DEFAULT, C.LOG_OPT_OUTPUT_FILE, C.LOG_OPT_OUTPUT_MEMORY): | |
268 raise ValueError(u"Invalid output [%s]" % output) | |
269 | |
270 if output == C.LOG_OPT_OUTPUT_DEFAULT: | |
271 # no option for defaut handler | |
272 _handlers[output] = None | |
273 elif output == C.LOG_OPT_OUTPUT_FILE: | |
274 if not options: | |
275 ValueError("%(handler)s output need a path as option" % {'handler': output}) | |
276 _handlers.setdefault(output, []).append(options) | |
277 options = None # option are parsed, we can empty them | |
278 elif output == C.LOG_OPT_OUTPUT_MEMORY: | |
279 # we have memory handler, option can be the len limit or None | |
280 try: | |
281 limit = int(options) | |
282 options = None # option are parsed, we can empty them | |
283 except (TypeError, ValueError): | |
284 limit = C.LOG_OPT_OUTPUT_MEMORY_LIMIT | |
285 _handlers[output] = limit | |
286 | |
287 if options: # we should not have unparsed options | |
288 raise ValueError(u"options [%(options)s] are not supported for %(handler)s output" % {'options': options, 'handler': output}) | |
289 | |
290 | |
291 class ConfigureBasic(Configure): | |
292 def configureLevel(self, level): | |
293 if level is not None: | |
294 # we deactivate methods below level | |
295 level_idx = C.LOG_LEVELS.index(level) | |
296 def dev_null(self, msg): | |
297 pass | |
298 for _level in C.LOG_LEVELS[:level_idx]: | |
299 setattr(Logger, _level.lower(), dev_null) | |
300 | |
301 def configureFormat(self, fmt): | |
302 if fmt is not None: | |
303 if fmt != '%(message)s': # %(message)s is the same as None | |
304 Logger.fmt = fmt | |
305 | |
306 def configureOutput(self, output): | |
307 if output is not None: | |
308 if output != C.LOG_OPT_OUTPUT_SEP + C.LOG_OPT_OUTPUT_DEFAULT: | |
309 # TODO: manage other outputs | |
310 raise NotImplementedError("Basic backend only manage default output yet") | |
311 | |
312 def configureLogger(self, logger): | |
313 if logger: | |
314 Logger.filter_name = FilterName(logger) | |
315 | |
316 def configureColors(self, colors, force_colors): | |
317 if colors: | |
318 import sys | |
319 if force_colors or sys.stdout.isatty(): # FIXME: isatty should be tested on each handler, not globaly | |
320 # we need colors | |
321 Logger.post_treat = lambda self, level, message: _ansiColors(level, message) | |
322 elif force_colors: | |
323 raise ValueError("force_colors can't be used if colors is False") | |
324 | |
325 | |
326 class ConfigureTwisted(ConfigureBasic): | |
327 LOGGER_CLASS = TwistedLogger | |
328 | |
329 def changeObserver(self, observer, can_color=False): | |
330 """Install a hook on observer to manage SàT specificities | |
331 | |
332 @param observer: original observer to hook | |
333 @param can_color: True if observer can display ansi colors | |
334 """ | |
335 def observer_hook(event): | |
336 """redirect non SàT log to twisted_logger, and add colors when possible""" | |
337 if 'sat_logged' in event: # we only want our own logs, other are managed by twistedObserver | |
338 # we add colors if possible | |
339 if (can_color and self.LOGGER_CLASS.colors) or self.LOGGER_CLASS.force_colors: | |
340 message = event.get('message', tuple()) | |
341 level = event.get('level', C.LOG_LVL_INFO) | |
342 if message: | |
343 event['message'] = (_ansiColors(level, ''.join(message)),) # must be a tuple | |
344 observer(event) # we can now call the original observer | |
345 | |
346 return observer_hook | |
347 | |
348 def changeFileLogObserver(self, observer): | |
349 """Install SàT hook for FileLogObserver | |
350 | |
351 if the output is a tty, we allow colors, else we don't | |
352 @param observer: original observer to hook | |
353 """ | |
354 log_obs = observer.__self__ | |
355 log_file = log_obs.write.__self__ | |
356 try: | |
357 can_color = log_file.isatty() | |
358 except AttributeError: | |
359 can_color = False | |
360 return self.changeObserver(observer, can_color=can_color) | |
361 | |
362 def installObserverHook(self, observer): | |
363 """Check observer type and install SàT hook when possible | |
364 | |
365 @param observer: observer to hook | |
366 @return: hooked observer or original one | |
367 """ | |
368 if hasattr(observer, '__self__'): | |
369 ori = observer | |
370 if isinstance(observer.__self__, self.log.FileLogObserver): | |
371 observer = self.changeFileLogObserver(observer) | |
372 elif isinstance(observer.__self__, self.log.DefaultObserver): | |
373 observer = self.changeObserver(observer, can_color=True) | |
374 else: | |
375 # we use print because log system is not fully initialized | |
376 print("Unmanaged observer [%s]" % observer) | |
377 return observer | |
378 self.observers[ori] = observer | |
379 return observer | |
380 | |
381 def preTreatment(self): | |
382 """initialise needed attributes, and install observers hooks""" | |
383 self.observers = {} | |
384 from twisted.python import log | |
385 self.log = log | |
386 self.log_publisher = log.msg.__self__ | |
387 def addObserverObserver(self_logpub, other): | |
388 """Install hook so we know when a new observer is added""" | |
389 other = self.installObserverHook(other) | |
390 return self_logpub._originalAddObserver(other) | |
391 def removeObserverObserver(self_logpub, ori): | |
392 """removeObserver hook fix | |
393 | |
394 As we wrap the original observer, the original removeObserver may want to remove the original object instead of the wrapper, this method fix this | |
395 """ | |
396 if ori in self.observers: | |
397 self_logpub._originalRemoveObserver(self.observers[ori]) | |
398 else: | |
399 try: | |
400 self_logpub._originalRemoveObserver(ori) | |
401 except ValueError: | |
402 try: | |
403 ori in self.cleared_observers | |
404 except AttributeError: | |
405 raise ValueError("Unknown observer") | |
406 | |
407 # we replace addObserver/removeObserver by our own | |
408 log.LogPublisher._originalAddObserver = log.LogPublisher.addObserver | |
409 log.LogPublisher._originalRemoveObserver = log.LogPublisher.removeObserver | |
410 import types # see https://stackoverflow.com/a/4267590 (thx Chris Morgan/aaronasterling) | |
411 log.addObserver = types.MethodType(addObserverObserver, self.log_publisher, log.LogPublisher) | |
412 log.removeObserver = types.MethodType(removeObserverObserver, self.log_publisher, log.LogPublisher) | |
413 | |
414 # we now change existing observers | |
415 for idx, observer in enumerate(self.log_publisher.observers): | |
416 self.log_publisher.observers[idx] = self.installObserverHook(observer) | |
417 | |
418 | |
419 def configureLevel(self, level): | |
420 self.LOGGER_CLASS.level = level | |
421 super(ConfigureTwisted, self).configureLevel(level) | |
422 | |
423 def configureOutput(self, output): | |
424 import sys | |
425 if output is None: | |
426 output = C.LOG_OPT_OUTPUT_SEP + C.LOG_OPT_OUTPUT_DEFAULT | |
427 self.manageOutputs(output) | |
428 addObserver = self.log.addObserver | |
429 | |
430 if C.LOG_OPT_OUTPUT_DEFAULT in _handlers: | |
431 # default output is already managed, we just add output to stdout if we are in debug mode | |
432 from twisted.internet import defer | |
433 if defer.Deferred.debug: | |
434 addObserver(self.log.FileLogObserver(sys.stdout).emit) | |
175 else: | 435 else: |
176 options = None | 436 # \\default is not in the output, so we remove current observers |
177 | 437 self.cleared_observers = self.log_publisher.observers |
178 if output not in (C.LOG_OPT_OUTPUT_DEFAULT, C.LOG_OPT_OUTPUT_FILE, C.LOG_OPT_OUTPUT_MEMORY): | 438 self.observers.clear() |
179 raise ValueError(u"Invalid output [%s]" % output) | 439 del self.log_publisher.observers[:] |
180 | 440 # and we forbid twistd to add any observer |
181 if output == C.LOG_OPT_OUTPUT_DEFAULT: | 441 self.log.addObserver = lambda other: None |
182 # no option for defaut handler | 442 |
183 _handlers[output] = None | 443 if C.LOG_OPT_OUTPUT_FILE in _handlers: |
184 elif output == C.LOG_OPT_OUTPUT_FILE: | 444 from twisted.python import logfile |
185 if not options: | 445 for path in _handlers[C.LOG_OPT_OUTPUT_FILE]: |
186 ValueError("%(handler)s output need a path as option" % {'handler': output}) | 446 log_file = sys.stdout if path == '-' else logfile.LogFile.fromFullPath(path) |
187 _handlers[output] = options | 447 addObserver(self.log.FileLogObserver(log_file).emit) |
188 options = None # option are parsed, we can empty them | 448 |
189 elif output == C.LOG_OPT_OUTPUT_MEMORY: | 449 if C.LOG_OPT_OUTPUT_MEMORY in _handlers: |
190 # we have memory handler, option can be the len limit or None | 450 raise NotImplementedError("Memory observer is not implemented in Twisted backend") |
191 try: | 451 |
192 limit = int(options) | 452 def configureColors(self, colors, force_colors): |
193 options = None # option are parsed, we can empty them | 453 self.LOGGER_CLASS.colors = colors |
194 except (TypeError, ValueError): | 454 self.LOGGER_CLASS.force_colors = force_colors |
195 limit = C.LOG_OPT_OUTPUT_MEMORY_LIMIT | 455 if force_colors and not colors: |
196 _handlers[output] = limit | 456 raise ValueError('colors must be True if force_colors is True') |
197 | 457 |
198 if options: # we should not have unparsed options | 458 def postTreatment(self): |
199 raise ValueError(u"options [%(options)s] are not supported for %(handler)s output" % {'options': options, 'handler': output}) | 459 """Install twistedObserver which manage non SàT logs""" |
200 | 460 def twistedObserver(event): |
201 def _configureStdLogging(logging, level=None, fmt=C.LOG_OPT_FORMAT[1], output=C.LOG_OPT_OUTPUT[1], logger=None, colors=False, force_colors=False): | 461 """Observer which redirect log message not produced by SàT to SàT logging system""" |
202 """Configure standard logging module | 462 if not 'sat_logged' in event: |
203 | 463 # this log was not produced by SàT |
204 @param logging: standard logging module | 464 from twisted.python import log |
205 @param level: one of C.LOG_LEVELS | 465 text = log.textFromEventDict(event) |
206 @param fmt: format string, pretty much as in std logging. Accept the following keywords (maybe more depending on backend): | 466 if text is None: |
207 - "message" | 467 return |
208 - "levelname" | 468 twisted_logger = getLogger(C.LOG_TWISTED_LOGGER) |
209 - "name" (logger name) | 469 log_method = twisted_logger.error if event.get('isError', False) else twisted_logger.info |
210 @param logger: if set, use it as a regular expression to filter on logger name. | 470 log_method(text.decode('utf-8')) |
211 Use search to match expression, so ^ or $ can be necessary. | 471 |
212 @param colors: if True use ANSI colors to show log levels | 472 self.log_publisher._originalAddObserver(twistedObserver) |
213 @param force_colors: if True ANSI colors are used even if stdout is not a tty | 473 |
214 """ | 474 |
215 format_ = fmt | 475 class ConfigureStandard(Configure): |
216 if level is None: | 476 |
217 level = C.LOG_LVL_DEBUG | 477 def __init__(self, level=None, fmt=C.LOG_OPT_FORMAT[1], output=C.LOG_OPT_OUTPUT[1], logger=None, colors=False, force_colors=False): |
218 import sys | 478 super(ConfigureStandard, self).__init__(level, fmt, output, logger, colors, force_colors) |
219 with_color = colors & (sys.stdout.isatty() or force_colors) | 479 |
220 if not colors and force_colors: | 480 def preTreatment(self): |
221 raise ValueError("force_colors can't be used if colors is False") | 481 """We use logging methods directly, instead of using Logger""" |
222 | 482 global getLogger |
223 class SatFormatter(logging.Formatter): | 483 global debug |
224 u"""Formatter which manage SàT specificities""" | 484 global info |
225 | 485 global warning |
226 def __init__(self, fmt=None, datefmt=None): | 486 global error |
227 super(SatFormatter, self).__init__(fmt, datefmt) | 487 global critical |
228 | 488 import logging |
229 def format(self, record): | 489 getLogger = logging.getLogger |
230 s = super(SatFormatter, self).format(record) | 490 debug = logging.debug |
231 if with_color: | 491 info = logging.info |
232 s = _ansiColors(record.levelname, s) | 492 warning = logging.warning |
233 return s | 493 error = logging.error |
234 | 494 critical = logging.critical |
235 root_logger = logging.getLogger() | 495 |
236 if len(root_logger.handlers) == 0: | 496 def configureLevel(self, level): |
237 _manageOutputs(output) | 497 if level is None: |
238 formatter = SatFormatter(format_) | 498 level = C.LOG_LVL_DEBUG |
239 name_filter = FilterName(logger) if logger else None | 499 self.level = level |
240 for handler, options in _handlers.items(): | 500 |
241 if handler == C.LOG_OPT_OUTPUT_DEFAULT: | 501 def configureFormat(self, fmt): |
242 hdlr = logging.StreamHandler() | 502 import logging |
243 elif handler == C.LOG_OPT_OUTPUT_MEMORY: | 503 format_ = fmt |
244 import logging.handlers | 504 |
245 hdlr = logging.handlers.BufferingHandler(options) | 505 class SatFormatter(logging.Formatter): |
246 _handlers[handler] = hdlr # we keep a reference to the handler to read the buffer later | 506 u"""Formatter which manage SàT specificities""" |
247 elif handler == C.LOG_OPT_OUTPUT_FILE: | 507 |
248 import os.path | 508 def __init__(self, fmt=None, datefmt=None): |
249 hdlr = logging.FileHandler(os.path.expanduser(options)) | 509 super(SatFormatter, self).__init__(fmt, datefmt) |
250 else: | 510 |
251 raise ValueError("Unknown handler type") | 511 def format(self, record): |
252 hdlr.setFormatter(formatter) | 512 s = super(SatFormatter, self).format(record) |
253 root_logger.addHandler(hdlr) | 513 if self.with_color: |
254 root_logger.setLevel(level) | 514 s = _ansiColors(record.levelname, s) |
255 if name_filter is not None: | 515 return s |
256 hdlr.addFilter(name_filter) | 516 |
257 else: | 517 self.formatter = SatFormatter(format_) |
258 root_logger.warning(u"Handlers already set on root logger") | 518 |
259 | 519 def configureOutput(self, output): |
260 def _configureBasic(level=None, fmt=None, output=None, logger=None, colors=False, force_colors=False): | 520 self.manageOutputs(output) |
261 """Configure basic backend | 521 |
262 @param level: same as _configureStdLogging.level | 522 def configureLogger(self, logger): |
263 @param fmt: same as _configureStdLogging.fmt | 523 self.name_filter = FilterName(logger) if logger else None |
264 @param output: not implemented yet TODO | 524 |
265 @param logger: same as _configureStdLogging.logger | 525 def configureColors(self, colors, force_colors): |
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 | 526 import sys |
289 if force_colors or sys.stdout.isatty(): | 527 self.with_color = colors & (sys.stdout.isatty() or force_colors) |
290 # we need colors | 528 if not colors and force_colors: |
291 Logger.post_treat = lambda self, level, message: _ansiColors(level, message) | 529 raise ValueError("force_colors can't be used if colors is False") |
292 elif force_colors: | 530 |
293 raise ValueError("force_colors can't be used if colors is False") | 531 def _addHandler(self, root_logger, hdlr): |
532 hdlr.setFormatter(self.formatter) | |
533 root_logger.addHandler(hdlr) | |
534 root_logger.setLevel(self.level) | |
535 if self.name_filter is not None: | |
536 hdlr.addFilter(self.name_filter) | |
537 | |
538 def postTreatment(self): | |
539 import logging | |
540 root_logger = logging.getLogger() | |
541 if len(root_logger.handlers) == 0: | |
542 for handler, options in _handlers.items(): | |
543 if handler == C.LOG_OPT_OUTPUT_DEFAULT: | |
544 hdlr = logging.StreamHandler() | |
545 self._addHandler(root_logger, hdlr) | |
546 elif handler == C.LOG_OPT_OUTPUT_MEMORY: | |
547 import logging.handlers | |
548 hdlr = logging.handlers.BufferingHandler(options) | |
549 _handlers[handler] = hdlr # we keep a reference to the handler to read the buffer later | |
550 self._addHandler(root_logger, hdlr) | |
551 elif handler == C.LOG_OPT_OUTPUT_FILE: | |
552 import os.path | |
553 for path in options: | |
554 hdlr = logging.FileHandler(os.path.expanduser(path)) | |
555 self._addHandler(root_logger, hdlr) | |
556 else: | |
557 raise ValueError("Unknown handler type") | |
558 else: | |
559 root_logger.warning(u"Handlers already set on root logger") | |
560 | |
294 | 561 |
295 def configure(backend=C.LOG_BACKEND_STANDARD, **options): | 562 def configure(backend=C.LOG_BACKEND_STANDARD, **options): |
296 """Configure logging behaviour | 563 """Configure logging behaviour |
297 @param backend: can be: | 564 @param backend: can be: |
298 C.LOG_BACKEND_STANDARD: use standard logging module | 565 C.LOG_BACKEND_STANDARD: use standard logging module |
302 global _backend | 569 global _backend |
303 if _backend is not None: | 570 if _backend is not None: |
304 raise exceptions.InternalError("Logging can only be configured once") | 571 raise exceptions.InternalError("Logging can only be configured once") |
305 _backend = backend | 572 _backend = backend |
306 | 573 |
307 if backend in (C.LOG_BACKEND_TWISTED, C.LOG_BACKEND_STANDARD): | 574 if backend == C.LOG_BACKEND_BASIC: |
308 if backend == C.LOG_BACKEND_TWISTED: | 575 ConfigureBasic(**options) |
309 from twisted.python import log | 576 |
310 observer = log.PythonLoggingObserver() | 577 elif backend == C.LOG_BACKEND_TWISTED: |
311 observer.start() | 578 ConfigureTwisted(**options) |
312 global getLogger | 579 |
313 global debug | 580 elif backend == C.LOG_BACKEND_STANDARD: |
314 global info | 581 ConfigureStandard(**options) |
315 global warning | |
316 global error | |
317 global critical | |
318 import logging | |
319 _configureStdLogging(logging, **options) | |
320 getLogger = logging.getLogger | |
321 debug = logging.debug | |
322 info = logging.info | |
323 warning = logging.warning | |
324 error = logging.error | |
325 critical = logging.critical | |
326 | |
327 elif backend == C.LOG_BACKEND_BASIC: | |
328 _configureBasic(**options) | |
329 | 582 |
330 else: | 583 else: |
331 raise ValueError("unknown backend") | 584 raise ValueError("unknown backend") |
332 | 585 |
333 def _parseOptions(options): | 586 def _parseOptions(options): |
373 | 626 |
374 _parseOptions(log_conf) | 627 _parseOptions(log_conf) |
375 configure(backend, **log_conf) | 628 configure(backend, **log_conf) |
376 | 629 |
377 def getLogger(name=C.LOG_BASE_LOGGER): | 630 def getLogger(name=C.LOG_BASE_LOGGER): |
378 return _loggers.setdefault(name, Logger(name)) | 631 if _backend in (None, C.LOG_BACKEND_BASIC): |
632 logger_class = Logger | |
633 elif _backend == C.LOG_BACKEND_TWISTED: | |
634 logger_class = TwistedLogger | |
635 else: | |
636 raise ValueError("This method should not be called with backend [%s]" % _backend) | |
637 return _loggers.setdefault(name, logger_class(name)) | |
379 | 638 |
380 _root_logger = getLogger() | 639 _root_logger = getLogger() |
381 | 640 |
382 def debug(msg): | 641 def debug(msg): |
383 _root_logger.debug(msg) | 642 _root_logger.debug(msg) |