log.py 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. """Logging Utilities."""
  2. from __future__ import absolute_import, unicode_literals
  3. import logging
  4. import numbers
  5. import os
  6. import sys
  7. from logging.handlers import WatchedFileHandler
  8. from .five import string_t
  9. from .utils.encoding import safe_repr, safe_str
  10. from .utils.functional import maybe_evaluate
  11. from .utils.objects import cached_property
  12. __all__ = ('LogMixin', 'LOG_LEVELS', 'get_loglevel', 'setup_logging')
  13. try:
  14. LOG_LEVELS = dict(logging._nameToLevel)
  15. LOG_LEVELS.update(logging._levelToName)
  16. except AttributeError:
  17. LOG_LEVELS = dict(logging._levelNames)
  18. LOG_LEVELS.setdefault('FATAL', logging.FATAL)
  19. LOG_LEVELS.setdefault(logging.FATAL, 'FATAL')
  20. DISABLE_TRACEBACKS = os.environ.get('DISABLE_TRACEBACKS')
  21. def get_logger(logger):
  22. """Get logger by name."""
  23. if isinstance(logger, string_t):
  24. logger = logging.getLogger(logger)
  25. if not logger.handlers:
  26. logger.addHandler(logging.NullHandler())
  27. return logger
  28. def get_loglevel(level):
  29. """Get loglevel by name."""
  30. if isinstance(level, string_t):
  31. return LOG_LEVELS[level]
  32. return level
  33. def naive_format_parts(fmt):
  34. parts = fmt.split('%')
  35. for i, e in enumerate(parts[1:]):
  36. yield None if not e or not parts[i - 1] else e[0]
  37. def safeify_format(fmt, args,
  38. filters={'s': safe_str,
  39. 'r': safe_repr}):
  40. for index, type in enumerate(naive_format_parts(fmt)):
  41. filt = filters.get(type)
  42. yield filt(args[index]) if filt else args[index]
  43. class LogMixin(object):
  44. """Mixin that adds severity methods to any class."""
  45. def debug(self, *args, **kwargs):
  46. return self.log(logging.DEBUG, *args, **kwargs)
  47. def info(self, *args, **kwargs):
  48. return self.log(logging.INFO, *args, **kwargs)
  49. def warn(self, *args, **kwargs):
  50. return self.log(logging.WARN, *args, **kwargs)
  51. def error(self, *args, **kwargs):
  52. kwargs.setdefault('exc_info', True)
  53. return self.log(logging.ERROR, *args, **kwargs)
  54. def critical(self, *args, **kwargs):
  55. kwargs.setdefault('exc_info', True)
  56. return self.log(logging.CRITICAL, *args, **kwargs)
  57. def annotate(self, text):
  58. return '%s - %s' % (self.logger_name, text)
  59. def log(self, severity, *args, **kwargs):
  60. if DISABLE_TRACEBACKS:
  61. kwargs.pop('exc_info', None)
  62. if self.logger.isEnabledFor(severity):
  63. log = self.logger.log
  64. if len(args) > 1 and isinstance(args[0], string_t):
  65. expand = [maybe_evaluate(arg) for arg in args[1:]]
  66. return log(severity,
  67. self.annotate(args[0].replace('%r', '%s')),
  68. *list(safeify_format(args[0], expand)), **kwargs)
  69. else:
  70. return self.logger.log(
  71. severity, self.annotate(' '.join(map(safe_str, args))),
  72. **kwargs)
  73. def get_logger(self):
  74. return get_logger(self.logger_name)
  75. def is_enabled_for(self, level):
  76. return self.logger.isEnabledFor(self.get_loglevel(level))
  77. def get_loglevel(self, level):
  78. if not isinstance(level, numbers.Integral):
  79. return LOG_LEVELS[level]
  80. return level
  81. @cached_property
  82. def logger(self):
  83. return self.get_logger()
  84. @property
  85. def logger_name(self):
  86. return self.__class__.__name__
  87. class Log(LogMixin):
  88. def __init__(self, name, logger=None):
  89. self._logger_name = name
  90. self._logger = logger
  91. def get_logger(self):
  92. if self._logger:
  93. return self._logger
  94. return LogMixin.get_logger(self)
  95. @property
  96. def logger_name(self):
  97. return self._logger_name
  98. def setup_logging(loglevel=None, logfile=None):
  99. """Setup logging."""
  100. logger = logging.getLogger()
  101. loglevel = get_loglevel(loglevel or 'ERROR')
  102. logfile = logfile if logfile else sys.__stderr__
  103. if not logger.handlers:
  104. if hasattr(logfile, 'write'):
  105. handler = logging.StreamHandler(logfile)
  106. else:
  107. handler = WatchedFileHandler(logfile)
  108. logger.addHandler(handler)
  109. logger.setLevel(loglevel)
  110. return logger