首页 文章

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

提问于
浏览
334

我使用 logging.error 将Python异常消息打印到日志文件:

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

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

9 回答

  • 84

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

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

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

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

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

    关于 logging.exception 没有显示 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
    
  • -6

    如果使用普通日志 - 所有日志记录都应符合此规则: 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)
    

    利润!

  • 28

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

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

    问候

  • 539

    Quoting

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

    现在, 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)
    
  • 158

    一点点装饰处理(非常松散的启发可能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'
    

    你也可以修改这个解决方案,从 except 部分返回比 None 更有意义的东西(或者甚至通过在 fallible 的参数中指定这个返回值来使解决方案通用) .

  • 10

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

    如果你可以处理额外的依赖,那么使用twisted.log,你不必显式地记录错误,它也会返回整个回溯和文件或流的时间 .

  • -1

    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中,您必须在 except 部分内部调用 logging.exception 方法 . 如果您在任意位置调用此方法,您可能会遇到一个奇怪的异常 . 文档会提醒您 . ”

相关问题