如何记录Python异常?

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

下面是一个简单的例子,摘自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')

使用日志记录。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

我最近的工作任务是记录应用程序中的所有回溯/异常。我尝试了其他人在网上发布的许多技巧,比如上面的方法,但最终选择了一种不同的方法。覆盖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  

这是为主线程工作,但我很快发现我的系统。除了thook将不存在跨任何新的线程我的进程开始。这是一个大问题,因为在这个项目中,大多数事情都发生在线程中。

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

该线程的第一篇文章展示了sys. js的一个工作示例。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块中,以捕获和记录异常,要么是monkey patch threading.Thread.run在您自己的try except块中运行并记录异常。

The first method of subclassing Thread seems to me to be less elegant in your code as you would have to import and use your custom Thread class EVERYWHERE you wanted to have a logging thread. This ended up being a hassle because I had to search our entire code base and replace all normal Threads with this custom Thread. However, it was clear as to what this Thread was doing and would be easier for someone to diagnose and debug if something went wrong with the custom logging code. A custome logging thread might look like this:

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

monkey补丁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")

somewhere in my code. However, wrapping a a method that called this method was a try except block that printed out the traceback and swallowed the exception. This was very frustrating because I saw the traceback bring printed to STDOUT but not being logged. It was I then decided that a much easier method of logging the tracebacks was just to monkey patch the method that all python code uses to print the tracebacks themselves, traceback.print_exception. I ended up with something similar to the following:

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

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


也许没有那么时尚,但更简单:

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

使用exc_info选项可能更好,保留警告或错误标题:

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

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

例子

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

./test.py 2>> mylog.log

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

./test.py &> mylog.log

你可以记录主线程上所有未捕获的异常,方法是将一个处理程序分配给sys. exe。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')

If your program uses threads, however, then note that threads created using threading.Thread will not trigger sys.excepthook when an uncaught exception occurs inside them, as noted in Issue 1230540 on Python's issue tracker. Some hacks have been suggested there to work around this limitation, like monkey-patching Thread.__init__ to overwrite self.run with an alternative run method that wraps the original in a try block and calls sys.excepthook from inside the except block. Alternatively, you could just manually wrap the entry point for each of your threads in try/except yourself.


我要找的是:

import sys
import traceback

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

看到的:

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


您可以使用记录器在任何级别(DEBUG, INFO,…)获得跟踪。注意使用日志记录。异常时,级别为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)

下面是一个使用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

我就是这么做的。

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.

要关闭其他可能在这里丢失的内容,在日志中捕获它的最佳方法是使用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)