Codebase list logbook / 854b2064-14b6-4be9-9d9b-f6a59b8db224/main logbook / compat.py
854b2064-14b6-4be9-9d9b-f6a59b8db224/main

Tree @854b2064-14b6-4be9-9d9b-f6a59b8db224/main (Download .tar.gz)

compat.py @854b2064-14b6-4be9-9d9b-f6a59b8db224/main

9af17e1
 
 
 
 
 
 
 
 
 
 
ffdbf14
9af17e1
ffdbf14
9af17e1
 
 
ffdbf14
9949c7f
9af17e1
 
 
 
c2895a0
9af17e1
 
 
c2895a0
fea2298
 
 
9af17e1
 
 
c2895a0
 
9af17e1
 
 
 
 
 
 
 
 
 
 
c2895a0
9af17e1
c2895a0
 
9af17e1
 
c2895a0
9af17e1
 
 
c2895a0
9af17e1
 
 
 
 
d8b6d32
 
 
ffdbf14
 
 
 
 
 
d8b6d32
 
9af17e1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d8b6d32
9af17e1
 
 
 
 
 
 
fea2298
 
 
9af17e1
 
 
 
 
 
 
 
 
 
 
 
ffdbf14
 
 
 
9949c7f
ffdbf14
 
d8b6d32
fea2298
ffdbf14
 
fea2298
 
9af17e1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
# -*- coding: utf-8 -*-
"""
    logbook.compat
    ~~~~~~~~~~~~~~

    Backwards compatibility with stdlib's logging package and the
    warnings module.

    :copyright: (c) 2010 by Armin Ronacher, Georg Brandl.
    :license: BSD, see LICENSE for more details.
"""
import collections
import logging
import sys
import warnings
from datetime import date, datetime

import logbook
from logbook.helpers import u, string_types, iteritems, collections_abc

_epoch_ord = date(1970, 1, 1).toordinal()


def redirect_logging(set_root_logger_level=True):
    """Permanently redirects logging to the stdlib.  This also
    removes all otherwise registered handlers on root logger of
    the logging system but leaves the other loggers untouched.

    :param set_root_logger_level: controls of the default level of the legacy
       root logger is changed so that all legacy log messages get redirected
       to Logbook
    """
    del logging.root.handlers[:]
    logging.root.addHandler(RedirectLoggingHandler())
    if set_root_logger_level:
        logging.root.setLevel(logging.DEBUG)


class redirected_logging(object):
    """Temporarily redirects logging for all threads and reverts
    it later to the old handlers.  Mainly used by the internal
    unittests::

        from logbook.compat import redirected_logging
        with redirected_logging():
            ...
    """
    def __init__(self, set_root_logger_level=True):
        self.old_handlers = logging.root.handlers[:]
        self.old_level = logging.root.level
        self.set_root_logger_level = set_root_logger_level

    def start(self):
        redirect_logging(self.set_root_logger_level)

    def end(self, etype=None, evalue=None, tb=None):
        logging.root.handlers[:] = self.old_handlers
        logging.root.setLevel(self.old_level)

    __enter__ = start
    __exit__ = end


class LoggingCompatRecord(logbook.LogRecord):

    def _format_message(self, msg, *args, **kwargs):
        if kwargs:
            assert not args
            return msg % kwargs
        else:
            assert not kwargs
            return msg % tuple(args)


class RedirectLoggingHandler(logging.Handler):
    """A handler for the stdlib's logging system that redirects
    transparently to logbook.  This is used by the
    :func:`redirect_logging` and :func:`redirected_logging`
    functions.

    If you want to customize the redirecting you can subclass it.
    """

    def __init__(self):
        logging.Handler.__init__(self)

    def convert_level(self, level):
        """Converts a logging level into a logbook level."""
        if level >= logging.CRITICAL:
            return logbook.CRITICAL
        if level >= logging.ERROR:
            return logbook.ERROR
        if level >= logging.WARNING:
            return logbook.WARNING
        if level >= logging.INFO:
            return logbook.INFO
        return logbook.DEBUG

    def find_extra(self, old_record):
        """Tries to find custom data from the old logging record.  The
        return value is a dictionary that is merged with the log record
        extra dictionaries.
        """
        rv = vars(old_record).copy()
        for key in ('name', 'msg', 'args', 'levelname', 'levelno',
                    'pathname', 'filename', 'module', 'exc_info',
                    'exc_text', 'lineno', 'funcName', 'created',
                    'msecs', 'relativeCreated', 'thread', 'threadName',
                    'greenlet', 'processName', 'process'):
            rv.pop(key, None)
        return rv

    def find_caller(self, old_record):
        """Tries to find the caller that issued the call."""
        frm = sys._getframe(2)
        while frm is not None:
            if (frm.f_globals is globals() or
                    frm.f_globals is logbook.base.__dict__ or
                    frm.f_globals is logging.__dict__):
                frm = frm.f_back
            else:
                return frm

    def convert_time(self, timestamp):
        """Converts the UNIX timestamp of the old record into a
        datetime object as used by logbook.
        """
        return datetime.utcfromtimestamp(timestamp)

    def convert_record(self, old_record):
        """Converts an old logging record into a logbook log record."""
        args = old_record.args
        kwargs = None

        # Logging allows passing a mapping object, in which case args will be a mapping.
        if isinstance(args, collections_abc.Mapping):
            kwargs = args
            args = None
        record = LoggingCompatRecord(old_record.name,
                                     self.convert_level(old_record.levelno),
                                     old_record.msg, args,
                                     kwargs, old_record.exc_info,
                                     self.find_extra(old_record),
                                     self.find_caller(old_record))
        record.time = self.convert_time(old_record.created)
        return record

    def emit(self, record):
        logbook.dispatch_record(self.convert_record(record))


