在Python中获取更有用的'logging'模块错误输出

4 投票
2 回答
3487 浏览
提问于 2025-04-16 22:39

我在使用 Python 的 logger 模块(版本是 3.x,但这应该没什么关系),我发现当格式字符串出错时,错误信息是这样的:

Traceback (most recent call last):
  File "/usr/lib/python3.1/logging/__init__.py", line 770, in emit
    msg = self.format(record)
  File "/usr/lib/python3.1/logging/__init__.py", line 650, in format
    return fmt.format(record)
  File "/usr/lib/python3.1/logging/__init__.py", line 438, in format
    record.message = record.getMessage()
  File "/usr/lib/python3.1/logging/__init__.py", line 308, in getMessage
    msg = msg % self.args
TypeError: %d format: a number is required, not str

你可以看到,错误信息里并没有提到我代码中实际出错的地方。顺便说一下,我代码里的问题是:

logging.debug('This is a string %d', str(foo))

%d 改成 %s 就解决了这个问题。

我想问的是:我怎么才能从 logging 模块的输出中得到一些更有用的信息呢?我需要自己写一个 logger 吗?我该在哪里调整 logger 模块呢?

2 个回答

3

如果我理解得没错,你的问题是错误信息没有告诉你代码中是从哪里出错的。你需要自己去找出出错的那一行。

logging.debug('This is a string %d', str(foo))

这个过程可能会很麻烦。

日志模块的设计是为了在emit()调用时发生异常时,由处理器的handleError方法来处理这些异常:

def handleError(self, record):
    """
    Handle errors which occur during an emit() call.

    This method should be called from handlers when an exception is
    encountered during an emit() call. If raiseExceptions is false,
    exceptions get silently ignored. This is what is mostly wanted
    for a logging system - most users will not care about errors in
    the logging system, they are more interested in application errors.
    You could, however, replace this with a custom handler if you wish.
    The record which was being processed is passed in to this method.
    """

你可以重写这个方法,这样就能看到完整的错误追踪信息:

    import sys
    import logging

    class MyStreamHandler(logging.StreamHandler):
        def handleError(self, record):
            raise

    if __name__ == '__main__':
        console = MyStreamHandler()
        logger=logging.getLogger(__name__)
        logger.setLevel(logging.DEBUG)
        logger.addHandler(console)
        logger.debug('%d','ahh')

这样做的结果是

Traceback (most recent call last):
  File "/tmp/test.py", line 25, in <module>
    logger.debug('%d','ahh')
  File "/usr/lib/python2.6/logging/__init__.py", line 1036, in debug
    self._log(DEBUG, msg, args, **kwargs)
  File "/usr/lib/python2.6/logging/__init__.py", line 1165, in _log
    self.handle(record)
  File "/usr/lib/python2.6/logging/__init__.py", line 1175, in handle
    self.callHandlers(record)
  File "/usr/lib/python2.6/logging/__init__.py", line 1212, in callHandlers
    hdlr.handle(record)
  File "/usr/lib/python2.6/logging/__init__.py", line 673, in handle
    self.emit(record)
  File "/usr/lib/python2.6/logging/__init__.py", line 796, in emit
    self.handleError(record)
  File "/usr/lib/python2.6/logging/__init__.py", line 768, in emit
    msg = self.format(record)
  File "/usr/lib/python2.6/logging/__init__.py", line 648, in format
    return fmt.format(record)
  File "/usr/lib/python2.6/logging/__init__.py", line 436, in format
    record.message = record.getMessage()
  File "/usr/lib/python2.6/logging/__init__.py", line 306, in getMessage
    msg = msg % self.args
TypeError: %d format: a number is required, not str

而使用普通的StreamHandler时,你只能看到:

Traceback (most recent call last):
  File "/usr/lib/python2.6/logging/__init__.py", line 768, in emit
    msg = self.format(record)
  File "/usr/lib/python2.6/logging/__init__.py", line 648, in format
    return fmt.format(record)
  File "/usr/lib/python2.6/logging/__init__.py", line 436, in format
    record.message = record.getMessage()
  File "/usr/lib/python2.6/logging/__init__.py", line 306, in getMessage
    msg = msg % self.args
TypeError: %d format: a number is required, not str
2

我刚准备发这个的时候,Unutbu已经抢先一步了。不过我还是把它放在这里:

你可以尝试对你正在使用的处理器进行子类化(下面的例子是用StreamHandler),然后重写格式化方法,用一个try:块把它包裹起来。

import traceback, logging

class MyStreamHandler(logging.StreamHandler):
    def format(self, record):
        try:
            return logging.StreamHandler.format(self, record)
        except TypeError:
            # Print a stack trace that includes the original log call
            traceback.print_stack() 


if __name__ == "__main__":
    log = logging.getLogger("testlogger")
    handler = MyStreamHandler()

    log.addHandler(handler)

    log.error("Try interpolating an int correctly: %i", 1)

    log.error("Now try passing a string to an int: %d", 'abc')

    log.error("And then a string to a string %s", 'abc')

这给我的结果是:

Try interpolating an int correctly: 1
  File "logtest2.py", line 19, in ?
    log.error("Now try passing a string to an int: %d", 'abc')
  File "/usr/lib64/python2.4/logging/__init__.py", line 999, in error
    apply(self._log, (ERROR, msg, args), kwargs)
  File "/usr/lib64/python2.4/logging/__init__.py", line 1079, in _log
    self.handle(record)
  File "/usr/lib64/python2.4/logging/__init__.py", line 1089, in handle
    self.callHandlers(record)
  File "/usr/lib64/python2.4/logging/__init__.py", line 1126, in callHandlers
    hdlr.handle(record)
  File "/usr/lib64/python2.4/logging/__init__.py", line 642, in handle
    self.emit(record)
  File "/usr/lib64/python2.4/logging/__init__.py", line 731, in emit
    msg = self.format(record)
  File "logtest2.py", line 8, in format
    traceback.print_stack()
None
And then a string to a string abc

我不会把这个留在任何生产代码里,但它应该能帮助你找到一些像

log.error("%d", 'a string')

撰写回答