关于异常:如何使用调试信息记录Python错误?

How do I log a Python error with debug information?

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

1
2
3
4
5
import logging
try:
    1/0
except ZeroDivisionError as e:
    logging.error(e)  # ERROR:root:division by zero

是否可以打印关于异常和生成异常的代码的更详细的信息,而不仅仅是异常字符串?像行号或堆栈跟踪之类的东西会很好。


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

例如:

1
2
3
4
5
import logging
try:
    1/0
except ZeroDivisionError as e:
    logging.exception("message")

输出:

1
2
3
4
ERROR:root:message
Traceback (most recent call last):
  File"<stdin>", line 2, in <module>
ZeroDivisionError: integer division or modulo by zero

@paulo check notes,"请注意,在python 3中,必须在except部分内调用logging.exception方法。如果在任意位置调用此方法,可能会得到一个奇怪的异常。医生们对此发出了警告。"


关于logging.exception,siggyf的答案没有显示的一个好处是,您可以传递任意消息,日志记录仍将显示所有异常详细信息的完整回溯:

1
2
3
4
5
import logging
try:
    1/0
except ZeroDivisionError:
    logging.exception("Deliberate divide by zero traceback")

由于默认(在最新版本中)只将错误打印到sys.stderr的日志记录行为,看起来如下:

1
2
3
4
5
6
7
8
9
10
>>> 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


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

1
2
3
4
try:
    # do something here
except Exception as e:
    logging.fatal(e, exc_info=True)  # log exception info at FATAL log level


引用

What if your application does logging some other way – not using the logging module?

现在,可以在这里使用traceback

1
2
3
4
5
6
7
8
9
import traceback

def log_traceback(ex, ex_traceback=None):
    if ex_traceback is None:
        ex_traceback = ex.__traceback__
    tb_lines = [ line.rstrip('
'
) for line in
                 traceback.format_exception(ex.__class__, ex, ex_traceback)]
    exception_logger.log(tb_lines)
  • 在python 2中使用它:

    1
    2
    3
    4
    5
    try:
        # your function call is here
    except Exception as ex:
        _, _, ex_traceback = sys.exc_info()
        log_traceback(ex, ex_traceback)
  • 在python 3中使用它:

    1
    2
    3
    4
    try:
        x = get_number()
    except Exception as ex:
        log_traceback(ex)


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

但回溯信息是多行的。因此,我的答案是在本文中由Zangw提出的解决方案的扩展版本。问题是,回溯线内部可能有
,因此我们需要做额外的工作来消除这一行的结尾:

1
2
3
4
5
6
7
8
9
10
11
12
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('
'
) for line in e_traceback]:
        traceback_lines.extend(line.splitlines())
    logger.log(level, traceback_lines.__str__())

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

1
2
3
ex_traceback = ['line 1', 'line 2', ...]
for line in ex_traceback:
    print(line)

利润!


这个答案是由上述优秀的答案构成的。

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

1
2
3
4
5
6
7
8
9
# 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)

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

1
2
3
4
try:
    1/0
except ZeroDivisionError, e:
    my_logger.exception(e)


一点装饰处理(很松散的灵感可能是单子和起重)。您可以安全地删除python3.6类型注释并使用旧的消息格式样式。

错误的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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

演示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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'

您还可以修改这个解决方案,从except部分返回比None更有意义的内容(或者甚至通过在fallible的参数中指定这个返回值使该解决方案成为通用的)。


如果您能够处理额外的依赖关系,那么就使用twisted.log,您不必显式地记录错误,它还会将整个回溯和时间返回到文件或流。


一种干净的方法是使用format_exc(),然后解析输出以获得相关部分:

1
2
3
4
5
6
7
from traceback import format_exc

try:
    1/0
except Exception:
    print 'the relevant part is: '+format_exc().split('
'
)[-2]

当做