Python日志记录中添加自定义属性

日志记录对于任何软件系统都是必不可少的。使用日志,您可以解决各种问题,包括调试应用程序错误、安全缺陷、系统缓慢等。在本文中,我们将讨论如何使用自定义属性有效地使用Python日志记录。

Python 日志记录
在我们深入研究之前,我想用一个例子简单地解释一下基本的 Python 日志模块。

#!/opt/bb/bin/python3.7

import logging
import sys

root = logging.getLogger()
root.setLevel(logging.DEBUG)
std_out_logger = logging.StreamHandler(sys.stdout)
std_out_logger.setLevel(logging.INFO)
std_out_formatter = logging.Formatter("%(levelname)s - %(asctime)s  %(message)s")
std_out_logger.setFormatter(std_out_formatter)
root.addHandler(std_out_logger)

logging.info("I love DDD!")

上面的示例在执行时打印以下内容:

INFO - 2024-03-09 19:49:07,734  I love DDD!


在上面的示例中,我们正在创建root记录器和日志消息的记录格式。在第 6 行,logging.getLogger()如果已创建,则返回记录器;如果没有,它会上升到层次结构的上一级并返回父记录器。我们定义自己的StreamHandler来在控制台打印日志消息。

每当我们记录消息时,都必须记录.LogRecord在第 10 行,我们定义了基本格式,包括级别名称、字符串格式的时间以及实际消息本身。由此创建的处理程序设置在根记录器级别。

我们可以使用LogRecord库中的任何预定义日志属性名称和格式。但是,假设您想要打印一些附加属性contextId,例如自定义日志记录适配器来救援。

记录适配器

class MyLoggingAdapter(logging.LoggerAdapter):

    def <strong>init</strong>(self, logger):
        logging.LoggerAdapter.<strong>init</strong>(self, logger=logger, extra={})

    def process(self, msg, kwargs):
        return msg, kwargs

我们创建自己的版本Logging Adapter并将“额外”参数作为格式化程序的字典传递。

上下文ID过滤器

import contextvars

class ContextIdFilter(logging.Filter):

    context_id = contextvars.ContextVar('context_id', default='')

    def filter(self, record):
        # add a new UUID to the context.
        req_id = str(uuid.uuid4())
        if not self.context_id.get():
            self.context_id.set(req_id)
        record.context_id = self.context_id.get()
        return True


我们创建自己的过滤器来扩展logging过滤器,该过滤器返回True是否应记录指定的日志记录。我们只需将我们的参数添加到日志记录中并return True始终,从而将我们的唯一参数添加id到记录中。在上面的示例中,id为每个新上下文生成一个唯一的上下文。对于现有上下文,我们返回contextId已从contextVars.

自定义记录器

import logging

root = logging.getLogger()
root.setLevel(logging.DEBUG)
std_out_logger = logging.StreamHandler(sys.stdout)
std_out_logger.setLevel(logging.INFO)
std_out_formatter = logging.Formatter("%(levelname)s - %(asctime)s ContextId:%(context_id)s  %(message)s")
std_out_logger.setFormatter(std_out_formatter)
root.addHandler(std_out_logger)
root.addFilter(ContextIdFilter())

adapter = MyLoggingAdapter(root)

adapter.info("I love DDD!")
adapter.info("this is my custom logger")
adapter.info("Exiting the application")


现在让我们将其放在记录器文件中。将contextId过滤器添加到root.请注意,我们在需要记录消息的地方使用我们自己的适配器来代替记录。

运行上面的代码会打印以下消息:

INFO - 2024-04-20 23:54:59,839 ContextId:c10af4e9-6ea4-4cdf-9743-ea24d0febab6 I love DDD!
INFO - 2024-04-20 23:54:59,842 ContextId:c10af4e9-6ea4-4cdf-9743-ea24d0febab6 this is my custom logger
INFO - 2024-04-20 23:54:59,843 ContextId:c10af4e9-6ea4-4cdf-9743-ea24d0febab6 Exiting the application


通过设置root.propagate = False,记录到此记录器的事件将被传递到更高级别的日志记录(也称为父日志记录类)的处理程序。

结论
Python 不提供内置选项来在日志记录中添加自定义参数。相反,我们在 Python 根记录器之上创建一个包装记录器并打印我们的自定义参数。这在调试特定于请求的问题时会很有帮助。