繁体   English   中英

在 Python 中记录带有回溯的异常

[英]Log exception with traceback in Python

如何记录我的 Python 异常?

try:
    do_something()
except:
    # How can I log my exception here, complete with its traceback?

except:处理程序/块中使用logging.exception来记录当前异常以及跟踪信息,并带有一条消息。

import logging
LOG_FILENAME = '/tmp/logging_example.out'
logging.basicConfig(filename=LOG_FILENAME, level=logging.DEBUG)

logging.debug('This message should go to the log file')

try:
    run_my_stuff()
except:
    logging.exception('Got exception on main handler')
    raise

现在查看日志文件/tmp/logging_example.out

DEBUG:root:This message should go to the log file
ERROR:root:Got exception on main handler
Traceback (most recent call last):
  File "/tmp/teste.py", line 9, in <module>
    run_my_stuff()
NameError: name 'run_my_stuff' is not defined

使用exc_info选项可能会更好,仍然是警告或错误标题:

try:
    # coode in here
except Exception as e:
    logging.error(e, exc_info=True)

我的工作最近要求我记录我们应用程序中的所有回溯/异常。 我尝试了许多其他人在网上发布的技术,例如上面的技术,但最终选择了一种不同的方法。 覆盖traceback.print_exception

我在http://www.bbarrows.com/ 上写了一篇文章,这会更容易阅读,但我也会将其粘贴在这里。

当我的任务是记录我们的软件可能在野外遇到的所有异常时,我尝试了许多不同的技术来记录我们的 Python 异常回溯。 起初我认为python系统异常钩子,sys.excepthook将是插入日志代码的完美位置。 我正在尝试类似的东西:

import traceback
import StringIO
import logging
import os, sys

def my_excepthook(excType, excValue, traceback, logger=logger):
    logger.error("Logging an uncaught exception",
                 exc_info=(excType, excValue, traceback))

sys.excepthook = my_excepthook  

这适用于主线程,但我很快发现我的 sys.excepthook 在我的进程启动的任何新线程中都不存在。 这是一个大问题,因为大多数事情都发生在这个项目的线程中。

在谷歌搜索并阅读大量文档后,我发现最有用的信息来自 Python 问题跟踪器。

该线程上的第一篇文章显示了sys.excepthook不跨线程持久化的工作示例(如下所示)。 显然这是预期的行为。

import sys, threading

def log_exception(*args):
    print 'got exception %s' % (args,)
sys.excepthook = log_exception

def foo():
    a = 1 / 0

threading.Thread(target=foo).start()

这个 Python 问题线程上的消息确实导致了 2 个建议的黑客攻击。 子类化Thread并将 run 方法包装在我们自己的 try except 块中以捕获和记录异常,或者猴子补丁threading.Thread.run在您自己的 try except 块中运行并记录异常。

在我看来,子类化Thread的第一种方法在您的代码中不太优雅,因为您必须在任何想要拥有日志记录线程的地方导入和使用您的自定义Thread类。 这最终很麻烦,因为我必须搜索我们的整个代码库并用这个自定义Thread替换所有普通Threads 但是,该Thread在做什么很清楚,如果自定义日志记录代码出现问题,人们可以更轻松地进行诊断和调试。 客户日志记录线程可能如下所示:

class TracebackLoggingThread(threading.Thread):
    def run(self):
        try:
            super(TracebackLoggingThread, self).run()
        except (KeyboardInterrupt, SystemExit):
            raise
        except Exception, e:
            logger = logging.getLogger('')
            logger.exception("Logging an uncaught exception")

猴子修补threading.Thread.run的第二种方法很好,因为我可以在__main__之后立即运行它一次,并在所有异常中检测我的日志记录代码。 Monkey 补丁调试起来可能很烦人,因为它改变了某些东西的预期功能。 来自 Python 问题跟踪器的建议补丁是:

def installThreadExcepthook():
    """
    Workaround for sys.excepthook thread bug
    From
http://spyced.blogspot.com/2007/06/workaround-for-sysexcepthook-bug.html

(https://sourceforge.net/tracker/?func=detail&atid=105470&aid=1230540&group_id=5470).
    Call once from __main__ before creating any threads.
    If using psyco, call psyco.cannotcompile(threading.Thread.run)
    since this replaces a new-style class method.
    """
    init_old = threading.Thread.__init__
    def init(self, *args, **kwargs):
        init_old(self, *args, **kwargs)
        run_old = self.run
        def run_with_except_hook(*args, **kw):
            try:
                run_old(*args, **kw)
            except (KeyboardInterrupt, SystemExit):
                raise
            except:
                sys.excepthook(*sys.exc_info())
        self.run = run_with_except_hook
    threading.Thread.__init__ = init

直到我开始测试我的异常日志记录,我才意识到我做错了。

为了测试我已经放置了一个

raise Exception("Test")

