You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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;或是传播机制虽开启,但日志未通过命名日志器自身的级别检查。

需求

  1. 初始化Root Logger
  2. 程序运行后期为Root Logger添加自定义Handler
  3. 使用命名日志器
  4. 命名日志器的日志能传播到Root Logger
  5. 确保日志触发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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 18:10:59