Python日志格式能否获取包含类的全限定函数名?
如何让Python logging记录函数的全限定名称(qualname)
这个需求完全可以实现,核心思路是通过自定义日志处理逻辑,在日志记录生成时获取函数的__qualname__并注入到日志记录对象中,之后就可以在格式字符串中使用该属性。
方案一:自定义Logging Filter
通过Filter在日志记录前添加qualname属性,不修改原有日志核心逻辑,灵活性高。
import logging import inspect class QualnameFilter(logging.Filter): def filter(self, record): stack = inspect.stack() try: # 跳过logging模块自身的栈帧,定位到业务代码的调用位置 for stack_frame in stack: if not stack_frame.filename.endswith('logging/__init__.py'): qualname = stack_frame.function # 处理类方法/类函数的情况,拼接类名 if 'self' in stack_frame.frame.f_locals: qualname = f"{stack_frame.frame.f_locals['self'].__class__.__name__}.{qualname}" elif 'cls' in stack_frame.frame.f_locals: qualname = f"{stack_frame.frame.f_locals['cls'].__name__}.{qualname}" record.qualname = qualname break except Exception: # 异常时回退到默认的funcName record.qualname = record.funcName finally: del stack return True # 配置并使用Filter logger = logging.getLogger(__name__) logger.setLevel(logging.DEBUG) logger.addFilter(QualnameFilter()) # 日志格式中使用%(qualname)s formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(qualname)s: %(message)s') handler = logging.StreamHandler() handler.setFormatter(formatter) logger.addHandler(handler)
方案二:自定义LogRecord类
替换logging默认的LogRecord工厂,从根源上给日志记录对象添加qualname属性。
import logging import inspect class QualnameLogRecord(logging.LogRecord): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) stack = inspect.stack() try: for stack_frame in stack: if not stack_frame.filename.endswith('logging/__init__.py'): qualname = stack_frame.function if 'self' in stack_frame.frame.f_locals: qualname = f"{stack_frame.frame.f_locals['self'].__class__.__name__}.{qualname}" elif 'cls' in stack_frame.frame.f_locals: qualname = f"{stack_frame.frame.f_locals['cls'].__name__}.{qualname}" self.qualname = qualname break except Exception: self.qualname = self.funcName finally: del stack # 替换默认的LogRecord工厂 logging.setLogRecordFactory(QualnameLogRecord) # 配置日志 logger = logging.getLogger(__name__) logger.setLevel(logging.DEBUG) formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(qualname)s: %(message)s') handler = logging.StreamHandler() handler.setFormatter(formatter) logger.addHandler(handler)
测试验证
编写测试代码验证效果:
class DemoClass: def demo_method(self): logger.debug('这是类实例方法的日志') @classmethod def demo_classmethod(cls): logger.debug('这是类方法的日志') def demo_function(): logger.debug('这是普通函数的日志') DemoClass().demo_method() DemoClass.demo_classmethod() demo_function()
输出示例:
2024-05-20 15:30:00,123 - DEBUG - DemoClass.demo_method: 这是类实例方法的日志 2024-05-20 15:30:00,124 - DEBUG - DemoClass.demo_classmethod: 这是类方法的日志 2024-05-20 15:30:00,124 - DEBUG - demo_function: 这是普通函数的日志
注意事项
- 栈帧层级:代码中跳过logging模块栈帧的逻辑,可能需要根据Python版本或logging的具体实现微调栈帧遍历的判断条件。
- 性能影响:
inspect.stack()会遍历调用栈,频繁记录日志时可能带来一定性能开销,高并发场景建议评估后使用。
内容的提问来源于stack exchange,提问作者Troels Blum
相关产品推荐
相关产品推荐

