如何记录带有调试信息的Python错误?


468

我正在使用以下命令将Python异常消息打印到日志文件中logging.error

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

除了异常字符串以外,是否可以打印有关异常及其生成代码的更多详细信息?行号或堆栈跟踪之类的东西会很棒。

Answers:


733

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

例如:

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

输出:

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

@Paulo Check指出:“请注意,在Python 3中,您必须logging.exceptionexcept零件内部调用该方法。如果在任意位置调用此方法,则可能会遇到奇怪的异常。文档对此有所提示。”


131
exception方法只是调用error(message, exc_info=1)exc_info从异常上下文传递到任何日志记录方法后,您将立即获得回溯。
Helmut Grohne 2013年

16
您还可以设置sys.excepthook(请参阅此处),以避免必须将所有代码包装在try / except中。
七月

23
您可能except Exception:因为没有使用e而写信;)
Marco Ferrari

21
您可能很想e在尝试交互式调试代码时进行检查。:)这就是为什么我总是包含它。
Vicki Laidler

4
如果我错了,请纠正我,在这种情况下,没有对异常的真正处理,因此raiseexcept范围的末尾添加是有意义的。否则,运行会像一切正常一样继续进行。
Dror

184

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

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

在默认情况下(在最新版本中),仅将错误打印到的日志记录行为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

是否可以在不提供消息的情况下记录异常?
Stevoisiak

@StevenVascellaro- ''如果您真的不想键入消息,我建议您通过...不过,如果没有至少一个参数就无法调用该函数,因此您必须给它一些东西。
ArtOfWarfare

147

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

try:
    # do something here
except Exception as e:
    logging.critical(e, exc_info=True)  # log exception info at CRITICAL log level

@CivFan:我实际上没有看其他编辑内容或帖子介绍;该介绍也由第三方编辑添加。我在删除的注释中没有看到原本打算的任何内容,但是我也可以撤消我的编辑并删除这些注释,这里的表决已经很长时间了,除了已编辑的版本之外,其他任何表决都没有。 。
马丁·彼得

logging.fatal在日志库的方法?我只看到critical
伊恩

1
@Ian 是to 的别名critical,就像warnto一样warning
0xc0de

35

报价单

如果您的应用程序以其他方式记录日志而不使用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)
  • Python 2中使用它:

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

    try:
        x = get_number()
    except Exception as ex:
        log_traceback(ex)

为什么将“ _,_,ex_traceback = sys.exc_info()”放在函数log_traceback之外,然后将其作为参数传递?为什么不直接在函数内部使用它呢?
罗勒·穆萨

@BasilMusa,简而言之,要回答与Python 3兼容的问题,因为该语言ex_traceback来自 ex.__traceback__Python 3,但ex_traceback来自sys.exc_info()Python2。–
zangw

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

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

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

利润!


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)

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

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

如果您希望有专门的记录器来处理例外情况,这确实是一个有用的结束语。
logicOnAbstractions

7

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

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

5

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

fallable.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

演示:

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'

您还可以修改此解决方案来回报比的东西更有意义一点Noneexcept部分(甚至使溶液一般,通过指定该返回值fallible的论点)。


0

在您的日志记录模块(如果是自定义模块)中,只需启用stack_info即可。

api_logger.exceptionLog("*Input your Custom error message*",stack_info=True)

-1

如果您可以处理额外的依赖关系,则可以使用twisted.log,您不必显式记录错误,而且它会将整个回溯和时间返回到文件或流。


8
也许twisted是一个很好的建议,但是这个答案并没有真正的贡献。它没有说明如何使用twisted.log,也没有说明它比logging标准库中的模块有什么优势,也没有说明“您不必显式记录错误”的含义。
Mark Amery

-8

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

from traceback import format_exc

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

问候


4
??为什么是“相关部分”?所有.split('\n')[-2]所做的就是扔掉从结果的行数和回溯format_exc()-通常要有用的信息!更重要的是,它甚至没有做一个好工作 ; 如果您的异常消息包含换行符,则此方法将仅显示异常消息的最后一行-这意味着您将丢失异常类,并且大多数异常消息将丢失回溯。-1。
Mark Amery
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.