Python 中使用回溯记录异常

发布于 2024-08-06 15:44:22 字数 147 浏览 10 评论 0原文

如何记录 Python 异常?

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

How can I log my Python exceptions?

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(13

梦初启 2024-08-13 15:44:22

在 < 中使用 logging.exception code>except: 处理程序/块,用于记录当前异常以及跟踪信息,并在前面添加一条消息。

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

Use logging.exception from within the except: handler/block to log the current exception along with the trace information, prepended with a message.

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

Now looking at the log file, /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
栖竹 2024-08-13 15:44:22

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

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

Use exc_info options may be better, remains warning or error title:

try:
    # coode in here
except Exception as e:
    logging.error(e, exc_info=True)
維他命╮ 2024-08-13 15:44:22

我的工作最近要求我记录应用程序中的所有回溯/异常。我尝试了其他人在网上发布的多种技术,例如上面的技术,但最终选择了一种不同的方法。重写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 Issue 线程上的消息确实导致了 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”记录器,它获取错误级别日志并将其发送回家进行分析。

My job recently tasked me with logging all the tracebacks/exceptions from our application. I tried numerous techniques that others had posted online such as the one above but settled on a different approach. Overriding traceback.print_exception.

I have a write up at http://www.bbarrows.com/ That would be much easier to read but Ill paste it in here as well.

When tasked with logging all the exceptions that our software might encounter in the wild I tried a number of different techniques to log our python exception tracebacks. At first I thought that the python system exception hook, sys.excepthook would be the perfect place to insert the logging code. I was trying something similar to:

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  

This worked for the main thread but I soon found that the my sys.excepthook would not exist across any new threads my process started. This is a huge issue because most everything happens in threads in this project.

After googling and reading plenty of documentation the most helpful information I found was from the Python Issue tracker.

The first post on the thread shows a working example of the sys.excepthook NOT persisting across threads (as shown below). Apparently this is expected behavior.

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

The messages on this Python Issue thread really result in 2 suggested hacks. Either subclass Thread and wrap the run method in our own try except block in order to catch and log exceptions or monkey patch threading.Thread.run to run in your own try except block and log the exceptions.

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

The second method of monkey patching threading.Thread.run is nice because I could just run it once right after __main__ and instrument my logging code in all exceptions. Monkey patching can be annoying to debug though as it changes the expected functionality of something. The suggested patch from the Python Issue tracker was:

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

It was not until I started testing my exception logging I realized that I was going about it all wrong.

To test I had placed a

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

This code writes the traceback to a String Buffer and logs it to logging ERROR. I have a custom logging handler set up the 'customLogger' logger which takes the ERROR level logs and send them home for analysis.

赠我空喜 2024-08-13 15:44:22

您可以通过将处理程序分配给 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 在未捕获的异常时不会触发sys.excepthook发生在它们内部,如 Python 问题跟踪器上的 问题 1230540 中所述。有人建议采用一些技巧来解决此限制,例如猴子修补 Thread.__init__ 以使用替代的 run 方法覆盖 self.run它将原始内容包装在 try 块中,并从 except 块内部调用 sys.excepthook 。或者,您可以自己手动将每个线程的入口点包装在 try/except 中。

You can log all uncaught exceptions on the main thread by assigning a handler to sys.excepthook, perhaps using the 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')

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.

长安忆 2024-08-13 15:44:22

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

You can get the traceback using a logger, at any level (DEBUG, INFO, ...). Note that using logging.exception, the level is 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: :(

EDIT:

This works too (using python 3.6)

logging.debug("Something went wrong", exc_info=True)
命硬 2024-08-13 15:44:22

我在寻找什么:

import sys
import traceback

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

请参阅:

What I was looking for:

import sys
import traceback

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

See:

雄赳赳气昂昂 2024-08-13 15:44:22

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

示例

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

./test.py 2>> mylog.log

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

./test.py &> mylog.log

Uncaught exception messages go to STDERR, so instead of implementing your logging in Python itself you could send STDERR to a file using whatever shell you're using to run your Python script. In a Bash script, you can do this with output redirection, as described in the BASH guide.

Examples

Append errors to file, other output to the terminal:

./test.py 2>> mylog.log

Overwrite file with interleaved STDOUT and STDERR output:

./test.py &> mylog.log
囚你心 2024-08-13 15:44:22

这是使用 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

Here is a version that uses 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
归属感 2024-08-13 15:44:22

我就是这样做的。

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.

This is how I do it.

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.
歌入人心 2024-08-13 15:44:22

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

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

maybe not as stylish, but easier:

#!/bin/bash
log="/var/log/yourlog"
/path/to/your/script.py 2>&1 | (while read; do echo "$REPLY" >> $log; done)
揽月 2024-08-13 15:44:22

为了避免其他人可能在这里迷失,在日志中捕获它的最佳方法是使用 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)

To key off of others that may be getting lost in here, the way that works best with capturing it in logs is to use the traceback.format_exc() call and then split this string for each line in order to capture in the generated log file:

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)
杀手六號 2024-08-13 15:44:22

由于某种原因,简单地使用 log.exception(exc) 对我来说不起作用,我必须手动构建 exc_info 参数,如 这个答案

log.exception(exc, exc_info=(type(exc), exc, exc.__traceback__))

For some reason simply using log.exception(exc) didn't work for me and I had to manually build the exc_info param as in this answer.

log.exception(exc, exc_info=(type(exc), exc, exc.__traceback__))
蓝眼泪 2024-08-13 15:44:22

下面是一个取自 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')

Heres a simple example taken from the 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')
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文