如何在Python日志中格式化异常堆栈跟踪?

21

我在Python中创建的日志旨在作为文件暂时存储,这些文件将进一步处理成日志数据库。它们采用以管道分隔的格式来指定日志的处理方式,但logging.exception()会破坏我的标准,因为它添加了一个过多的字段和过多的换行符。

import logging
logging.basicConfig(filename='output.txt', 
                    format='%(asctime)s|%(levelname)s|%(message)s|', 
                    datefmt='%m/%d/%Y %I:%M:%S %p', 
                    level=logging.DEBUG)
logging.info('Sample message')

try:
    x = 1 / 0
except ZeroDivisionError as e:
    logging.exception('ZeroDivisionError: {0}'.format(e))

# output.txt
01/27/2015 02:09:01 PM|INFO|Sample message|
01/27/2015 02:09:01 PM|ERROR|ZeroDivisionError: integer division or modulo by zero|
Traceback (most recent call last):
  File "C:\Users\matr06586\Desktop\ETLstage\Python\blahblah.py", line 90, in <module>
    x = 1 / 0
ZeroDivisionError: integer division or modulo by zero

我应该如何处理或格式化带有空格和换行符的回溯信息?这些消息是在logging.exception()中不可或缺的,但当我尝试记录异常实例时,规避该函数似乎有点奇怪。我该如何记录我的回溯信息并对其进行格式化?回溯信息应该被忽略吗?

谢谢您的时间!


你是在询问该做什么,还是如何去做?你想要如何格式化日志文件中的错误信息完全取决于你自己。你希望它们看起来像什么? - BrenBarn
感谢您澄清这一点。理想情况下,我可以将回溯作为另一个以管道分隔的属性包含在与其余记录消息相同的行中。 - twoxmachine
3个回答

28
您可以定义自己的Formatter,并覆盖其中的方法,以便按您想要的方式格式化异常信息。以下是一个简单(但有效)的示例:
import logging

class OneLineExceptionFormatter(logging.Formatter):
    def formatException(self, exc_info):
        result = super(OneLineExceptionFormatter, self).formatException(exc_info)
        return repr(result) # or format into one line however you want to

    def format(self, record):
        s = super(OneLineExceptionFormatter, self).format(record)
        if record.exc_text:
            s = s.replace('\n', '') + '|'
        return s

fh = logging.FileHandler('output.txt', 'w')
f = OneLineExceptionFormatter('%(asctime)s|%(levelname)s|%(message)s|', '%m/%d/%Y %I:%M:%S %p')
fh.setFormatter(f)
root = logging.getLogger()
root.setLevel(logging.DEBUG)
root.addHandler(fh)
logging.info('Sample message')

try:
    x = 1 / 0
except ZeroDivisionError as e:
    logging.exception('ZeroDivisionError: {0}'.format(e))

这将仅生成两行:

01/28/2015 07:28:27 AM|INFO|Sample message|
01/28/2015 07:28:27 AM|ERROR|ZeroDivisionError: integer division or modulo by zero|'Traceback (most recent call last):\n  File "logtest2.py", line 23, in <module>\n    x = 1 / 0\nZeroDivisionError: integer division or modulo by zero'|

当然,您可以根据这个例子进行扩展以实现您想要的功能,例如通过 traceback 模块。


1
不确定为什么需要在此处重写formatException,如果不需要用“”包装traceback,则什么也不会发生,您能否解释一下为什么要重写formatException方法?谢谢。 - SKSKSKSK
1
@SunKe 因为异常本身可能包含换行符并需要格式化为单行。在特定情况下可能不需要,但是注释“或按照您想要的方式格式化为一行”指示您可能需要更改内容的位置。 - Vinay Sajip
@VinaySajip这个能不用文件处理器实现吗?我试图让单个日志行记录到stdout。 - Skewjo
1
@Skewjo 是的 - 只需将格式化程序附加到控制台处理程序(例如 StreamHandler 实例)。 - Vinay Sajip

6

对于我的使用情况,Vinay Sajip的代码效果不够好(我使用了更复杂的消息格式),因此我想出了这个代码(对我来说也更加简洁):

class OneLineExceptionFormatter(logging.Formatter):
    def format(self, record):
        if record.exc_info:
            # Replace record.msg with the string representation of the message
            # use repr() to prevent printing it to multiple lines
            record.msg = repr(super().formatException(record.exc_info))
            record.exc_info = None
            record.exc_text = None
        result = super().format(record)
        return result

因此,这个format()方法可以检测到将要记录异常,并且只需将其转换为其字符串表示形式,而日志消息的格式化仅针对该普通消息字符串进行。

我在Python 3中进行了测试。


该方法不考虑原始日志调用中的任何参数。例如:logging.exception("Unhandled exception when x=%s y=%s", "1", "2") 将失败。格式化是在调用 format 之后应用的,因此将 formatException 结果附加到 record.msg 是一个简单的方法,例如:record.msg += repr(super().formatException(record.exc_info)) -- 但总的来说,我认为基于官方文档的接受答案更好:https://docs.python.org/3/howto/logging-cookbook.html#customized-exception-formatting - NikT

0
你应该定义自己的函数,使用 traceback.extract_tb 来格式化回溯信息为你想要的语法,然后将其返回或写入文件中:
traceback.extract_tb(traceback[, limit])

返回从回溯对象中提取的最多 limit 个“预处理”堆栈跟踪条目的列表。 它对于堆栈跟踪的备用格式非常有用。如果省略或为 None,则提取所有条目。一个“预处理”的堆栈跟踪条目是一个4元组(文件名、行号、函数名、文本),表示通常为堆栈跟踪打印的信息。文本是一个字符串,其中包含前导和尾随空格;如果源不可用,则为None

https://docs.python.org/2/library/traceback.html


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