如何从json文件中指定要使用的日志记录配置

2024-04-20 04:46:08 发布

您现在位置:Python中文网/ 问答频道 /正文

我正在尝试为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格式的配置部分,然后将其提取并在中使用。配置如下所示:

"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的文件中,而不需要任何控制台输出

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


Tags: nameselfinfologfalsemessagedbget
2条回答

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

The logging library takes a modular approach and offers several categories of components: loggers, handlers, filters, and formatters.

  • Loggers expose the interface that application code directly uses.
  • Handlers send the log records (created by loggers) to the appropriate destination.
  • Filters provide a finer grained facility for determining which log records to output.
  • Formatters specify the layout of log records in the final output.

特别是关于这个片段的一些观点

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

  2. 如果您确实想在运行时更改日志记录配置,请注意 可能需要设置disable_existing_loggers=False。有一个警告 关于这一点,请参阅文件:

    The fileConfig() function takes a default parameter, disable_existing_loggers, which defaults to True for reasons of backward compatibility. This may or may not be what you want, since it will cause any non-root loggers existing before the fileConfig() call to be disabled unless they (or an ancestor) are explicitly named in the configuration. Please refer to the reference documentation for more information, and specify False for this parameter if you wish.

    The dictionary passed to dictConfig() can also specify a Boolean value with key disable_existing_loggers, which if not specified explicitly in the dictionary also defaults to being interpreted as True. This leads to the logger-disabling behaviour described above, which may not be what you want - in which case, provide the key explicitly with a value of False.

    Incremental Configuration还有以下警告:

    [...] there is not a compelling case for arbitrarily altering the object graph of loggers, handlers, filters, formatters at run-time, once a configuration is set up; the verbosity of loggers and handlers can be controlled just by setting levels (and, in the case of loggers, propagation flags). Changing the object graph arbitrarily in a safe way is problematic in a multi-threaded environment; while not impossible, the benefits are not worth the complexity it adds to the implementation.

    如果仍要继续^{}可以是 灵感

  3. 你不需要get_callerInfoLogRecord有这些{a4}:{},{},{},{}。揭露他们 在日志中,以字符串或子类的格式引用它们 logging.Formatter因为您需要一些格式化逻辑

  4. 要将日志记录写入新介质,例如Postgres(stdlib^{}或第三方软件包尚不支持)?编写logging.Handler的子类。还请注意,记录到生产数据库可能很棘手。两个示例:logbeam(AWS CloudWatch日志的日志处理程序)和Chronologer(我编写的用MySQL和SQLite编写的Python客户机/服务器日志系统)

感谢您的回复@saaj

  1. 我不需要在运行时更改配置。但是,这将在高度分布式的环境中运行(使用Celery)。 我不熟悉引导的详细工作原理,但我认为为了执行一些小任务而创建的芹菜工人并不是应用程序主执行线程的一部分(如果我在这里遗漏了什么,请纠正我)。我想说的是,在生成芹菜工人时,afaik没有引导过程,因此工人看不到配置。我没有看到任何情况下,我们希望在运行时更改配置,这些更改将以计划的方式进行,然后在整个应用程序重新启动后应用。 我真正想要的是能够为特定上下文创建日志记录程序(例如:两个密切相关的模块应该以相同的方式配置日志记录/记录到相同的特定文件)。 我不知道如何配置日志记录,以便具有不同名称值的模块都记录到同一个文件中

  2. 我之所以得到这样的配置,是因为它与我们配置应用程序其他部分的方式一致。我的团队希望所有配置都存储在一个高度保护的文件中。 它有点旧,在我被派去工作之前就是这样设计的。我想我不能改变它

  3. 我很确定我曾尝试在配置(logging.formatter)中公开这些,但没有找到正确的值。它只是显示有关日志库的信息,而不是与日志库相关的信息 调用日志包装器的模块

  4. 当我们想要登录到postgresql数据库时,我将看看如何创建logging.Handler的子类

我感谢你的反馈和建议

相关问题 更多 >