Python命名日志器无法触发Root Logger自定义Handler的解决方法
日志传播问题:命名日志器日志无法触发Root Logger的自定义Handler
问题场景
- 使用Root Logger进行全局日志记录
- 程序启动后通过回调(如数据库回调)为Root Logger添加自定义
CallbackHandler - 模块内的命名日志器日志无法传递到Root Logger,导致
CallbackHandler.emit()未被触发(直接使用Root Logger时可正常触发)
代码示例
main.py
import logging import logging.config import MyLogger from MyApp import MyApp MyLogger.init() _logger = logging.getLogger() # root def main() : _logger.error( "main - root logger" ) app = MyApp() # setup app and attach CallbackHandler to root logger app.testLog() # call named logger - should call root logger & callback handler if __name__ == "__main__" : main()
MyLogger.py
import logging from logging import LogRecord import logging.config import os from typing import Callable LOG_PATH = "./logs" LOGGING_CONFIG : dict = { "version" : 1 , 'formatters': { 'simple': { 'format': '%(name)s %(message)s' }, }, "handlers" : { "ConsoleHandler" : { "class" : "logging.StreamHandler" , "formatter" : "simple" , } , } , "root" : { "handlers" : [ "ConsoleHandler" , ] , "level" : "DEBUG" , } } def init() : os.makedirs( LOG_PATH , exist_ok = True ) logging.config.dictConfig( LOGGING_CONFIG ) class CallbackHandler( logging.Handler ) : def __init__( self , level = logging.DEBUG , callback : Callable = None ) : super().__init__( level ) self._callback = callback def emit( self , record : LogRecord ) : if self._callback is not None : self._callback( record.name + " | " + record.msg )
MyApp.py
import logging from MyLogger import CallbackHandler _logger = logging.getLogger( __name__ ) class MyApp : def __init__( self ) : rootLogger = logging.getLogger() rootLogger.addHandler( CallbackHandler( callback = self.myCallback ) ) def myCallback( self , msg : str ) : print( "CALLBACK: " + msg ) def testLog( self ) : _logger.error( "MyApp.testLog() - named logger" )
核心问题
根据官方文档,命名日志器默认不会继承父级Handler,但会将日志传播到父级/Root Logger,Root Logger已绑定Handler但未触发。本质原因是命名日志器的有效日志级别未正确继承Root Logger的级别,导致日志被提前过滤,无法传播到Root;或是传播机制虽开启,但日志未通过命名日志器自身的级别检查。
需求
- 初始化Root Logger
- 程序运行后期为Root Logger添加自定义Handler
- 使用命名日志器
- 命名日志器的日志能传播到Root Logger
- 确保日志触发Root Logger的自定义Handler
解决方案
方案1:显式设置命名日志器的级别
在命名日志器创建时,显式设置其级别为不高于要输出的日志级别(如DEBUG),确保日志能通过命名日志器的过滤并传播到Root Logger。
修改MyApp.py中的日志器定义:
_logger = logging.getLogger( __name__ ) _logger.setLevel(logging.DEBUG) # 显式设置级别,与Root Logger保持一致
方案2:通过日志配置统一设置所有命名日志器
在MyLogger.py的LOGGING_CONFIG中添加全局命名日志器配置,让所有命名日志器自动继承Root的级别并开启传播:
LOGGING_CONFIG : dict = { "version" : 1 , 'formatters': { 'simple': { 'format': '%(name)s %(message)s' }, }, "handlers" : { "ConsoleHandler" : { "class" : "logging.StreamHandler" , "formatter" : "simple" , } , } , "root" : { "handlers" : [ "ConsoleHandler" , ] , "level" : "DEBUG" , }, "loggers": { "": { # 匹配所有命名日志器 "level": "DEBUG", "propagate": True } } }
原理说明
- 默认情况下,新创建的命名日志器级别为
NOTSET,理论上会继承首个有级别设置的祖先日志器(Root)的级别,但logging.config.dictConfig的默认配置可能导致该继承逻辑未生效,显式设置级别可避免此问题。 - 命名日志器的
propagate属性默认是True,只要日志通过命名日志器的级别过滤,就会自动传播到Root Logger,触发Root的所有Handler(包括自定义的CallbackHandler)。
内容的提问来源于stack exchange,提问作者nonsensation
相关产品推荐
相关产品推荐

