首页 > 解决方案 > 如何从 json 文件中指定要使用的日志记录配置

问题描述

我正在尝试为 python 日志库创建一个包装类。这个想法是,用户可以向构造函数提供记录器的名称,并根据 json 文件的内容配置实例,其中提供的名称是配置文件相应部分的键。

这是我的代码

class LogLib:
    def __init__(self, logger_name=""):
        conf_dict = Config.get("LogLib")
        logging_config.dictConfig(conf_dict)
        if not logger_name:
            self.logger = logging.getLogger()
        else:
            self.logger = logging.getLogger(logger_name)

    def debug(self, message, db=False):
        caller_info = self.get_callerInfo()
        msg = message + caller_info
        self.logger.debug(msg)
        if db:
            self.log_db(message, "DEBUG")

    def info(self, message, db=False):
        caller_info = self.get_callerInfo()
        msg = message + caller_info
        self.logger.info(msg)
        if db:
            self.log_db(message, "INFO")

    def warning(self, message, db=False):
        caller_info = self.get_callerInfo()
        msg = message + caller_info
        self.logger.warning(msg)
        if db:
            self.log_db(message, "WARNING")

    def error(self, message, db=False, stacktrace=False):
        caller_info = self.get_callerInfo()
        msg = message + caller_info
        self.logger.error(msg, exc_info=stacktrace)
        if db:
            self.log_db(message, "ERROR")

    def critical(self, message, db=False, stacktrace=False):
        caller_info = self.get_callerInfo()
        msg = message + caller_info
        self.logger.critical(msg, exc_info=stacktrace)
        if db:
            self.log_db(message, "CRITICAL")

    def log_db(self, message, level):
        raise NotImplemented()
        # psql = PostgresqlConnector()
        # with psql.create_session() as session:
        #    psql.insert(session, Log(message=message, level=level))

    def get_callerInfo(self):
        raw = self.logger.findCaller(stack_info=False)
        caller_fileName = raw[0].rsplit("/", 1)[1].split(".")[0]
        return f"\nSOURCE > {caller_fileName}.{raw[2]}, Line: {raw[1]}\n"

为了做一些测试,我在类之外的 LogLib 文件的底部添加了一个小的 main()。它看起来像这样:

    def main():
        logger = LogLib(logger_name="db_logger")
        logger.debug("Debug test - This should not show up in the file.")
        logger.info("Info test")
        logger.warning("Warning test")
        logger.error("Error test")

    if __name__ == "__main__":
        main()

为了配置这个包装器,我创建了一个 JSON 格式的配置部分,然后在_ _ init _ 中获取并使用它。配置如下所示:

"LogLib": {
    "version": 1,
    "root": {
        "handlers": ["console", "file"],
        "level": "DEBUG"
    },
    "db_logger": {
        "handlers": ["db_file"],
        "level": "INFO"
    },
    "handlers": {
        "console": {
            "formatter": "console_formatter",
            "class": "logging.StreamHandler",
            "level": "WARNING"
          },
        "file": {
          "formatter": "file_formatter",
          "class": "logging.FileHandler",
          "level": "DEBUG",
          "filename": "C:\\Users\\name\\Documents\\GitHub\\proj\\logs\\app_err.log"
        },
        "db_file": {
            "formatter": "file_formatter",
            "class": "logging.FileHandler",
            "level": "INFO",
            "filename": "C:\\Users\\name\\Documents\\GitHub\\proj\\logs\\db.log"
        }
      },
    "formatters": {
        "console_formatter": {
          "format": "%(asctime)s [%(levelname)s] > %(message)s",
          "datefmt": "%d/%m/%Y-%I:%M:%S"
        },
        "file_formatter": {
          "format": "%(asctime)s [%(levelname)s] > %(message)s",
          "datefmt": "%d/%m/%Y-%I:%M:%S"
        }
      }
}

根记录器在它的配置方式上工作正常(写入 app_err.log 并打印到给定级别的控制台)但是当我尝试为其提供名称“db_logger”时它不起作用并且默认为 root 无论如何。

我想要的是当用户通过参数“logger_name”向构造函数提供名称时,它应该检查具有该名称的记录器的配置并将为该名称指定的配置应用于 LogLib 实例。在这种情况下,我希望将所有 INFO 级别或更高级别的日志消息发送到名为 db.log 的文件,而无需任何控制台输出。

我已经检查了文档并搜索了堆栈溢出,但到目前为止我一直无法理解我做错了什么。在此问题上的任何帮助将不胜感激。谢谢你。

标签: pythonjsonloggingconfiguration

解决方案


我认为logging包装器通常是一个糟糕的设计,我在商业和 OSS 代码库中都看到过很多包装器。我将其称为 Python 中的反模式,因为logging包实际上是非常可扩展的,并且很少会超出其(扩展)机制

日志库采用模块化方法并提供几类组件:记录器、处理程序、过滤器和格式化程序。

  • 记录器公开应用程序代码直接使用的接口。
  • 处理程序将日志记录(由记录器创建)发送到适当的目的地。
  • 过滤器提供了更细粒度的工具来确定要输出哪些日志记录。
  • 格式化程序指定最终输出中日志记录的布局。

还有一些关于片段的具体要点。

  1. logging.config.dictConfig除非您想在运行时更改 JSON 配置文件,否则调用每个记录器是没有意义的。在应用程序引导程序上调用一次。

  2. 如果您确实想在运行时更改日志记录配置,请注意您可能需要设置disable_existing_loggers=False. 文档中有关于它的警告:

    fileConfig()函数采用默认参数 ,出于向后兼容性的原因disable_existing_loggers,默认为 。True这可能是也可能不是您想要的,因为它将导致fileConfig()调用之前存在的任何非 root 记录器被禁用,除非它们(或祖先)在配置中明确命名。有关详细信息,请参阅参考文档,并根据需要指定False此参数。

    传递给的字典dictConfig()也可以用 key 指定一个布尔值disable_existing_loggers,如果没有在字典中明确指定,也默认被解释为True。这会导致上述禁用记录器的行为,这可能不是您想要的 - 在这种情况下,请显式为键提供False.

    增量配置也有这个警告:

    [...] 一旦设置了配置,在运行时任意更改记录器、处理程序、过滤器、格式化程序的对象图并没有令人信服的理由;记录器和处理程序的详细程度可以通过设置级别(以及,在记录器的情况下,传播标志)来控制。在多线程环境中,以安全的方式任意更改对象图是有问题的;虽然并非不可能,但其好处并不值得它为实现增加的复杂性。

    如果您仍然想继续logging.config.listen,可以成为一个灵感。

  3. 你不需要get_callerInfoLogRecord这些开箱即用:filename,,,, . 要在您的日志中公开它们,请在格式字符串或子类中引用它们, 以便您拥有一些格式化逻辑。modulelinenofuncNamelogging.Formatter

  4. logging.handlers想要将日志记录写入新介质,比如 Postgres(stdlib或 3rd 方包尚未支持)?写一个子类logging.Handler。另请注意,记录到生产数据库可能很棘手。几个例子:logbeam(AWS CloudWatch Logs 的日志处理程序)和 Chronogger 我写的用 MySQL 和 SQLite 编写的 Python 客户端/服务器日志系统)。


推荐阅读