我正在使用logging.error
将Python异常消息打印到日志文件中:
import logging
try:
1/0
except ZeroDivisionError as e:
logging.error(e) # ERROR:root:division by zero
是否有可能打印关于异常和生成它的代码的更详细信息,而不仅仅是异常字符串?像行号或堆栈跟踪之类的东西会很棒。
我正在使用logging.error
将Python异常消息打印到日志文件中:
import logging
try:
1/0
except ZeroDivisionError as e:
logging.error(e) # ERROR:root:division by zero
是否有可能打印关于异常和生成它的代码的更详细信息,而不仅仅是异常字符串?像行号或堆栈跟踪之类的东西会很棒。
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中,您必须仅在except
部分内调用logging.exception
方法。如果您在任意位置调用此方法,则可能会收到异常结果。文档已经对此进行了警告。”
使用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.fatal
是 logging 库中的一个方法吗?我只看到了 critical
。 - Iancritical
的别名,就像 warn
是 warning
的别名一样。 - 0xc0delogging.exception
的一个好处是,与SiggyF的回答不同的是,您可以传递任意消息,而日志记录仍将显示所有异常详细信息的完整回溯:
import logging
try:
1/0
except ZeroDivisionError:
logging.exception("Deliberate divide by zero traceback")
默认情况下(在最近的版本中),日志记录行为只会将错误打印到sys.stderr
,效果如下:
使用最近版本的默认日志行为,默认只将错误信息打印到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
''
如果你真的不想输入消息... 但是函数至少需要一个参数才能被调用,所以你必须给它一些东西。 - ArtOfWarfare如果您的应用程序以另一种方式记录日志,而不是使用
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)
ex_traceback
是来自Python 3下的ex.__traceback__
,但在Python 2下,ex_traceback
是来自sys.exc_info()
。 - zangw您可以在不使用异常的情况下记录堆栈跟踪。
https://docs.python.org/zh-cn/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>
>>>
一条记录 = 一行
。遵循这个规则,您可以使用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)
盈利!
这个回答是基于以上出色的回答而建立的。
在大多数应用程序中,您不会直接调用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.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 上发现更多。
稍加修饰(受Maybe单子和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
演示:
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
的参数中指定此返回值来使解决方案通用)。api_logger.exceptionLog("*Input your Custom error message*",stack_info=True)
exception
方法只是简单地调用error(message, exc_info=1)
。只要你从异常上下文中向任何一个日志记录方法传递了exc_info
参数,你就会得到一个回溯信息。 - Helmut Grohnesys.excepthook
(参见这里)来避免必须在所有代码中都加入 try/except 块。 - julexcept Exception:
,因为您并没有以任何方式使用e
;) - Marco Ferrarie
。 :) 这就是为什么我总是把它包含在内的原因。 - Vicki Laidlerexcept
语句块的末尾添加raise
是有意义的。否则,程序将继续运行,就好像一切都很正常。 - Dror