from __future__ import absolute_import import os import logging import sys from .utils import cached_property from .utils.compat import WatchedFileHandler from .utils.encoding import safe_repr, safe_str from .utils.functional import maybe_promise __all__ = ['LogMixin', 'LOG_LEVELS', 'get_loglevel', 'setup_logging'] LOG_LEVELS = dict(logging._levelNames) LOG_LEVELS['FATAL'] = logging.FATAL LOG_LEVELS[logging.FATAL] = 'FATAL' DISABLE_TRACEBACKS = os.environ.get('DISABLE_TRACEBACKS') class NullHandler(logging.Handler): def emit(self, record): pass def get_logger(logger): if isinstance(logger, basestring): logger = logging.getLogger(logger) if not logger.handlers: logger.addHandler(NullHandler()) return logger def anon_logger(name): logger = logging.getLogger(name) logger.addHandler(NullHandler()) return logger def get_loglevel(level): if isinstance(level, basestring): return LOG_LEVELS[level] return level def naive_format_parts(fmt): l = fmt.split('%') for i, e in enumerate(l[1:]): if not e or not l[i - 1]: yield elif e[0] in ['r', 's']: yield e[0] def safeify_format(fmt, *args): for index, type in enumerate(naive_format_parts(fmt)): if not type: yield args[index] elif type == 'r': yield safe_repr(args[index]) elif type == 's': yield safe_str(args[index]) class LogMixin(object): def debug(self, *args, **kwargs): return self.log(logging.DEBUG, *args, **kwargs) def info(self, *args, **kwargs): return self.log(logging.INFO, *args, **kwargs) def warn(self, *args, **kwargs): return self.log(logging.WARN, *args, **kwargs) def error(self, *args, **kwargs): return self._error(logging.ERROR, *args, **kwargs) def critical(self, *args, **kwargs): return self._error(logging.CRITICAL, *args, **kwargs) def _error(self, severity, *args, **kwargs): kwargs.setdefault('exc_info', True) if DISABLE_TRACEBACKS: kwargs.pop('exc_info', None) return self.log(severity, *args, **kwargs) def annotate(self, text): return '%s - %s' % (self.logger_name, text) def log(self, severity, *args, **kwargs): if self.logger.isEnabledFor(severity): log = self.logger.log if len(args) > 1 and isinstance(args[0], basestring): expand = [maybe_promise(arg) for arg in args[1:]] return log(severity, self.annotate(args[0].replace('%r', '%s')), *list(safeify_format(args[0], *expand)), **kwargs) else: return self.logger.log(severity, self.annotate(' '.join(map(safe_str, args))), **kwargs) def get_logger(self): return get_logger(self.logger_name) def is_enabled_for(self, level): return self.logger.isEnabledFor(self.get_loglevel(level)) def get_loglevel(self, level): if not isinstance(level, int): return LOG_LEVELS[level] return level @cached_property def logger(self): return self.get_logger() @property def logger_name(self): return self.__class__.__name__ class Log(LogMixin): def __init__(self, name, logger=None): self._logger_name = name self._logger = logger def get_logger(self): if self._logger: return self._logger return LogMixin.get_logger(self) @property def logger_name(self): return self._logger_name def setup_logging(loglevel=None, logfile=None): logger = logging.getLogger() loglevel = get_loglevel(loglevel or 'ERROR') logfile = logfile if logfile else sys.__stderr__ if not logger.handlers: if hasattr(logfile, 'write'): handler = logging.StreamHandler(logfile) else: handler = WatchedFileHandler(logfile) logger.addHandler(handler) logger.setLevel(loglevel) return logger