在我的代码中的某个地方。 然而,包装一个调用这个方法的方法是一个 try except 块,它打印出回溯并吞下异常。 这非常令人沮丧,因为我看到回溯带打印到 STDOUT 但没有被记录。 然后我决定记录回溯的一个更简单的方法就是修补所有 python 代码用来打印回溯本身的方法,traceback.print_exception。 我最终得到了类似于以下内容:

def add_custom_print_exception():
    old_print_exception = traceback.print_exception
    def custom_print_exception(etype, value, tb, limit=None, file=None):
        tb_output = StringIO.StringIO()
        traceback.print_tb(tb, limit, tb_output)
        logger = logging.getLogger('customLogger')
        logger.error(tb_output.getvalue())
        tb_output.close()
        old_print_exception(etype, value, tb, limit=None, file=None)
    traceback.print_exception = custom_print_exception

此代码将回溯写入字符串缓冲区并将其记录到日志错误中。 我有一个自定义日志处理程序设置了“customLogger”记录器,它接收 ERROR 级别的日志并将它们发送回家进行分析。

您可以通过将处理程序分配给sys.excepthook来记录主线程上所有未捕获的异常,也许使用Python 的日志记录函数exc_info参数

import sys
import logging

logging.basicConfig(filename='/tmp/foobar.log')

def exception_hook(exc_type, exc_value, exc_traceback):
    logging.error(
        "Uncaught exception",
        exc_info=(exc_type, exc_value, exc_traceback)
    )

sys.excepthook = exception_hook

raise Exception('Boom')

但是,如果您的程序使用线程,请注意使用threading.Thread创建的threading.Thread不会在其中发生未捕获的异常时触发sys.excepthook ,如 Python 问题跟踪器的问题1230540 中所述 有人建议在那里使用一些 hack 来解决此限制,例如猴子修补Thread.__init__以使用替代run方法覆盖self.run ,该方法将原始方法包装在try块中并从except块内部调用sys.excepthook 或者,您可以手动将每个线程的入口点包装在try / 中, except自己。

您可以在任何级别(调试、信息、...)使用记录器获取回溯。 请注意,使用logging.exception ,级别为 ERROR。

# test_app.py
import sys
import logging

logging.basicConfig(level="DEBUG")

def do_something():
    raise ValueError(":(")

try:
    do_something()
except Exception:
    logging.debug("Something went wrong", exc_info=sys.exc_info())
DEBUG:root:Something went wrong
Traceback (most recent call last):
  File "test_app.py", line 10, in <module>
    do_something()
  File "test_app.py", line 7, in do_something
    raise ValueError(":(")
ValueError: :(

编辑:

这也有效(使用python 3.6)

logging.debug("Something went wrong", exc_info=True)

我在找什么:

import sys
import traceback

exc_type, exc_value, exc_traceback = sys.exc_info()
traceback_in_var = traceback.format_tb(exc_traceback)

看:

未捕获的异常消息会转到 STDERR,因此您可以使用用于运行 Python 脚本的任何 shell 将 STDERR 发送到文件,而不是在 Python 本身中实现日志记录。 在 Bash 脚本中,您可以使用输出重定向来执行此操作,如BASH 指南 中所述

例子

将错误附加到文件,其他输出到终端:

./test.py 2>> mylog.log

用交错的 STDOUT 和 STDERR 输出覆盖文件:

./test.py &> mylog.log

我就是这样做的。

try:
    do_something()
except:
    # How can I log my exception here, complete with its traceback?
    import traceback
    traceback.format_exc() # this will print a complete trace to stout.

这是一个使用 sys.excepthook 的版本

import traceback
import sys

logger = logging.getLogger()

def handle_excepthook(type, message, stack):
     logger.error(f'An unhandled exception occured: {message}. Traceback: {traceback.format_tb(stack)}')

sys.excepthook = handle_excepthook

也许不那么时尚,但更容易:

#!/bin/bash
log="/var/log/yourlog"
/path/to/your/script.py 2>&1 | (while read; do echo "$REPLY" >> $log; done)

为了避免其他人可能在这里迷路,在日志中捕获它的最佳方法是使用traceback.format_exc()调用,然后为每一行拆分此字符串,以便在生成的日志文件中捕获:

import logging
import sys
import traceback

try:
  ...
except Exception as ex:
  # could be done differently, just showing you can split it apart to capture everything individually
  ex_t = type(ex).__name__
  err = str(ex)
  err_msg = f'[{ex_t}] - {err}'
  logging.error(err_msg)

  # go through the trackback lines and individually add those to the log as an error
  for l in traceback.format_exc().splitlines():
    logging.error(l)

下面是一个取自python 2.6 文档的简单示例:

import logging
LOG_FILENAME = '/tmp/logging_example.out'
logging.basicConfig(filename=LOG_FILENAME,level=logging.DEBUG,)

logging.debug('This message should go to the log file')

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM