【问题标题】:How do I specify which logging configuration to use from a json file如何从 json 文件中指定要使用的日志记录配置
【发布时间】:2021-09-25 08:03:19
【问题描述】:

我正在尝试为 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”时它不起作用并默认为根无论如何。

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

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

【问题讨论】:

    标签: python json logging configuration


    【解决方案1】:

    感谢@saaj 的精彩回复

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

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

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

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

    感谢您的反馈和建议。

    【讨论】:

      【解决方案2】:

      我认为logging 包装器通常是一个糟糕的设计,而且我在商业和 OSS 代码库中都看到过很多这样的包装器。我会称它为 Python 中的反模式,因为 logging 包实际上是非常可扩展的,而且很少能超越 its (extension) mechanisms

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

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

      还有一些关于 sn-p 的具体观点。

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

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

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

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

        Incremental Configuration 也有这个警告:

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

        如果您仍想继续,logging.config.listen 可以是 灵感。

      3. 您不需要get_callerInfoLogRecord 有这些out of the boxfilenamemodulelinenofuncName。揭露他们 在您的日志中以格式字符串或子类引用它们 logging.Formatter 你想要一些格式化逻辑。

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

      【讨论】:

        猜你喜欢
        • 2011-02-17
        • 2013-05-21
        • 2015-12-02
        • 1970-01-01
        • 2010-09-30
        • 2012-11-18
        • 2010-12-27
        • 1970-01-01
        • 2018-10-12
        相关资源
        最近更新 更多