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

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

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


当前回答

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

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

其他回答

一点点装饰处理(非常松散的灵感来自可能单子和提升)。您可以安全地删除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的参数中指定这个返回值,使解决方案具有泛型)。

日志记录有一个好处。SiggyF的答案没有显示的异常是,你可以传递一个任意的消息,日志仍然会显示所有异常细节的完整跟踪:

import logging
try:
    1/0
except ZeroDivisionError:
    logging.exception("Deliberate divide by zero traceback")

默认情况下(在最近的版本中)只是将错误打印到sys. log行为。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

我把所有的函数都包在我的自定义设计的日志记录器周围:

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)

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

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

您可以记录堆栈跟踪而不出现异常。

https://docs.python.org/3/library/logging.html#logging.Logger.debug

The second optional keyword argument is stack_info, which defaults to False. If true, stack information is added to the logging message, including the actual logging call. Note that this is not the same stack information as that displayed through specifying exc_info: The former is stack frames from the bottom of the stack up to the logging call in the current thread, whereas the latter is information about stack frames which have been unwound, following an exception, while searching for exception handlers.

例子:

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