Mercurial > libervia-backend
comparison src/core/log.py @ 1021:a836b6da2c5c
core (log): moved configuration to core.log_config; this avoid import issues with pyjamas.
author | Goffi <goffi@goffi.org> |
---|---|
date | Wed, 14 May 2014 12:51:24 +0200 |
parents | 0ea97f483464 |
children | 08f50fdac21b |
comparison
equal
deleted
inserted
replaced
1020:adbde4a3a52f | 1021:a836b6da2c5c |
---|---|
21 # XXX: this module use standard logging module when possible, but as SàT can work in different cases where logging is not the best choice (twisted, pyjamas, etc), it is necessary to have a dedicated module. Additional feature like environment variables and colors are also managed. | 21 # XXX: this module use standard logging module when possible, but as SàT can work in different cases where logging is not the best choice (twisted, pyjamas, etc), it is necessary to have a dedicated module. Additional feature like environment variables and colors are also managed. |
22 | 22 |
23 from sat.core.constants import Const as C | 23 from sat.core.constants import Const as C |
24 from sat.core import exceptions | 24 from sat.core import exceptions |
25 | 25 |
26 _backend = None | 26 backend = None |
27 _loggers = {} | 27 _loggers = {} |
28 _handlers = {} | 28 handlers = {} |
29 | 29 |
30 | 30 |
31 class Filtered(Exception): | 31 class Filtered(Exception): |
32 pass | 32 pass |
33 | 33 |
99 except TypeError: | 99 except TypeError: |
100 return message | 100 return message |
101 except KeyError as e: | 101 except KeyError as e: |
102 if e.args[0] == 'profile': | 102 if e.args[0] == 'profile': |
103 # XXX: %(profile)s use some magic with introspection, for debugging purpose only *DO NOT* use in production | 103 # XXX: %(profile)s use some magic with introspection, for debugging purpose only *DO NOT* use in production |
104 record['profile'] = _getProfile() | 104 record['profile'] = configure_cls[backend].getProfile() |
105 return self.fmt % record | 105 return self.fmt % record |
106 else: | 106 else: |
107 raise e | 107 raise e |
108 | 108 |
109 | |
110 def debug(self, msg): | 109 def debug(self, msg): |
111 self.log(C.LOG_LVL_DEBUG, msg) | 110 self.log(C.LOG_LVL_DEBUG, msg) |
112 | 111 |
113 def info(self, msg): | 112 def info(self, msg): |
114 self.log(C.LOG_LVL_INFO, msg) | 113 self.log(C.LOG_LVL_INFO, msg) |
119 def error(self, msg): | 118 def error(self, msg): |
120 self.log(C.LOG_LVL_ERROR, msg) | 119 self.log(C.LOG_LVL_ERROR, msg) |
121 | 120 |
122 def critical(self, msg): | 121 def critical(self, msg): |
123 self.log(C.LOG_LVL_CRITICAL, msg) | 122 self.log(C.LOG_LVL_CRITICAL, msg) |
124 | |
125 | |
126 class TwistedLogger(Logger): | |
127 colors = True | |
128 force_colors = False | |
129 | |
130 def __init__(self, *args, **kwargs): | |
131 super(TwistedLogger, self).__init__(*args, **kwargs) | |
132 from twisted.python import log | |
133 self.twisted_log = log | |
134 | |
135 def out(self, message, level=None): | |
136 """Actually log the message | |
137 | |
138 @param message: formatted message | |
139 """ | |
140 self.twisted_log.msg(message.encode('utf-8', 'ignore'), sat_logged=True, level=level) | |
141 | 123 |
142 | 124 |
143 class FilterName(object): | 125 class FilterName(object): |
144 """Filter on logger name according to a regex""" | 126 """Filter on logger name according to a regex""" |
145 | 127 |
167 pass | 149 pass |
168 log_record = LogRecord() | 150 log_record = LogRecord() |
169 log_record.name = dict_record['name'] | 151 log_record.name = dict_record['name'] |
170 return self.filter(log_record) == 1 | 152 return self.filter(log_record) == 1 |
171 | 153 |
172 def memoryGet(size=None): | 154 |
173 """Return buffered logs | 155 class ConfigureBase(object): |
174 | |
175 @param size: number of logs to return | |
176 """ | |
177 if not C.LOG_OPT_OUTPUT_MEMORY in _handlers: | |
178 raise ValueError('memory output is not used') | |
179 if _backend == C.LOG_BACKEND_STANDARD: | |
180 mem_handler = _handlers[C.LOG_OPT_OUTPUT_MEMORY] | |
181 return (log_msg for log_msg in mem_handler.buffer[size if size is None else -size:]) | |
182 else: | |
183 raise NotImplementedError | |
184 | |
185 def _getProfile(): | |
186 """Try to find profile value using introspection""" | |
187 import inspect | |
188 stack = inspect.stack() | |
189 current_path = stack[0][1] | |
190 for frame_data in stack[:-1]: | |
191 if frame_data[1] != current_path: | |
192 if _backend == C.LOG_BACKEND_STANDARD and "/logging/__init__.py" in frame_data[1]: | |
193 continue | |
194 break | |
195 | |
196 frame = frame_data[0] | |
197 args = inspect.getargvalues(frame) | |
198 try: | |
199 profile = args.locals.get('profile') or args.locals['profile_key'] | |
200 except (TypeError, KeyError): | |
201 try: | |
202 try: | |
203 profile = args.locals['self'].profile | |
204 except AttributeError: | |
205 try: | |
206 profile = args.locals['self'].parent.profile | |
207 except AttributeError: | |
208 profile = args.locals['self'].host.profile # used in quick_frontend for single profile configuration | |
209 except Exception: | |
210 # we can't find profile, we return an empty value | |
211 profile = '' | |
212 return profile | |
213 | |
214 def _ansiColors(level, message): | |
215 """Colorise message depending on level for terminals | |
216 | |
217 @param level: one of C.LOG_LEVELS | |
218 @param message: formatted message to log | |
219 @return: message with ANSI escape codes for coloration | |
220 """ | |
221 if level == C.LOG_LVL_DEBUG: | |
222 out = (C.ANSI_FG_CYAN, message, C.ANSI_RESET) | |
223 elif level == C.LOG_LVL_WARNING: | |
224 out = (C.ANSI_FG_YELLOW, message, C.ANSI_RESET) | |
225 elif level == C.LOG_LVL_ERROR: | |
226 out = (C.ANSI_FG_RED, | |
227 C.ANSI_BLINK, | |
228 r'/!\ ', | |
229 C.ANSI_BLINK_OFF, | |
230 message, | |
231 C.ANSI_RESET) | |
232 elif level == C.LOG_LVL_CRITICAL: | |
233 out = (C.ANSI_BOLD, | |
234 C.ANSI_FG_RED, | |
235 'Guru Meditation ', | |
236 C.ANSI_NORMAL_WEIGHT, | |
237 message, | |
238 C.ANSI_RESET) | |
239 else: | |
240 out = message | |
241 return ''.join(out) | |
242 | |
243 | |
244 class Configure(object): | |
245 LOGGER_CLASS = Logger | 156 LOGGER_CLASS = Logger |
246 | 157 |
247 def __init__(self, level=None, fmt=None, output=None, logger=None, colors=False, force_colors=False): | 158 def __init__(self, level=None, fmt=None, output=None, logger=None, colors=False, force_colors=False): |
248 """Configure backend | 159 """Configure a backend |
160 | |
249 @param level: one of C.LOG_LEVELS | 161 @param level: one of C.LOG_LEVELS |
250 @param fmt: format string, pretty much as in std logging. Accept the following keywords (maybe more depending on backend): | 162 @param fmt: format string, pretty much as in std logging. Accept the following keywords (maybe more depending on backend): |
251 - "message" | 163 - "message" |
252 - "levelname" | 164 - "levelname" |
253 - "name" (logger name) | 165 - "name" (logger name) |
274 | 186 |
275 def preTreatment(self): | 187 def preTreatment(self): |
276 pass | 188 pass |
277 | 189 |
278 def configureLevel(self, level): | 190 def configureLevel(self, level): |
279 pass | 191 if level is not None: |
192 # we deactivate methods below level | |
193 level_idx = C.LOG_LEVELS.index(level) | |
194 def dev_null(self, msg): | |
195 pass | |
196 for _level in C.LOG_LEVELS[:level_idx]: | |
197 setattr(Logger, _level.lower(), dev_null) | |
280 | 198 |
281 def configureFormat(self, fmt): | 199 def configureFormat(self, fmt): |
282 pass | 200 if fmt is not None: |
201 if fmt != '%(message)s': # %(message)s is the same as None | |
202 Logger.fmt = fmt | |
283 | 203 |
284 def configureOutput(self, output): | 204 def configureOutput(self, output): |
285 pass | 205 if output is not None: |
206 if output != C.LOG_OPT_OUTPUT_SEP + C.LOG_OPT_OUTPUT_DEFAULT: | |
207 # TODO: manage other outputs | |
208 raise NotImplementedError("Basic backend only manage default output yet") | |
286 | 209 |
287 def configureLogger(self, logger): | 210 def configureLogger(self, logger): |
288 pass | 211 if logger: |
212 Logger.filter_name = FilterName(logger) | |
289 | 213 |
290 def configureColors(self, colors, force_colors): | 214 def configureColors(self, colors, force_colors): |
291 pass | 215 pass |
292 | 216 |
293 def postTreatment(self): | 217 def postTreatment(self): |
294 pass | 218 pass |
295 | 219 |
296 def manageOutputs(self, outputs_raw): | 220 def manageOutputs(self, outputs_raw): |
297 """ Parse output option in a backend agnostic way, and fill _handlers consequently | 221 """ Parse output option in a backend agnostic way, and fill handlers consequently |
298 | 222 |
299 @param outputs_raw: output option as enterred in environment variable or in configuration | 223 @param outputs_raw: output option as enterred in environment variable or in configuration |
300 """ | 224 """ |
301 if not outputs_raw: | 225 if not outputs_raw: |
302 return | 226 return |
303 outputs = outputs_raw.split(C.LOG_OPT_OUTPUT_SEP) | 227 outputs = outputs_raw.split(C.LOG_OPT_OUTPUT_SEP) |
304 global _handlers | 228 global handlers |
305 if len(outputs) == 1: | 229 if len(outputs) == 1: |
306 _handlers[C.LOG_OPT_OUTPUT_FILE] = [outputs.pop()] | 230 handlers[C.LOG_OPT_OUTPUT_FILE] = [outputs.pop()] |
307 | 231 |
308 for output in outputs: | 232 for output in outputs: |
309 if not output: | 233 if not output: |
310 continue | 234 continue |
311 if output[-1] == ')': | 235 if output[-1] == ')': |
319 if output not in (C.LOG_OPT_OUTPUT_DEFAULT, C.LOG_OPT_OUTPUT_FILE, C.LOG_OPT_OUTPUT_MEMORY): | 243 if output not in (C.LOG_OPT_OUTPUT_DEFAULT, C.LOG_OPT_OUTPUT_FILE, C.LOG_OPT_OUTPUT_MEMORY): |
320 raise ValueError(u"Invalid output [%s]" % output) | 244 raise ValueError(u"Invalid output [%s]" % output) |
321 | 245 |
322 if output == C.LOG_OPT_OUTPUT_DEFAULT: | 246 if output == C.LOG_OPT_OUTPUT_DEFAULT: |
323 # no option for defaut handler | 247 # no option for defaut handler |
324 _handlers[output] = None | 248 handlers[output] = None |
325 elif output == C.LOG_OPT_OUTPUT_FILE: | 249 elif output == C.LOG_OPT_OUTPUT_FILE: |
326 if not options: | 250 if not options: |
327 ValueError("%(handler)s output need a path as option" % {'handler': output}) | 251 ValueError("%(handler)s output need a path as option" % {'handler': output}) |
328 _handlers.setdefault(output, []).append(options) | 252 handlers.setdefault(output, []).append(options) |
329 options = None # option are parsed, we can empty them | 253 options = None # option are parsed, we can empty them |
330 elif output == C.LOG_OPT_OUTPUT_MEMORY: | 254 elif output == C.LOG_OPT_OUTPUT_MEMORY: |
331 # we have memory handler, option can be the len limit or None | 255 # we have memory handler, option can be the len limit or None |
332 try: | 256 try: |
333 limit = int(options) | 257 limit = int(options) |
334 options = None # option are parsed, we can empty them | 258 options = None # option are parsed, we can empty them |
335 except (TypeError, ValueError): | 259 except (TypeError, ValueError): |
336 limit = C.LOG_OPT_OUTPUT_MEMORY_LIMIT | 260 limit = C.LOG_OPT_OUTPUT_MEMORY_LIMIT |
337 _handlers[output] = limit | 261 handlers[output] = limit |
338 | 262 |
339 if options: # we should not have unparsed options | 263 if options: # we should not have unparsed options |
340 raise ValueError(u"options [%(options)s] are not supported for %(handler)s output" % {'options': options, 'handler': output}) | 264 raise ValueError(u"options [%(options)s] are not supported for %(handler)s output" % {'options': options, 'handler': output}) |
341 | 265 |
342 | 266 @staticmethod |
343 class ConfigureBasic(Configure): | 267 def memoryGet(size=None): |
344 def configureLevel(self, level): | 268 """Return buffered logs |
345 if level is not None: | 269 |
346 # we deactivate methods below level | 270 @param size: number of logs to return |
347 level_idx = C.LOG_LEVELS.index(level) | 271 """ |
348 def dev_null(self, msg): | 272 raise NotImplementedError |
349 pass | 273 |
350 for _level in C.LOG_LEVELS[:level_idx]: | 274 @staticmethod |
351 setattr(Logger, _level.lower(), dev_null) | 275 def ansiColors(level, message): |
352 | 276 """Colorise message depending on level for terminals |
353 def configureFormat(self, fmt): | 277 |
354 if fmt is not None: | 278 @param level: one of C.LOG_LEVELS |
355 if fmt != '%(message)s': # %(message)s is the same as None | 279 @param message: formatted message to log |
356 Logger.fmt = fmt | 280 @return: message with ANSI escape codes for coloration |
357 | 281 """ |
358 def configureOutput(self, output): | 282 if level == C.LOG_LVL_DEBUG: |
359 if output is not None: | 283 out = (C.ANSI_FG_CYAN, message, C.ANSI_RESET) |
360 if output != C.LOG_OPT_OUTPUT_SEP + C.LOG_OPT_OUTPUT_DEFAULT: | 284 elif level == C.LOG_LVL_WARNING: |
361 # TODO: manage other outputs | 285 out = (C.ANSI_FG_YELLOW, message, C.ANSI_RESET) |
362 raise NotImplementedError("Basic backend only manage default output yet") | 286 elif level == C.LOG_LVL_ERROR: |
363 | 287 out = (C.ANSI_FG_RED, |
364 def configureLogger(self, logger): | 288 C.ANSI_BLINK, |
365 if logger: | 289 r'/!\ ', |
366 Logger.filter_name = FilterName(logger) | 290 C.ANSI_BLINK_OFF, |
367 | 291 message, |
368 def configureColors(self, colors, force_colors): | 292 C.ANSI_RESET) |
369 if colors: | 293 elif level == C.LOG_LVL_CRITICAL: |
370 import sys | 294 out = (C.ANSI_BOLD, |
371 if force_colors or sys.stdout.isatty(): # FIXME: isatty should be tested on each handler, not globaly | 295 C.ANSI_FG_RED, |
372 # we need colors | 296 'Guru Meditation ', |
373 Logger.post_treat = lambda self, level, message: _ansiColors(level, message) | 297 C.ANSI_NORMAL_WEIGHT, |
374 elif force_colors: | 298 message, |
375 raise ValueError("force_colors can't be used if colors is False") | 299 C.ANSI_RESET) |
376 | |
377 | |
378 class ConfigureTwisted(ConfigureBasic): | |
379 LOGGER_CLASS = TwistedLogger | |
380 | |
381 def changeObserver(self, observer, can_colors=False): | |
382 """Install a hook on observer to manage SàT specificities | |
383 | |
384 @param observer: original observer to hook | |
385 @param can_colors: True if observer can display ansi colors | |
386 """ | |
387 def observer_hook(event): | |
388 """redirect non SàT log to twisted_logger, and add colors when possible""" | |
389 if 'sat_logged' in event: # we only want our own logs, other are managed by twistedObserver | |
390 # we add colors if possible | |
391 if (can_colors and self.LOGGER_CLASS.colors) or self.LOGGER_CLASS.force_colors: | |
392 message = event.get('message', tuple()) | |
393 level = event.get('level', C.LOG_LVL_INFO) | |
394 if message: | |
395 event['message'] = (_ansiColors(level, ''.join(message)),) # must be a tuple | |
396 observer(event) # we can now call the original observer | |
397 | |
398 return observer_hook | |
399 | |
400 def changeFileLogObserver(self, observer): | |
401 """Install SàT hook for FileLogObserver | |
402 | |
403 if the output is a tty, we allow colors, else we don't | |
404 @param observer: original observer to hook | |
405 """ | |
406 log_obs = observer.__self__ | |
407 log_file = log_obs.write.__self__ | |
408 try: | |
409 can_colors = log_file.isatty() | |
410 except AttributeError: | |
411 can_colors = False | |
412 return self.changeObserver(observer, can_colors=can_colors) | |
413 | |
414 def installObserverHook(self, observer): | |
415 """Check observer type and install SàT hook when possible | |
416 | |
417 @param observer: observer to hook | |
418 @return: hooked observer or original one | |
419 """ | |
420 if hasattr(observer, '__self__'): | |
421 ori = observer | |
422 if isinstance(observer.__self__, self.log.FileLogObserver): | |
423 observer = self.changeFileLogObserver(observer) | |
424 elif isinstance(observer.__self__, self.log.DefaultObserver): | |
425 observer = self.changeObserver(observer, can_colors=True) | |
426 else: | |
427 # we use print because log system is not fully initialized | |
428 print("Unmanaged observer [%s]" % observer) | |
429 return observer | |
430 self.observers[ori] = observer | |
431 return observer | |
432 | |
433 def preTreatment(self): | |
434 """initialise needed attributes, and install observers hooks""" | |
435 self.observers = {} | |
436 from twisted.python import log | |
437 self.log = log | |
438 self.log_publisher = log.msg.__self__ | |
439 def addObserverObserver(self_logpub, other): | |
440 """Install hook so we know when a new observer is added""" | |
441 other = self.installObserverHook(other) | |
442 return self_logpub._originalAddObserver(other) | |
443 def removeObserverObserver(self_logpub, ori): | |
444 """removeObserver hook fix | |
445 | |
446 As we wrap the original observer, the original removeObserver may want to remove the original object instead of the wrapper, this method fix this | |
447 """ | |
448 if ori in self.observers: | |
449 self_logpub._originalRemoveObserver(self.observers[ori]) | |
450 else: | |
451 try: | |
452 self_logpub._originalRemoveObserver(ori) | |
453 except ValueError: | |
454 try: | |
455 ori in self.cleared_observers | |
456 except AttributeError: | |
457 raise ValueError("Unknown observer") | |
458 | |
459 # we replace addObserver/removeObserver by our own | |
460 log.LogPublisher._originalAddObserver = log.LogPublisher.addObserver | |
461 log.LogPublisher._originalRemoveObserver = log.LogPublisher.removeObserver | |
462 import types # see https://stackoverflow.com/a/4267590 (thx Chris Morgan/aaronasterling) | |
463 log.addObserver = types.MethodType(addObserverObserver, self.log_publisher, log.LogPublisher) | |
464 log.removeObserver = types.MethodType(removeObserverObserver, self.log_publisher, log.LogPublisher) | |
465 | |
466 # we now change existing observers | |
467 for idx, observer in enumerate(self.log_publisher.observers): | |
468 self.log_publisher.observers[idx] = self.installObserverHook(observer) | |
469 | |
470 | |
471 def configureLevel(self, level): | |
472 self.LOGGER_CLASS.level = level | |
473 super(ConfigureTwisted, self).configureLevel(level) | |
474 | |
475 def configureOutput(self, output): | |
476 import sys | |
477 if output is None: | |
478 output = C.LOG_OPT_OUTPUT_SEP + C.LOG_OPT_OUTPUT_DEFAULT | |
479 self.manageOutputs(output) | |
480 addObserver = self.log.addObserver | |
481 | |
482 if C.LOG_OPT_OUTPUT_DEFAULT in _handlers: | |
483 # default output is already managed, we just add output to stdout if we are in debug mode | |
484 from twisted.internet import defer | |
485 if defer.Deferred.debug: | |
486 addObserver(self.log.FileLogObserver(sys.stdout).emit) | |
487 else: | 300 else: |
488 # \\default is not in the output, so we remove current observers | 301 out = message |
489 self.cleared_observers = self.log_publisher.observers | 302 return ''.join(out) |
490 self.observers.clear() | 303 |
491 del self.log_publisher.observers[:] | 304 @staticmethod |
492 # and we forbid twistd to add any observer | 305 def getProfile(): |
493 self.log.addObserver = lambda other: None | 306 """Try to find profile value using introspection""" |
494 | 307 raise NotImplementedError |
495 if C.LOG_OPT_OUTPUT_FILE in _handlers: | 308 |
496 from twisted.python import logfile | 309 |
497 for path in _handlers[C.LOG_OPT_OUTPUT_FILE]: | 310 class ConfigureCustom(ConfigureBase): |
498 log_file = sys.stdout if path == '-' else logfile.LogFile.fromFullPath(path) | |
499 addObserver(self.log.FileLogObserver(log_file).emit) | |
500 | |
501 if C.LOG_OPT_OUTPUT_MEMORY in _handlers: | |
502 raise NotImplementedError("Memory observer is not implemented in Twisted backend") | |
503 | |
504 def configureColors(self, colors, force_colors): | |
505 self.LOGGER_CLASS.colors = colors | |
506 self.LOGGER_CLASS.force_colors = force_colors | |
507 if force_colors and not colors: | |
508 raise ValueError('colors must be True if force_colors is True') | |
509 | |
510 def postTreatment(self): | |
511 """Install twistedObserver which manage non SàT logs""" | |
512 def twistedObserver(event): | |
513 """Observer which redirect log message not produced by SàT to SàT logging system""" | |
514 if not 'sat_logged' in event: | |
515 # this log was not produced by SàT | |
516 from twisted.python import log | |
517 text = log.textFromEventDict(event) | |
518 if text is None: | |
519 return | |
520 twisted_logger = getLogger(C.LOG_TWISTED_LOGGER) | |
521 log_method = twisted_logger.error if event.get('isError', False) else twisted_logger.info | |
522 log_method(text.decode('utf-8')) | |
523 | |
524 self.log_publisher._originalAddObserver(twistedObserver) | |
525 | |
526 | |
527 class ConfigureCustom(ConfigureBasic): | |
528 LOGGER_CLASS = None | 311 LOGGER_CLASS = None |
529 | 312 |
530 def __init__(self, logger_class, *args, **kwargs): | 313 def __init__(self, logger_class, *args, **kwargs): |
531 ConfigureCustom.LOGGER_CLASS = logger_class | 314 ConfigureCustom.LOGGER_CLASS = logger_class |
532 | 315 |
533 | 316 |
534 class ConfigureStandard(Configure): | 317 configure_cls = { None: ConfigureBase, |
535 | 318 C.LOG_BACKEND_CUSTOM: ConfigureCustom |
536 def __init__(self, level=None, fmt=None, output=None, logger=None, colors=False, force_colors=False): | 319 } # XXX: (key: backend, value: Configure subclass) must be filled when new backend are added |
537 if fmt is None: | 320 |
538 fmt = C.LOG_OPT_FORMAT[1] | 321 |
539 if output is None: | 322 def configure(backend_, **options): |
540 output = C.LOG_OPT_OUTPUT[1] | |
541 super(ConfigureStandard, self).__init__(level, fmt, output, logger, colors, force_colors) | |
542 | |
543 def preTreatment(self): | |
544 """We use logging methods directly, instead of using Logger""" | |
545 global getLogger | |
546 global debug | |
547 global info | |
548 global warning | |
549 global error | |
550 global critical | |
551 import logging | |
552 getLogger = logging.getLogger | |
553 debug = logging.debug | |
554 info = logging.info | |
555 warning = logging.warning | |
556 error = logging.error | |
557 critical = logging.critical | |
558 | |
559 def configureLevel(self, level): | |
560 if level is None: | |
561 level = C.LOG_LVL_DEBUG | |
562 self.level = level | |
563 | |
564 def configureFormat(self, fmt): | |
565 import logging | |
566 | |
567 class SatFormatter(logging.Formatter): | |
568 u"""Formatter which manage SàT specificities""" | |
569 _format = fmt | |
570 _with_profile = '%(profile)s' in fmt | |
571 | |
572 def __init__(self, can_colors=False): | |
573 super(SatFormatter, self).__init__(self._format) | |
574 self.can_colors = can_colors | |
575 | |
576 def format(self, record): | |
577 if self._with_profile: | |
578 record.profile = _getProfile() | |
579 s = super(SatFormatter, self).format(record) | |
580 if self.with_colors and (self.can_colors or self.force_colors): | |
581 s = _ansiColors(record.levelname, s) | |
582 return s | |
583 | |
584 self.formatterClass = SatFormatter | |
585 | |
586 def configureOutput(self, output): | |
587 self.manageOutputs(output) | |
588 | |
589 def configureLogger(self, logger): | |
590 self.name_filter = FilterName(logger) if logger else None | |
591 | |
592 def configureColors(self, colors, force_colors): | |
593 self.formatterClass.with_colors = colors | |
594 self.formatterClass.force_colors = force_colors | |
595 if not colors and force_colors: | |
596 raise ValueError("force_colors can't be used if colors is False") | |
597 | |
598 def _addHandler(self, root_logger, hdlr, can_colors=False): | |
599 hdlr.setFormatter(self.formatterClass(can_colors)) | |
600 root_logger.addHandler(hdlr) | |
601 root_logger.setLevel(self.level) | |
602 if self.name_filter is not None: | |
603 hdlr.addFilter(self.name_filter) | |
604 | |
605 def postTreatment(self): | |
606 import logging | |
607 root_logger = logging.getLogger() | |
608 if len(root_logger.handlers) == 0: | |
609 for handler, options in _handlers.items(): | |
610 if handler == C.LOG_OPT_OUTPUT_DEFAULT: | |
611 hdlr = logging.StreamHandler() | |
612 try: | |
613 can_colors = hdlr.stream.isatty() | |
614 except AttributeError: | |
615 can_colors = False | |
616 self._addHandler(root_logger, hdlr, can_colors=can_colors) | |
617 elif handler == C.LOG_OPT_OUTPUT_MEMORY: | |
618 from logging.handlers import BufferingHandler | |
619 class SatMemoryHandler(BufferingHandler): | |
620 def emit(self, record): | |
621 super(SatMemoryHandler, self).emit(self.format(record)) | |
622 hdlr = SatMemoryHandler(options) | |
623 _handlers[handler] = hdlr # we keep a reference to the handler to read the buffer later | |
624 self._addHandler(root_logger, hdlr, can_colors=False) | |
625 elif handler == C.LOG_OPT_OUTPUT_FILE: | |
626 import os.path | |
627 for path in options: | |
628 hdlr = logging.FileHandler(os.path.expanduser(path)) | |
629 self._addHandler(root_logger, hdlr, can_colors=False) | |
630 else: | |
631 raise ValueError("Unknown handler type") | |
632 else: | |
633 root_logger.warning(u"Handlers already set on root logger") | |
634 | |
635 | |
636 def configure(backend=C.LOG_BACKEND_STANDARD, **options): | |
637 """Configure logging behaviour | 323 """Configure logging behaviour |
638 @param backend: can be: | 324 @param backend: can be: |
639 C.LOG_BACKEND_STANDARD: use standard logging module | |
640 C.LOG_BACKEND_TWISTED: use twisted logging module (with standard logging observer) | |
641 C.LOG_BACKEND_BASIC: use a basic print based logging | 325 C.LOG_BACKEND_BASIC: use a basic print based logging |
642 C.LOG_BACKEND_CUSTOM: use a given Logger subclass | 326 C.LOG_BACKEND_CUSTOM: use a given Logger subclass |
643 """ | 327 """ |
644 global _backend | 328 global backend |
645 if _backend is not None: | 329 if backend is not None: |
646 raise exceptions.InternalError("Logging can only be configured once") | 330 raise exceptions.InternalError("Logging can only be configured once") |
647 _backend = backend | 331 backend = backend_ |
648 | 332 |
649 if backend == C.LOG_BACKEND_BASIC: | 333 try: |
650 ConfigureBasic(**options) | 334 configure_class = configure_cls[backend] |
651 | 335 except KeyError: |
652 elif backend == C.LOG_BACKEND_TWISTED: | 336 raise ValueError("unknown backend [%s]" % backend) |
653 ConfigureTwisted(**options) | 337 if backend == C.LOG_BACKEND_CUSTOM: |
654 | |
655 elif backend == C.LOG_BACKEND_STANDARD: | |
656 ConfigureStandard(**options) | |
657 | |
658 elif backend == C.LOG_BACKEND_CUSTOM: | |
659 logger_class = options.pop('logger_class') | 338 logger_class = options.pop('logger_class') |
660 ConfigureCustom(logger_class, **options) | 339 configure_class(logger_class, **options) |
661 | |
662 else: | 340 else: |
663 raise ValueError("unknown backend") | 341 configure_class(**options) |
664 | 342 |
665 def _parseOptions(options): | 343 def memoryGet(size=None): |
666 """Parse string options as given in conf or environment variable, and return expected python value | 344 if not C.LOG_OPT_OUTPUT_MEMORY in handlers: |
667 | 345 raise ValueError('memory output is not used') |
668 @param options (dict): options with (key: name, value: string value) | 346 return configure_cls[backend].memoryGet(size) |
669 """ | |
670 COLORS = C.LOG_OPT_COLORS[0] | |
671 LEVEL = C.LOG_OPT_LEVEL[0] | |
672 | |
673 if COLORS in options: | |
674 if options[COLORS].lower() in ('1', 'true'): | |
675 options[COLORS] = True | |
676 elif options[COLORS] == 'force': | |
677 options[COLORS] = True | |
678 options['force_colors'] = True | |
679 else: | |
680 options[COLORS] = False | |
681 if LEVEL in options: | |
682 level = options[LEVEL].upper() | |
683 if level not in C.LOG_LEVELS: | |
684 level = C.LOG_LVL_INFO | |
685 options[LEVEL] = level | |
686 | |
687 def satConfigure(backend=C.LOG_BACKEND_STANDARD, const=None): | |
688 """Configure logging system for SàT, can be used by frontends | |
689 | |
690 logs conf is read in SàT conf, then in environment variables. It must be done before Memory init | |
691 @param backend: backend to use, it can be: | |
692 - C.LOG_BACKEND_BASIC: print based backend | |
693 - C.LOG_BACKEND_TWISTED: Twisted logging backend | |
694 - C.LOG_BACKEND_STANDARD: standard logging backend | |
695 @param const: Const class to use instead of sat.core.constants.Const (mainly used to change default values) | |
696 """ | |
697 if const is not None: | |
698 global C | |
699 C = const | |
700 import ConfigParser | |
701 import os | |
702 log_conf = {} | |
703 config = ConfigParser.SafeConfigParser() | |
704 config.read(C.CONFIG_FILES) | |
705 for opt_name, opt_default in C.LOG_OPTIONS(): | |
706 try: | |
707 log_conf[opt_name] = os.environ[''.join((C.ENV_PREFIX, C.LOG_OPT_PREFIX.upper(), opt_name.upper()))] | |
708 except KeyError: | |
709 try: | |
710 log_conf[opt_name] = config.get(C.LOG_OPT_SECTION, C.LOG_OPT_PREFIX + opt_name) | |
711 except (ConfigParser.NoOptionError, ConfigParser.NoSectionError): | |
712 log_conf[opt_name] = opt_default | |
713 | |
714 _parseOptions(log_conf) | |
715 configure(backend, **log_conf) | |
716 | 347 |
717 def getLogger(name=C.LOG_BASE_LOGGER): | 348 def getLogger(name=C.LOG_BASE_LOGGER): |
718 if _backend in (None, C.LOG_BACKEND_BASIC): | 349 try: |
719 logger_class = ConfigureBasic.LOGGER_CLASS | 350 logger_class = configure_cls[backend].LOGGER_CLASS |
720 elif _backend == C.LOG_BACKEND_TWISTED: | 351 except KeyError: |
721 logger_class = ConfigureTwisted.LOGGER_CLASS | 352 raise ValueError("This method should not be called with backend [%s]" % backend) |
722 elif _backend == C.LOG_BACKEND_CUSTOM: | |
723 logger_class = ConfigureCustom.LOGGER_CLASS | |
724 else: | |
725 raise ValueError("This method should not be called with backend [%s]" % _backend) | |
726 return _loggers.setdefault(name, logger_class(name)) | 353 return _loggers.setdefault(name, logger_class(name)) |
727 | 354 |
728 _root_logger = getLogger() | 355 _root_logger = getLogger() |
729 | 356 |
730 def debug(msg): | 357 def debug(msg): |