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):