Python Forum
Workaround to use format() syntax in loggers
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Workaround to use format() syntax in loggers
#1
Simply use BraceLogger.getLogger() instead of logging.getLogger() as shown in the code below. This tip is derived from a paragraph in the logging cookbook.
import logging

__version__ = "2018.11.03"

class BraceMessage(object):
    #see: https://docs.python.org/3/howto/logging-cookbook.html#using-custom-message-objects
    def __init__(self, msg, *args, **kwargs):
        self.msg = msg
        self.args = args
        self.kwargs = kwargs

    def __str__(self):
        try:
            return self.msg.format(*self.args, **self.kwargs)
        except Exception as exc:
            return "Interpolation error during message formatting\n    .format({}, *{}, **{})\n{}: {}".format(
                repr(self.msg), self.args, self.kwargs,
                type(exc).__name__, str(exc))

class BraceLogger(logging.getLoggerClass()):
    _reserved = (('exc_info', None), ('extra', None), ('stack_info', False))
    def _log(self, level, msg, args, **kwargs):
        d = {k: kwargs.pop(k, v) for k, v in self._reserved}
        return super()._log(
            level, BraceMessage(msg, *args, **kwargs), (), **d)
    
    def __init__(self, *args, **kwargs):
        raise TypeError('BraceLogger class cannot be directly instantiated')

    @classmethod
    def getLogger(cls, name):
        logger = logging.getLogger(name)
        # this is frowned upon, but it is so convenient here!
        logger.__class__ = cls
        return logger


if __name__ == '__main__':
    logging.basicConfig()
    log = BraceLogger.getLogger(__name__)
    log.setLevel(logging.DEBUG)
    log.info('{} Todays wisdom is that {x} + {y} = {z}', 'Hello!', x=1, z=3, y=2)
    try:
        1./0.
    except Exception:
        log.exception('Something {} happened!', 'BAD')
    log.warning("This is the END...")
Output:
INFO:__main__:Hello! Todays wisdom is that 1 + 2 = 3 ERROR:__main__:Something BAD happened! Traceback (most recent call last): File "bracelogger.py", line 42, in <module> 1./0. ZeroDivisionError: float division by zero WARNING:__main__:This is the END...
Note: this code is not very clean because we assign to the __class__ member instead of trying to properly create a subclass instance. On the other hand, this minimizes the interference with the logging module's internals.
Reply
#2
In this alternative version, we only patch the logger instance by adding or updating a member _log without changing its type
import logging

__version__ = "2018.11.04.1"

class BraceMessage(object):
    #see: https://docs.python.org/3/howto/logging-cookbook.html#using-custom-message-objects
    def __init__(self, msg, args, kwargs):
        self.msg = msg
        self.args = args
        self.kwargs = kwargs

    def __str__(self):
        try:
            return str(self.msg).format(*self.args, **self.kwargs)
        except Exception as exc:
            return "Interpolation error during message formatting\n    .format({}, *{}, **{})\n{}: {}".format(
                repr(self.msg), self.args, self.kwargs,
                type(exc).__name__, str(exc))


class _LogMethod:
    _reserved = (('exc_info', None), ('extra', None), ('stack_info', False))
    def __init__(self, logger):
        self._log = logger._log
        
    def __call__(self, level, msg, args, **kwargs):
        d = {k: kwargs.pop(k, v) for k, v in self._reserved}
        return self._log(level, BraceMessage(msg, args, kwargs), (), **d)
    
def getLogger(name):
    logger = logging.getLogger(name)
    logger._log = _LogMethod(logger)
    return logger

if __name__ == '__main__':
    logging.basicConfig()
    log = getLogger(__name__)
    log.setLevel(logging.DEBUG)
    log.info("{} Today's wisdom is that {x} + {y} = {z}", 'Hello!', x=1, z=3, y=2)
    try:
        1./0.
    except Exception:
        log.exception('Something {} happened!', 'BAD')
    log.warning("This is the END...")
Reply


Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020