Python 如何创建记录异常日志的装饰器

发布于 2024-12-15 19:39:20 字数 3662 浏览 2 评论 0

有一天,我突然想要创建一个用于捕获并记录异常的装饰器,我在 Github 上发现一个颇为复杂的例子,该例子启发了我,并让我写下了以下代码:

# exception_decor.py
import logging

def create_logger():
  """
  Creates a logging object and returns it
  """
  logger = logging.getLogger("example_logger")
  logger.setLevel(logging.INFO)
  # create the logging file handler
  fh = logging.FileHandler("/path/to/test.log")
  fmt = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
  formatter = logging.Formatter(fmt)
  fh.setFormatter(formatter)
  # add handler to logger object
  logger.addHandler(fh)
  return logger

def exception(function):
  """
  A decorator that wraps the passed in function and logs 
  exceptions should one occur
  """
  def wrapper(*args, **kwargs):
    logger = create_logger()
    try:
      return function(*args, **kwargs)
    except:
      # log the exception
      err = "There was an exception in  "
      err += function.__name__
      logger.exception(err)
    # re-raise the exception
    raise
  return wrapper

在这段代码中,定义了两个函数. 第一个函数创建并返回了一个 logging 对象. 第二个函数为我们的装饰器函数,其中我们将传入的函数包裹进一个 try/except 中,并使用 logger 记录下任何产生的异常. 同时你还会发现,我还记录下了产生异常的函数名。

现在让我们测试一下这个装饰器. 创建一个新 Python 脚本并添加以下代码. 请确保这个新 Python 脚本与上面创建装饰器的脚本在同一个目录下。

from exception_decor import exception
 
@exception
def zero_divide():
  1 / 0
 
if __name__ == '__main__':
  zero_divide()

当你在命令行中运行该代码后,你会发现多出了一个日志文件,其内容为:

  2016-06-09 08:26:50,874 - example_logger - ERROR - There was an exception in  zero_divide
Traceback (most recent call last):
  File "/home/mike/exception_decor.py", line 29, in wrapper
  return function(*args, **kwargs)
  File "/home/mike/test_exceptions.py", line 5, in zero_divide
  1 / 0
ZeroDivisionError: integer division or modulo by zero

我觉得这份代码很方便,我希望你们大家也会觉得有用。

UPDATE: 一个热心的读者提出,将代码泛化成从外界传递一个 logger 对象到装饰器会更好些. 那么然我们来看看如何实现它!

1 传递 logger 到我们的装饰器中

首先,将原始的代码中的 logging 代码拆到自己的模块中。 假设放到文件 exception_logger.py 中. 下面是源代码:

# exception_logger.py
import logging
def create_logger():
  """
  Creates a logging object and returns it
  """
  logger = logging.getLogger("example_logger")
  logger.setLevel(logging.INFO)
  # create the logging file handler
  fh = logging.FileHandler(r"/path/to/test.log")
  fmt = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
  formatter = logging.Formatter(fmt)
  fh.setFormatter(formatter)
  # add handler to logger object
  logger.addHandler(fh)
  return logger
logger = create_logger()

然后我们修改装饰器代码,使之接受一个 logger 为参数并保存为 exception_decor.py

# exception_decor.py

import functools


def exception(logger):
  """
  A decorator that wraps the passed in function and logs 
  exceptions should one occur

  @param logger: The logging object
  """

  def decorator(func):

    def wrapper(*args, **kwargs):
      try:
        return func(*args, **kwargs)
      except:
        # log the exception
        err = "There was an exception in  "
        err += func.__name__
        logger.exception(err)

      # re-raise the exception
      raise
    return wrapper
  return decorator

你会发现我们定义了多层嵌套的函数。 请看仔细并理解其中的意思。 最后,我们需要修改一下测试的代码:

from exception_decor import exception
from exception_logger import logger

@exception(logger)
def zero_divide():
  1 / 0

if __name__ == '__main__':
  zero_divide()

代码中,我们导入了装饰器和 logger 对象。 然后我们将装饰器作用于要记录的函数,并且我们给该装饰器传递了一个 logger 对象。 你运行该测试代码后,会发现产生了与第一个测试相同的文件。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

烟凡古楼

暂无简介

0 文章
0 评论
23 人气
更多

推荐作者

已经忘了多久

文章 0 评论 0

15867725375

文章 0 评论 0

LonelySnow

文章 0 评论 0

走过海棠暮

文章 0 评论 0

轻许诺言

文章 0 评论 0

信馬由缰

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文