返回

如何使用 Dependency Injection 在 Python 中从配置文件中动态配置日志记录?

python

从配置文件中动态配置 Python 日志记录:使用 Dependency Injection

作为一名经验丰富的程序员,在处理日志记录问题时,我遇到了一个有趣且富有挑战性的问题。我需要使用 Python 中经典的日志包和 dependency_injector 包,从配置文件中动态选择日志处理程序。这篇文章将分享我遇到这个挑战时面临的问题、解决方法以及一些相关的经验教训。

问题陈述

目标是通过配置文件设置处理程序的类型(或多个处理程序),并使用 DI 根据此配置创建日志记录资源。在此示例中,日志记录器将同时写入控制台和文件。如果只指定了一个文件,则只写入该文件;如果未指定任何内容,则不写入日志。

解决方案

为了解决这个问题,我们采用了一种新颖的方法。我们使用一个名为 demo() 的工厂函数来查找配置文件中设置了哪些参数。此函数负责创建处理程序列表,根据配置选择控制台和文件处理程序。

def _demo(log_filename, logger_service):
    handlers = []
    if 'console' in logger_service:
        handlers.append(providers.Factory(logging.StreamHandler))

    if 'file' in logger_service:
        handlers.append(providers.Factory(logging.FileHandler, log_filename))
    return handlers

然后,我们将此工厂函数用作创建处理程序列表的工厂,并将其传递给基本配置函数。

handlers = providers.Factory(lambda log_filename, logger_service: _demo(log_filename, logger_service),
                                 log_filename=log_filename, logger_service=config.logger_service)
logging_config = providers.Factory(logging.basicConfig, format=config.logger_format, datefmt=config.date_format,
                                       level=logger_level, handlers=handlers)

代码示例

以下是完整代码示例,展示如何从配置文件动态配置日志记录:

core.yml

date_format: '%d-%m-%Y %H:%M:%S'
logger_service: ['console', 'file']
logger_level: 'DEBUG'
logger_format: '%(asctime)s [%(levelname)s] %(message)s'
logger_filename: 'log.txt'

container_logger.py

import logging
from dependency_injector import containers, providers


class ContainerLogger(containers.DeclarativeContainer):
    config = providers.Configuration(yaml_files=['./config/core.yml'])
    log_filename = 'log.txt'
    logger_level = providers.Factory(config.getLevelName, config.logger_level)

    handlers = providers.Factory(lambda log_filename, logger_service: _demo(log_filename, logger_service),
                                 log_filename=log_filename, logger_service=config.logger_service)

    logging_config = providers.Factory(logging.basicConfig, format=config.logger_format, datefmt=config.date_format,
                                       level=logger_level, handlers=handlers)
    logging = providers.Resource(logging_config)


def _demo(log_filename, logger_service):
    handlers = []
    if 'console' in logger_service:
        handlers.append(providers.Factory(logging.StreamHandler))

    if 'file' in logger_service:
        handlers.append(providers.Factory(logging.FileHandler, log_filename))
    return handlers

main.py

import logging
from src.container_logger import ContainerLogger


if __name__ == '__main__':
    container = ContainerLogger()
    container.init_resources()
    logging.info("Resources are initialized")

结论

通过使用工厂函数 demo() 和 dependency_injector,我们能够根据配置文件动态配置日志记录。此解决方案允许我们在不使用复杂条件语句的情况下轻松创建和管理日志记录资源。这种方法提高了代码的可读性、可维护性和灵活性。

常见问题解答

1. 为什么我们不使用 if 语句来根据配置选择处理程序?
if 语句无法正确加载所需处理程序。Dependency Injection (DI) 框架在装载对象时有特定的方式,因此 if 语句不能正确地处理这个问题。

2. demo() 函数是如何工作的?
demo() 函数检查配置文件中设置了哪些参数,然后创建处理程序列表。它根据配置选择控制台和文件处理程序。

3. 为什么我们使用 Factory 而不是直接使用处理程序?
Factory 允许我们推迟处理程序的实例化,直到它们真正需要为止。这提高了代码的可读性和可维护性。

4. logging_config 变量的作用是什么?
logging_config 变量是一个工厂,它创建 logging.basicConfig() 调用的配置参数。

5. Resource 的作用是什么?
Resource 是一个 DI 提供程序,它管理 logging_config 的生命周期并提供 logging 实例。