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

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

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

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

554905 次浏览

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

@保罗支票指出,“请注意,在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]

问候

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

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

这个答案建立在上面的优秀答案之上。

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

引用

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

    try:
    # your function call is here
    except Exception as ex:
    _, _, ex_traceback = sys.exc_info()
    log_traceback(ex, ex_traceback)
    
  • Use it in Python 3:

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

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

利润!

一点点装饰器处理(非常松散地受到也许单子和提升的启发)。您可以安全地删除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的参数中指定此返回值来使解决方案通用)。

您可以无异常地记录堆栈跟踪。

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

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

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

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

希望有帮助!

如果“调试信息”意味着引发异常时存在的值,那么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上找到更多

我围绕我的自定义设计的记录器包装所有功能:

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("\n")
f.flush()
else:
main_writer(f,arg)
f.flush()
if singleLine==False:
f.write("\n")
if singleLine==True:
f.write("\n")


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)

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

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.get记录器)。 也可以简单地添加一个开关来引发错误或只是记录它。