如何使用调试信息记录Python错误?

pro*_*ach 417 python logging exception-handling exception

我正在将Python异常消息打印到日志文件中logging.error:

import logging
try:
    1/0
except ZeroDivisionError as e:
    logging.error(e)  # ERROR:root:division by zero
Run Code Online (Sandbox Code Playgroud)

是否可以打印有关异常的更详细信息以及生成它的代码而不仅仅是异常字符串?线号或堆栈跟踪之类的东西会很棒.

Sig*_*gyF 655

logger.exception 将在错误消息旁边输出堆栈跟踪.

例如:

import logging
try:
    1/0
except ZeroDivisionError as e:
    logging.exception("message")
Run Code Online (Sandbox Code Playgroud)

输出:

ERROR:root:message
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
ZeroDivisionError: integer division or modulo by zero
Run Code Online (Sandbox Code Playgroud)

@Paulo检查说明,"请注意,在Python 3中,您必须logging.exceptionexcept部件内部调用该方法.如果您在任意位置调用此方法,您可能会遇到一个奇怪的异常.文档会提醒您."

  • `exception`方法只调用`error(message,exc_info = 1)`.一旦将`exc_info`传递给异常上下文中的任何日志记录方法,您将获得回溯. (120认同)
  • 你可以写`除了Exception:`因为你没有以任何方式使用`e`;) (19认同)
  • 在尝试交互式调试代码时,您可能希望检查`e`.:)这就是我总是包含它的原因. (18认同)
  • 您还可以设置`sys.excepthook`(参见[此处](http://stackoverflow.com/a/8054179/326849)),以避免将所有代码包装在try/except中. (14认同)
  • 如果我错了,请纠正我,在这种情况下,没有真正处理异常,因此在`except`范围的末尾添加`raise`是有意义的.否则,运行将继续,好像一切都很好. (4认同)
  • 如果你想在不同的日志级别记录长回溯,那么做`logging.error("message"); logging.debug(e, exc_info=True)`,或您喜欢的任何级别。 (3认同)
  • @MarcoFerrari,此外,一个裸露的“例外”将捕获 SystemExit 和 KeyboardInterrupt,这可能不是有意的。` except Exception as e` 不会捕获这些。https://docs.python.org/3/library/exceptions.html#exception-hierarchy (2认同)
  • 恕我直言,“error(message, exc_info=1)”相当于“exception”方法,更具可读性。 (2认同)

nco*_*lan 174

一个妙处logging.exception在于SiggyF的回答并没有显示的是,你可以在任意的消息传递和记录仍然会显示完整的回溯与所有异常的详细信息:

import logging
try:
    1/0
except ZeroDivisionError:
    logging.exception("Deliberate divide by zero traceback")
Run Code Online (Sandbox Code Playgroud)

使用默认(在最新版本中)记录打印错误的行为sys.stderr,它看起来像这样:

>>> import logging
>>> try:
...     1/0
... except ZeroDivisionError:
...     logging.exception("Deliberate divide by zero traceback")
... 
ERROR:root:Deliberate divide by zero traceback
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
ZeroDivisionError: integer division or modulo by zero
Run Code Online (Sandbox Code Playgroud)


fly*_*cee 119

使用exc_info选项可能更好,允许您选择错误级别(如果使用exception,它将始终显示error):

try:
    # do something here
except Exception as e:
    logging.fatal(e, exc_info=True)  # log exception info at FATAL log level
Run Code Online (Sandbox Code Playgroud)

  • @Ian 它是 `critical` 的别名,就像 `warn` 是 `warning` 一样。 (2认同)

zan*_*ngw 32

引用

如果您的应用程序以其他方式记录 - 不使用logging模块,该怎么办?

现在,traceback可以在这里使用.

import traceback

def log_traceback(ex, ex_traceback=None):
    if ex_traceback is None:
        ex_traceback = ex.__traceback__
    tb_lines = [ line.rstrip('\n') for line in
                 traceback.format_exception(ex.__class__, ex, ex_traceback)]
    exception_logger.log(tb_lines)
Run Code Online (Sandbox Code Playgroud)


小智 12

如果使用普通日志 - 所有日志记录都应符合以下规则:one record = one line.遵循此规则,您可以使用grep其他工具来处理日志文件.

但追溯信息是多线的.所以我的答案是zangw在这个帖子中提出的解决方案的扩展版本.问题是回溯线可能有\n内部,所以我们需要做一些额外的工作来摆脱这个行结束:

import logging


logger = logging.getLogger('your_logger_here')

def log_app_error(e: BaseException, level=logging.ERROR) -> None:
    e_traceback = traceback.format_exception(e.__class__, e, e.__traceback__)
    traceback_lines = []
    for line in [line.rstrip('\n') for line in e_traceback]:
        traceback_lines.extend(line.splitlines())
    logger.log(level, traceback_lines.__str__())
Run Code Online (Sandbox Code Playgroud)

之后(当您将分析日志时),您可以从日志文件中复制/粘贴所需的回溯行并执行以下操作:

ex_traceback = ['line 1', 'line 2', ...]
for line in ex_traceback:
    print(line)
Run Code Online (Sandbox Code Playgroud)

利润!


Bac*_*zek 11

您可以毫无例外地记录堆栈跟踪。

https://docs.python.org/3/library/logging.html#logging.Logger.debug

第二个可选关键字参数是 stack_info,默认为 False。如果为 true,堆栈信息将添加到日志消息中,包括实际的日志调用。请注意,这与指定 exc_info 显示的堆栈信息不同:前者是从堆栈底部到当前线程中的日志记录调用的堆栈帧,而后者是有关已展开的堆栈帧的信息,跟随异常,同时搜索异常处理程序。

例子:

>>> import logging
>>> logging.basicConfig(level=logging.DEBUG)
>>> logging.getLogger().info('This prints the stack', stack_info=True)
INFO:root:This prints the stack
Stack (most recent call last):
  File "<stdin>", line 1, in <module>
>>>
Run Code Online (Sandbox Code Playgroud)


Kro*_*hka 11

如果“调试信息”意味着引发异常时出现的值,那么logging.exception(...)将无济于事。因此,您需要一个工具来自动记录所有变量值以及回溯行。

开箱即用,你会得到类似的日志

2020-03-30 18:24:31 main ERROR   File "./temp.py", line 13, in get_ratio
2020-03-30 18:24:31 main ERROR     return height / width
2020-03-30 18:24:31 main ERROR       height = 300
2020-03-30 18:24:31 main ERROR       width = 0
2020-03-30 18:24:31 main ERROR builtins.ZeroDivisionError: division by zero
Run Code Online (Sandbox Code Playgroud)

看看一些 pypi 工具,我命名为:

其中一些会给你漂亮的崩溃消息: 在此输入图像描述

但你可能会在pypi上找到更多内容


Wil*_*ill 9

这个答案来自上述优秀的答案.

在大多数应用程序中,您不会直接调用logging.exception(e).您很可能已经为您的应用程序或模块定义了一个特定的自定义记录器,如下所示:

# Set the name of the app or module
my_logger = logging.getLogger('NEM Sequencer')
# Set the log level
my_logger.setLevel(logging.INFO)

# Let's say we want to be fancy and log to a graylog2 log server
graylog_handler = graypy.GELFHandler('some_server_ip', 12201)
graylog_handler.setLevel(logging.INFO)
my_logger.addHandler(graylog_handler)
Run Code Online (Sandbox Code Playgroud)

在这种情况下,只需使用记录器来调用异常(e),如下所示:

try:
    1/0
except ZeroDivisionError, e:
    my_logger.exception(e)
Run Code Online (Sandbox Code Playgroud)


Eli*_*igo 5

一点点装饰器处理(非常松散地受到 Maybe monad 和提升的启发)。您可以安全地删除 Python 3.6 类型注释并使用较旧的消息格式样式。

容易出错的.py

from functools import wraps
from typing import Callable, TypeVar, Optional
import logging


A = TypeVar('A')


def fallible(*exceptions, logger=None) \
        -> Callable[[Callable[..., A]], Callable[..., Optional[A]]]:
    """
    :param exceptions: a list of exceptions to catch
    :param logger: pass a custom logger; None means the default logger, 
                   False disables logging altogether.
    """
    def fwrap(f: Callable[..., A]) -> Callable[..., Optional[A]]:

        @wraps(f)
        def wrapped(*args, **kwargs):
            try:
                return f(*args, **kwargs)
            except exceptions:
                message = f'called {f} with *args={args} and **kwargs={kwargs}'
                if logger:
                    logger.exception(message)
                if logger is None:
                    logging.exception(message)
                return None

        return wrapped

    return fwrap
Run Code Online (Sandbox Code Playgroud)

演示:

In [1] from fallible import fallible

In [2]: @fallible(ArithmeticError)
    ...: def div(a, b):
    ...:     return a / b
    ...: 
    ...: 

In [3]: div(1, 2)
Out[3]: 0.5

In [4]: res = div(1, 0)
ERROR:root:called <function div at 0x10d3c6ae8> with *args=(1, 0) and **kwargs={}
Traceback (most recent call last):
  File "/Users/user/fallible.py", line 17, in wrapped
    return f(*args, **kwargs)
  File "<ipython-input-17-e056bd886b5c>", line 3, in div
    return a / b

In [5]: repr(res)
'None'
Run Code Online (Sandbox Code Playgroud)

您还可以修改此解决方案以返回比Noneexcept零件更有意义的内容(或者甚至通过在fallible的参数中指定此返回值来使解决方案通用)。


Zby*_*000 5

从 Python 3.5 开始,可以在日志记录函数中显式指定异常:

try:
    1/0
except Exception as ex:
    logging.error("Error occurred", exc_info = ex)
Run Code Online (Sandbox Code Playgroud)

logging.exception函数仍然有效,但必须仅在块内调用except,并且不允许指定日志级别。