如何在Python中实现支持标准行为的自定义日志级别?
解决自定义日志级别显示正确调用文件的问题
这个问题我之前也碰到过!核心原因是你用的那个addLoggingLevel方法没有处理日志调用的栈帧层级,导致日志系统错误地把定义mylog函数的main.py当成了调用源。标准日志级别(比如debug、info)之所以能正确显示调用文件,是因为它们内部做了栈帧跳过处理,咱们只需要把这个逻辑加到自定义级别里就行。
正确的自定义日志级别实现方法
针对Python 3.8及以上版本(现在主流环境都是这个版本了),我们可以利用logging.log新增的stacklevel参数来完美解决这个问题。下面是改进后的addLoggingLevel函数,完全兼容标准日志级别的所有行为:
import logging def addLoggingLevel(levelName, levelNum, methodName=None): """ 新增自定义日志级别,兼容标准日志行为,正确显示调用文件和行号 :param levelName: 级别名称(大写,比如'MYLOG') :param levelNum: 级别数值(比如介于INFO和WARNING之间可以设为25) :param methodName: 调用方法名(小写,比如'mylog',默认是levelName小写) """ if not methodName: methodName = levelName.lower() # 先把级别名称注册到logging模块 logging.addLevelName(levelNum, levelName) # 给Logger类添加对应级别实例方法 def logForLevel(self, message, *args, **kwargs): if self.isEnabledFor(levelNum): # stacklevel=2 跳过当前函数,捕捉实际调用mylog的位置 self._log(levelNum, message, args, **kwargs, stacklevel=2) # 给logging模块添加全局便捷方法(比如logging.mylog) def logToRoot(message, *args, **kwargs): logging.log(levelNum, message, *args, **kwargs, stacklevel=2) # 绑定方法到对应对象 setattr(logging.Logger, methodName, logForLevel) setattr(logging, methodName, logToRoot)
使用示例
1. 在main.py中配置和初始化
# main.py import logging # 可以把上面的addLoggingLevel函数直接写在这里,或者从其他文件导入 # 添加自定义级别MYLOG,数值设为25(介于INFO(20)和WARNING(30)之间) addLoggingLevel('MYLOG', 25, 'mylog') # 配置日志格式,和你原来的一致 logging.basicConfig( format=u'[%(asctime)s] # %(levelname)-4s\t%(filename)s[LINE:%(lineno)d]\t%(message)s', level=logging.MYLOG ) # 本地调用测试 logging.mylog("这是main.py里输出的日志") # 导入其他模块并调用 import other_module other_module.test_custom_log()
2. 在other_module.py中调用自定义日志
# other_module.py import logging def test_custom_log(): logging.mylog("这是other_module.py里输出的日志")
运行main.py后,你会看到日志输出里的filename分别对应main.py和other_module.py,完全符合预期!
为什么这个方法有效?
- 我们给
Logger类添加了和标准级别逻辑一致的实例方法,和logger.info等方法的实现对齐; stacklevel=2参数告诉日志系统跳过当前的包装函数,直接捕捉到调用mylog的那一行代码,这样filename和lineno就会显示实际调用的位置;- 同时保留了标准日志级别的所有特性:比如可以通过
logger.setLevel控制输出级别、支持格式化参数、兼容日志处理器配置等。
内容的提问来源于stack exchange,提问作者Kroll
相关产品推荐
相关产品推荐

