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

Python封装Logger后filename始终显示logger.py,如何修改或调整设计?

解决Python日志filename显示错误的问题

问题根源

你遇到的情况是因为Python的logging模块默认记录的filename是日志记录器类(MyLogger)所在的文件路径(也就是logger.py),而不是调用日志方法(比如debug)的文件路径。这是模块的默认行为,和你当前的依赖注入、面向对象设计本身无关,不需要调整核心结构。

可行解决方案

方法1:用inspect获取调用者上下文,自定义日志字段

通过inspect模块获取调用栈的上一层帧信息,拿到实际调用日志方法的文件名和行号,然后通过extra参数传递给日志记录器,最后在日志格式中使用自定义字段替代默认的filename。

示例代码:

# logger.py
import logging
import inspect
import os

class MyLogger:
    def __init__(self):
        self.logger = logging.getLogger("my_app")
        # 配置日志格式,使用自定义的caller_filename和caller_lineno
        formatter = logging.Formatter(
            "%(asctime)s - %(caller_filename)s:%(caller_lineno)s - %(levelname)s - %(message)s"
        )
        handler = logging.FileHandler("app.log")
        handler.setFormatter(formatter)
        self.logger.addHandler(handler)
        self.logger.setLevel(logging.DEBUG)

    def _get_caller_info(self):
        # 跳过当前方法的栈帧,获取调用者的帧信息
        caller_frame = inspect.currentframe().f_back.f_back
        return {
            "caller_filename": os.path.basename(caller_frame.f_code.co_filename),  # 只保留文件名
            "caller_lineno": caller_frame.f_lineno
        }

    def debug(self, msg, *args, **kwargs):
        caller_info = self._get_caller_info()
        self.logger.debug(msg, *args, extra=caller_info, **kwargs)

    # 对info/warn/error等方法做同样处理
    def info(self, msg, *args, **kwargs):
        caller_info = self._get_caller_info()
        self.logger.info(msg, *args, extra=caller_info, **kwargs)

在Client类中正常注入使用:

# client.py
class Client:
    def __init__(self, logger):
        self.logger = logger

    def run(self):
        self.logger.debug("Client执行了run方法")

方法2:使用LoggerAdapter绑定调用者上下文

如果希望每个注入的类自动绑定自身的文件信息,可以用logging.LoggerAdapter为每个类生成带上下文的日志适配器,减少重复代码。

示例代码:

# logger.py
import logging
import inspect
import os

class MyLogger:
    def __init__(self):
        self.base_logger = logging.getLogger("my_app")
        formatter = logging.Formatter(
            "%(asctime)s - %(caller_filename)s:%(caller_lineno)s - %(levelname)s - %(message)s"
        )
        handler = logging.FileHandler("app.log")
        handler.setFormatter(formatter)
        self.base_logger.addHandler(handler)
        self.base_logger.setLevel(logging.DEBUG)

    def get_contextual_logger(self, caller_file):
        caller_filename = os.path.basename(caller_file)
        
        # 自定义Adapter,动态获取调用行号
        class ContextualAdapter(logging.LoggerAdapter):
            def process(self, msg, kwargs):
                # 跳过Adapter和调用方法的栈帧,拿到实际业务代码的帧
                caller_frame = inspect.currentframe().f_back.f_back
                kwargs["extra"] = {
                    "caller_filename": caller_filename,
                    "caller_lineno": caller_frame.f_lineno
                }
                return msg, kwargs

        return ContextualAdapter(self.base_logger, {})

在Client类中初始化上下文日志器:

# client.py
class Client:
    def __init__(self, logger):
        self.logger = logger.get_contextual_logger(__file__)

    def run(self):
        self.logger.debug("Client执行了run方法")

注意事项

  • 不要试图直接修改logging模块默认的filename字段,它是模块内部基于日志记录器定义位置生成的,无法直接覆盖,只能通过自定义字段替代。
  • 两种方法都不需要改变你现有的依赖注入设计,只需要扩展MyLogger的功能即可。
  • 如果需要显示完整文件路径,去掉os.path.basename()处理即可。

内容的提问来源于stack exchange,提问作者arpymastro

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 15:42:48