class LoggingHandler(logbook.Handler):
    """Does the opposite of the :class:`RedirectLoggingHandler`, it sends
    messages from logbook to logging.  Because of that, it's a very bad
    idea to configure both.

    This handler is for logbook and will pass stuff over to a logger
    from the standard library.

    Example usage::

        from logbook.compat import LoggingHandler, warn
        with LoggingHandler():
            warn('This goes to logging')
    """

    def __init__(self, logger=None, level=logbook.NOTSET, filter=None,
                 bubble=False):
        logbook.Handler.__init__(self, level, filter, bubble)
        if logger is None:
            logger = logging.getLogger()
        elif isinstance(logger, string_types):
            logger = logging.getLogger(logger)
        self.logger = logger

    def get_logger(self, record):
        """Returns the logger to use for this record.  This implementation
        always return :attr:`logger`.
        """
        return self.logger

    def convert_level(self, level):
        """Converts a logbook level into a logging level."""
        if level >= logbook.CRITICAL:
            return logging.CRITICAL
        if level >= logbook.ERROR:
            return logging.ERROR
        if level >= logbook.WARNING:
            return logging.WARNING
        if level >= logbook.INFO:
            return logging.INFO
        return logging.DEBUG

    def convert_time(self, dt):
        """Converts a datetime object into a timestamp."""
        year, month, day, hour, minute, second = dt.utctimetuple()[:6]
        days = date(year, month, 1).toordinal() - _epoch_ord + day - 1
        hours = days * 24 + hour
        minutes = hours * 60 + minute
        seconds = minutes * 60 + second
        return seconds

    def convert_record(self, old_record):
        """Converts a record from logbook to logging."""
        if sys.version_info >= (2, 5):
            # make sure 2to3 does not screw this up
            optional_kwargs = {'func': getattr(old_record, 'func_name')}
        else:
            optional_kwargs = {}
        record = logging.LogRecord(old_record.channel,
                                   self.convert_level(old_record.level),
                                   old_record.filename,
                                   old_record.lineno,
                                   old_record.message,
                                   (), old_record.exc_info,
                                   **optional_kwargs)
        for key, value in iteritems(old_record.extra):
            record.__dict__.setdefault(key, value)
        record.created = self.convert_time(old_record.time)
        return record

    def emit(self, record):
        self.get_logger(record).handle(self.convert_record(record))


def redirect_warnings():
    """Like :func:`redirected_warnings` but will redirect all warnings
    to the shutdown of the interpreter:

    .. code-block:: python

        from logbook.compat import redirect_warnings
        redirect_warnings()
    """
    redirected_warnings().__enter__()


class redirected_warnings(object):
    """A context manager that copies and restores the warnings filter upon
    exiting the context, and logs warnings using the logbook system.

    The :attr:`~logbook.LogRecord.channel` attribute of the log record will be
    the import name of the warning.

    Example usage:

    .. code-block:: python

        from logbook.compat import redirected_warnings
        from warnings import warn

        with redirected_warnings():
            warn(DeprecationWarning('logging should be deprecated'))
    """

    def __init__(self):
        self._entered = False

    def message_to_unicode(self, message):
        try:
            return u(str(message))
        except UnicodeError:
            return str(message).decode('utf-8', 'replace')

    def make_record(self, message, exception, filename, lineno):
        category = exception.__name__
        if exception.__module__ not in ('exceptions', 'builtins'):
            category = exception.__module__ + '.' + category
        rv = logbook.LogRecord(category, logbook.WARNING, message)
        # we don't know the caller, but we get that information from the
        # warning system.  Just attach them.
        rv.filename = filename
        rv.lineno = lineno
        return rv

    def start(self):
        if self._entered:  # pragma: no cover
            raise RuntimeError("Cannot enter %r twice" % self)
        self._entered = True
        self._filters = warnings.filters
        warnings.filters = self._filters[:]
        self._showwarning = warnings.showwarning

        def showwarning(message, category, filename, lineno,
                        file=None, line=None):
            message = self.message_to_unicode(message)
            record = self.make_record(message, category, filename, lineno)
            logbook.dispatch_record(record)
        warnings.showwarning = showwarning

    def end(self, etype=None, evalue=None, tb=None):
        if not self._entered:  # pragma: no cover
            raise RuntimeError("Cannot exit %r without entering first" % self)
        warnings.filters = self._filters
        warnings.showwarning = self._showwarning

    __enter__ = start
    __exit__ = end