【发布时间】:2010-12-03 06:26:30
【问题描述】:
如何记录我的 Python 错误?
try:
do_something()
except:
# How can I log my exception here, complete with its traceback?
【问题讨论】:
标签: python exception logging error-handling
如何记录我的 Python 错误?
try:
do_something()
except:
# How can I log my exception here, complete with its traceback?
【问题讨论】:
标签: python exception logging error-handling
我就是这样做的。
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.
【讨论】:
使用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
【讨论】:
logger = logging.getLogger('yourlogger')定义记录器,则必须编写logger.exception('...')才能使其正常工作...
logging.exception(...)(或logger.exception(...),如果这样配置)会这样做。但是在异常钩子处理程序的上下文中,我对这种方法exception 有点困惑:在这种情况下,它似乎没有打印出回溯。开始怀疑您的调用代码必须在 except 块中才能正常工作。
这是一个使用 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
【讨论】:
{traceback.format_exc()}代替{traceback.format_tb(stack)}怎么样?
您可以在任何级别(DEBUG、INFO、...)使用记录器获取回溯。注意使用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)
【讨论】:
使用exc_info选项可能会更好,仍然是警告或错误标题:
try:
# coode in here
except Exception as e:
logging.error(e, exc_info=True)
【讨论】:
exc_info= kwarg 叫什么了;谢谢!
logging.exception提供消息
logging.error,可以避免给''或' '这样的尴尬消息logging.exception。
我在寻找什么:
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 脚本中,您可以使用输出重定向来执行此操作,如 described in the BASH guide。
将错误附加到文件,其他输出到终端:
./test.py 2>> mylog.log
使用交错的 STDOUT 和 STDERR 输出覆盖文件:
./test.py &> mylog.log
【讨论】:
您可以通过将处理程序分配给sys.excepthook 来记录主线程上所有未捕获的异常,也许使用exc_info parameter of Python's logging functions:
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 创建的线程不会在其中发生未捕获的异常时触发sys.excepthook,如Issue 1230540 在 Python 问题中所述跟踪器。有人建议使用一些技巧来解决此限制,例如猴子修补Thread.__init__ 以使用替代run 方法覆盖run,该方法将原始方法包装在try 块中并从@ 内部调用sys.excepthook 987654335@ 块。或者,您可以自己手动将每个线程的入口点包装在 try/except 中。
【讨论】:
try: except 将所有内容包装在我的main() 函数中要优雅得多
我的工作最近要求我记录应用程序中的所有回溯/异常。我尝试了许多其他人在网上发布的技术,例如上面的技术,但最终选择了另一种方法。覆盖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__ 之后立即运行一次,并在所有异常中检测我的日志记录代码。猴子修补可能会令人讨厌,因为它会改变某些东西的预期功能。 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”记录器,该记录器获取错误级别的日志并将它们发送回家进行分析。
【讨论】:
add_custom_print_exception 似乎不在您链接到的网站上,而是那里有一些完全不同的最终代码。你会说哪个更好/更最终,为什么?谢谢!
logger.error(traceback.format_tb())(如果您也需要异常信息,也可以调用 format_exc()),而不是初始化 StringIO 并将异常打印到它。
raise Exception,在调用之前我调用了你定义的方法。什么都没有发生。 @Brad 和 @Christian
也许没有那么时尚,但更容易:
#!/bin/bash
log="/var/log/yourlog"
/path/to/your/script.py 2>&1 | (while read; do echo "$REPLY" >> $log; done)
【讨论】:
这是一个取自python 2.6 documentation的简单示例:
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')
【讨论】: