如何记录Python异常?

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

当前回答

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

我最近的工作任务是记录应用程序中的所有回溯/异常。我尝试了其他人在网上发布的许多技巧,比如上面的方法,但最终选择了一种不同的方法。覆盖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”日志记录器,它将错误级别的日志发送回家进行分析。

您可以使用记录器在任何级别(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)

我要找的是:

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

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