在Python中获得更有用的“logging”模块错误输出

4

我正在使用Python(版本为3.x,但应该没有关系)的logger模块,我发现格式字符串中的错误会被报告如下:

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))

%s中的%d更改解决了问题。

我的问题是:如何从logging模块输出中获取一些稍微有用的信息?我需要编写自己的日志记录器吗?在哪里调整logger模块?


我猜想的是,如果你的日志调用有误,默默失败比由于日志记录而导致应用程序出现异常更好。按照这种思路,该模块应该故意打印(或最好在错误日志中记录)调用上方的所有堆栈帧,而不仅仅是异常帧。我建议在 Python 邮件列表上提出这个问题。可能是用户邮件列表。 - wberry
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

谢谢,这正是我所期望的。有点令人不安的是,这个例子不在教程的第二行。但也许我没有找够。谢谢。 - lorenzog

2

我刚准备发布这个内容,但是Unutbu已经超过我了。但是无论如何,这里是:

你可以尝试对你正在使用的处理程序进行子类化(以下示例使用StreamHandler),并覆盖format方法以使用一个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')

我更喜欢你的答案,因为它将错误消息显示在最后,而不是在堆栈顶部。谢谢。 - lorenzog

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接