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

时间:2011-03-04 09:21:20

标签: python exception logging exception-handling

我正在使用logging.error

将Python异常消息打印到日志文件中
import logging
try:
    1/0
except ZeroDivisionError as e:
    logging.error(e)  # ERROR:root:division by zero

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

13 个答案:

答案 0 :(得分:597)

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 Cheque注意,“请注意,在Python 3中,您必须在logging.exception部分内调用except方法。如果您在任意位置调用此方法,您可能会得到一个奇怪的例外。文档对此提出警告。“

答案 1 :(得分:166)

logging.exception 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 的行为,它看起来像这样:

{{1}}

答案 2 :(得分:102)

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

答案 3 :(得分:29)

Quoting

  

如果您的应用程序以其他方式进行日志记录,而不是使用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)
    

答案 4 :(得分: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)

利润!

答案 5 :(得分: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)

答案 6 :(得分:3)

一点点装饰器处理(很可能是从Maybe monad和提升中获得的启发)。您可以安全地删除Python 3.6类型注释,并使用较旧的消息格式样式。

fallible.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'

您还可以修改此解决方案,以从None部分返回比except更有意义的内容(甚至通过在fallible中指定此返回值来使解决方案具有通用性)的参数)。

答案 7 :(得分:2)

如果“调试信息”是指引发异常时出现的值,那么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

看看一些pypi工具,我叫:

其中一些会给您漂亮的崩溃消息: enter image description here

但是您可能会在pypi

上找到更多内容

答案 8 :(得分:1)

如果您查看this code example(适用于Python 2和3),则会在下面看到可以提取的函数定义

  • 方法
  • 行号
  • 代码上下文
  • 文件路径

整个堆栈跟踪,无论是否存在异常:

def sentry_friendly_trace(get_last_exception=True):
    try:
        current_call = list(map(frame_trans, traceback.extract_stack()))
        alert_frame = current_call[-4]
        before_call = current_call[:-4]

        err_type, err, tb = sys.exc_info() if get_last_exception else (None, None, None)
        after_call = [alert_frame] if err_type is None else extract_all_sentry_frames_from_exception(tb)

        return before_call + after_call, err, alert_frame
    except:
        return None, None, None

当然,此功能取决于上面链接的整个要旨,尤其是extract_all_sentry_frames_from_exception()frame_trans(),但异常信息提取总共不到60行。

希望有帮助!

答案 9 :(得分:0)

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

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

答案 10 :(得分:0)

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

  List<string> result = ...

  ...

  result.AddRange(source.Select(item => map[item]));

答案 11 :(得分:-1)

如果您可以应对额外的依赖关系,那么使用twisted.log,您不必显式记录错误,也可以返回整个回溯和文件或流的时间。

答案 12 :(得分:-6)

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

from traceback import format_exc

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

此致