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

如何使用调试信息记录Python错误?,python,exception,logging,exception-handling,Python,Exception,Logging,Exception Handling,我正在使用日志记录将Python异常消息打印到日志文件中。错误: import logging try: 1/0 except ZeroDivisionError as e: logging.error(e) # ERROR:root:division by zero 是否可以打印有关异常和生成异常的代码的更详细信息,而不仅仅是异常字符串?行号或堆栈跟踪之类的东西会很好。将在错误消息旁边输出堆栈跟踪 例如: import logging try: 1/0 except

我正在使用
日志记录将Python异常消息打印到日志文件中。错误

import logging
try:
    1/0
except ZeroDivisionError as e:
    logging.error(e)  # ERROR:root:division by zero
是否可以打印有关异常和生成异常的代码的更详细信息,而不仅仅是异常字符串?行号或堆栈跟踪之类的东西会很好。

将在错误消息旁边输出堆栈跟踪

例如:

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
错误:根:消息
回溯(最近一次呼叫最后一次):
文件“”,第2行,在
ZeroDivisionError:整数除法或模零除法

注意,“请注意,在Python 3中,必须在
except
部分内调用
logging.exception
方法。如果在任意位置调用此方法,可能会出现奇怪的异常。文档会对此发出警告。”

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

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

from traceback import format_exc

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

关于关于
日志记录的一个优点是,您可以传入任意消息,而日志记录仍将显示包含所有异常详细信息的完整回溯:

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
# 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)
try:
    1/0
except ZeroDivisionError, e:
    my_logger.exception(e)
导入日志记录 >>>尝试: ... 1/0 ... 除零误差外: ... logging.exception(“故意除以零的回溯”) ... 错误:根:故意除以零回溯 回溯(最近一次呼叫最后一次): 文件“”,第2行,在 ZeroDivisionError:整数除法或模零除法
使用
exc\u info
选项可能更好,以允许您选择错误级别(如果使用
异常
,它将始终处于
错误
级别):


这个答案建立在上述优秀答案的基础上

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

>>> 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
# 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)
try:
    1/0
except ZeroDivisionError, e:
    my_logger.exception(e)
在这种情况下,只需使用记录器调用异常(e),如下所示:

>>> 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
# 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)
try:
    1/0
except ZeroDivisionError, e:
    my_logger.exception(e)

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

现在,这里可以使用
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中使用它:

  • 在Python 3中使用它:


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

但回溯信息是多行的。所以我的答案是上面在这个线程中提出的解决方案的扩展版本。问题是,回溯行中可能有
\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)

利润

一点装饰处理(灵感来源于Maybe monad和lifting)。您可以安全地删除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
演示:

[1]中的
from fallible导入fallible
在[2]中:@fallible(算术错误)
…:def分区(a、b):
…:返回a/b
...: 
...: 
在[3]中:div(1,2)
Out[3]:0.5
[4]中:res=div(1,0)
错误:root:使用*args=(1,0)和**kwargs={}调用
回溯(最近一次呼叫最后一次):
文件“/Users/user/fallible.py”,第17行,包装
返回f(*args,**kwargs)
文件“”,第3行,在div中
返回a/b
在[5]:repr(res)
“没有”

您还可以修改此解决方案,以从
部分返回比
更有意义的内容(或者甚至通过在
易出错的
参数中指定此返回值,使解决方案成为通用的)

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

第二个可选关键字参数是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>
>>>
导入日志记录 >>>logging.basicConfig(级别=logging.DEBUG) >>>logging.getLogger().info('这将打印堆栈',stack\u info=True) 信息:根:这将打印堆栈 堆栈(最后一次最近调用): 文件“”,第1行,在 >>>
在日志模块(如果是自定义模块)中,只需启用堆栈信息

api_logger.exceptionLog("*Input your Custom error message*",stack_info=True)
如果您查看(适用于Python2和Python3),您将看到下面的函数定义,它可以提取

  • 方法
  • 行号
  • 代码上下文
  • 文件路径
对于整个堆栈跟踪,无论是否存在异常:

def sentry\u友好跟踪(get\u last\u exception=True):
尝试:
当前调用=列表(映射(frame\u trans,traceback.extract\u stack())
警报\u帧=当前\u调用[-4]
呼叫前=当前呼叫[:-4]
err_type,err,tb=sys.exc_info()如果get_last_异常为其他(无,无,无)
after_call=[alert_frame]如果err_type为None-other,则从_异常(tb)中提取_-all_-sentry_frames_
返回前\u调用+后\u调用、错误、警报\u帧
除:
返回None,None,None
当然,此函数取决于上面链接的整个要点,尤其是
从\u exception()提取\u all\u sentry\u frames\u和
frames\u trans()<