ms3.logger — ms3 0.1.2.post0.dev2+g9d887e1.dirty documentation

# Source code for ms3.logger

    import logging, sys, os
    from functools import wraps

    LEVELS = {
        'DEBUG': logging.DEBUG,
        'INFO': logging.INFO,
        'WARNING': logging.WARNING,
        'ERROR': logging.ERROR,
        'CRITICAL': logging.CRITICAL,
        'D': logging.DEBUG,
        'I': logging.INFO,
        'W': logging.WARNING,
        'E': logging.ERROR,
        'C': logging.CRITICAL,
    }

    CURRENT_LEVEL = logging.WARNING

    class ContextAdapter(logging.LoggerAdapter):
        """ This LoggerAdapter is designed to include the module and function that called the logger."""
        def process(self, msg, overwrite={}, stack_info=False, **kwargs):
            # my_context = kwargs.pop('my_context', self.extra['my_context'])
            fn, l, f, s = self.logger.findCaller(stack_info=stack_info)
            fname = os.path.basename(overwrite.pop('fname', fn))
            line = overwrite.pop('line', l)
            func = overwrite.pop('func', f)
            stack = overwrite.pop('stack', s)
            msg = msg.replace('\n', '\n\t')
            message = f"{fname} (line {line}) {func}():\n\t{msg}" if stack is None else f"{fname} line {line}, {func}():\n\t{msg}\n{stack}"
            return message, kwargs

    def get_logger(name=None, level=None, adapter=ContextAdapter):
        """The function gets or creates the logger `name` and returns it, by default through the given LoggerAdapter class."""
        global CURRENT_LEVEL
        if name not in logging.root.manager.loggerDict:
            config_logger(name)
        logger = logging.getLogger(name)

        if level is not None:
            CURRENT_LEVEL = LEVELS[level.upper()] if level.__class__ == str else level
        logger.setLevel(CURRENT_LEVEL)

        if adapter is not None:
            return adapter(logger, {})

        return logger

    def config_logger(name, level=None, logfile=None):
        """Configs the logger with name `name`. Overwrites existing config."""
        logger = logging.getLogger(name)
        format = '%(levelname)-7s %(name)s -- %(message)s'
        formatter = logging.Formatter(format)
        if level is not None:
            if level.__class__ == str:
                level = LEVELS[level]
            logger.setLevel(level)
        existing_handlers = [h for h in logger.handlers]
        if not any(True for h in existing_handlers if h.__class__ == logging.StreamHandler):
            streamHandler = logging.StreamHandler(sys.stdout)
            streamHandler.setFormatter(formatter)
            logger.addHandler(streamHandler)
        if logfile is not None:
            if not any(True for h in existing_handlers if h.__class__ == logging.FileHandler and h.baseFilename == logfile):
                fileHandler = logging.FileHandler(logfile, mode='w')
                fileHandler.setFormatter(formatter)
                logger.addHandler(fileHandler)

    def function_logger(f):
        """This decorator ensures that the decorated function can use the variable `logger` for logging and
           makes it possible to pass the function the keyword argument `logger` with either a Logger object or
           the name of one. If the keyword argument is not passed, the root logger is used.

        Example
        -------
        This is how the decorator can be used:

        .. code-block:: python

            from ms3.logger import function_logger

            @function_logger
            def log_this(msg):
                logger.warning(msg)

            if __name__ == '__main__':
                log_this('First test', logger='my_logger')
                log_this('Second Test')

        Output:

        .. code-block:: python

            WARNING my_logger -- function_logger.py (line 5) log_this():
                First test
            WARNING root -- function_logger.py (line 5) log_this():
                Second Test

        """

        @wraps(f)
        def logger(*args, **kwargs):
            l = kwargs.pop('logger', None)
            if l is None or l.__class__ == str:
                logg = get_logger(l)
            else:
                logg = l

            func_globals = f.__globals__
            saved_values = func_globals.copy()
            func_globals.update({'logger': logg})
            try:
                result = f(*args, **kwargs)
            finally:
                func_globals = saved_values  # Undo changes.
            return result

        return logger