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

2025-03-26 09:10:00
admin
原创
11
摘要:问题描述:我正在使用以下命令将 Python 异常消息打印到日志文件logging.error:import logging try: 1/0 except ZeroDivisionError as e: logging.error(e) # ERROR:root:division by z...

问题描述:

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

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

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


解决方案 1:

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

例如:

import logging
try:
    1/0
except ZeroDivisionError:
    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。如果您在任意位置调用此方法,可能会出现奇怪的异常。文档会对此发出警告。”

解决方案 2:

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

解决方案 3:

SiggyF 的答案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

解决方案 4:

引用

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

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

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中使用它:

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)

解决方案 5:

您可以记录没有异常的堆栈跟踪。

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

解决方案 6:

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

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

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

解决方案 7:

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

但是回溯信息是多行的。所以我的答案是zangw在本线程中提出的解决方案的扩展版本。问题是回溯行可能包含`
`内部内容,所以我们需要做额外的工作来摆脱这个行尾:

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

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

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

利润!

解决方案 8:

这个答案是基于上述优秀答案的。

在大多数应用程序中,您不会直接调用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)

解决方案 9:

如果“调试信息”指的是异常发生时的值,那么它就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 工具,我会命名:

  • 结核病疫苗

  • 带变量的回溯

  • 更好的例外

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

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

解决方案 10:

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

易错.py

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


A = TypeVar('A')


def fallible(*exceptions, logger=None) \n        -> 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

解决方案 11:

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

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

解决方案 12:

如果你看一下这个代码示例(适用于 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 行。

希望有帮助!

解决方案 13:

我将所有功能包装在我定制设计的记录器中:

import json
import timeit
import traceback
import sys
import unidecode

def main_writer(f,argument):
  try:
    f.write(str(argument))
  except UnicodeEncodeError:
    f.write(unidecode.unidecode(argument))


def logger(*argv,logfile="log.txt",singleLine = False):
  """
  Writes Logs to LogFile
  """
  with open(logfile, 'a+') as f:
    for arg in argv:
      if arg == "{}":
        continue
      if type(arg) == dict and len(arg)!=0:
        json_object = json.dumps(arg, indent=4, default=str)
        f.write(str(json_object))
        f.flush()
        """
        for key,val in arg.items():
          f.write(str(key) + " : "+ str(val))
          f.flush()
        """
      elif type(arg) == list and len(arg)!=0:
        for each in arg:
          main_writer(f,each)
          f.write("
")
          f.flush()
      else:
        main_writer(f,arg)
        f.flush()
      if singleLine==False:
        f.write("
")
    if singleLine==True:
      f.write("
")

def tryFunc(func, func_name=None, *args, **kwargs):
  """
  Time for Successfull Runs
  Exception Traceback for Unsuccessful Runs
  """
  stack = traceback.extract_stack()
  filename, codeline, funcName, text = stack[-2]
  func_name = func.__name__ if func_name is None else func_name # sys._getframe().f_code.co_name # func.__name__
  start = timeit.default_timer()
  x = None
  try:
    x = func(*args, **kwargs)
    stop = timeit.default_timer()
    # logger("Time to Run {} : {}".format(func_name, stop - start))
  except Exception as e:
    logger("Exception Occurred for {} :".format(func_name))
    logger("Basic Error Info :",e)
    logger("Full Error TraceBack :")
    # logger(e.message, e.args)
    logger(traceback.format_exc())
  return x

def bad_func():
  return 'a'+ 7

if __name__ == '__main__':
    logger(234)
    logger([1,2,3])
    logger(['a','b','c'])
    logger({'a':7,'b':8,'c':9})
    tryFunc(bad_func)

解决方案 14:

我的方法是创建一个上下文管理器来记录和引发异常:

import logging
from contextlib import AbstractContextManager


class LogError(AbstractContextManager):

    def __init__(self, logger=None):
        self.logger = logger.name if isinstance(logger, logging.Logger) else logger

    def __exit__(self, exc_type, exc_value, traceback):
        if exc_value is not None:
            logging.getLogger(self.logger).exception(exc_value)


with LogError():
    1/0

您可以将记录器名称或记录器实例传递给LogError()。默认情况下,它将使用基本记录器(通过传递None给logging.getLogger)。也可以简单地添加一个开关来引发错误或仅记录错误。

解决方案 15:

实现此目的的最好且最小的库是loguru,它显示内置的回溯并提供调试信息:

import sys
from loguru import logger
logger.add(sys.stdout, level="TRACE")

try:
    a = 1/0
except Exception as err:
    logger.exception(str(err), backtrace=True, diagnose=True)

输出:

2023-08-09 10:46:53.669 | ERROR    | __main__:<module>:8 - division by zero
Traceback (most recent call last):

> File "new.py", line 6, in <module>
    a = 1/0

ZeroDivisionError: division by zero

解决方案 16:

如果您可以应对额外的依赖,那么请使用 twisted.log,您不必明确记录错误,而且它会将整个回溯和时间返回到文件或流。

解决方案 17:

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

from traceback import format_exc

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

问候

相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   2079  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1459  
  建筑行业正处于数字化转型的关键时期,建筑产品生命周期管理(PLM)系统的实施对于提升项目效率、质量和协同性至关重要。特别是在 2025 年,基于建筑信息模型(BIM)的项目进度优化工具成为众多建筑企业关注的焦点。这些工具不仅能够整合项目全生命周期的数据,还能通过精准的分析和模拟,为项目进度管理提供强大支持。BIM 与建...
plm是什么软件   0  
  PLM系统开发的重要性与现状PLM(产品生命周期管理)系统在现代企业的产品研发、生产与管理过程中扮演着至关重要的角色。它贯穿产品从概念设计到退役的整个生命周期,整合了产品数据、流程以及人员等多方面的资源,极大地提高了企业的协同效率和创新能力。通过PLM系统,企业能够实现产品信息的集中管理与共享,不同部门之间可以实时获取...
国产plm软件   0  
  PLM(产品生命周期管理)系统在企业产品研发与管理过程中扮演着至关重要的角色。随着市场竞争的加剧和技术的飞速发展,企业对PLM系统的迭代周期优化需求日益迫切。2025年敏捷认证对项目管理提出了新的要求,其中燃尽图作为一种强大的可视化工具,在PLM系统迭代周期优化中有着广泛且重要的应用。深入探讨这些应用,对于提升企业的项...
plm系统主要干什么的   0  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用