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

如何为每个Logger实例修改名称以追踪微服务模块日志

问题与解决方案

问题描述

我创建了一个全局日志类,打算用于所有微服务函数中。当前日志输出示例:
[2023-01-01 13:46:26] - INFO - [utils.logger.<module>:5] - testaaaaa
由于Logger定义在utils.logger模块中,日志里的%(name)s固定显示该路径。我希望将其改为Logger实例化所在模块的点式结构(如routes.users.functiona、routes.users.functionb),以此明确日志来源。同时不清楚logging如何通过__name__捕获模块路径,也可以修改日志类使其初始化时支持传入name参数。另外需要优化该日志类的健壮性,原代码如下:

import typing
import logging


class GlobalLogger:

    MINIMUM_GLOBAL_LEVEL = logging.DEBUG
    GLOBAL_HANDLER = logging.StreamHandler()
    LOG_FORMAT = "[%(asctime)s] - %(levelname)s - [%(name)s.%(funcName)s:%(lineno)d] - %(message)s"
    LOG_DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S"

    def __init__(self, level: typing.Union[int, str] = MINIMUM_GLOBAL_LEVEL):
        self.level = level
        self.logger = self._get_logger()
        self.log_format = self._log_formatter()
        self.GLOBAL_HANDLER.setFormatter(self.log_format)

    def _get_logger(self):
        logger = logging.getLogger(__name__)
        logger.setLevel(self.level)
        logger.addHandler(self.GLOBAL_HANDLER)
        return logger

    def _log_formatter(self):
        return logging.Formatter(fmt=self.LOG_FORMAT, datefmt=self.LOG_DATETIME_FORMAT)

核心解决方案:让日志名称跟随调用模块

1. 理解__name__的作用

Python中每个模块的__name__变量会自动被设置为模块的点式路径:

  • 若在routes/users.py中执行代码,__name__的值为routes.users
  • 若直接运行某个模块,__name__为__main__
  • logging.getLogger(name)会创建/复用对应名称的Logger实例,日志格式中的%(name)s就会填充这个名称,从而标识日志来源模块。

2. 修改日志类支持动态名称

有两种实现方式,按需选择:

方式一:实例化时手动传入__name__

修改类的__init__和_get_logger方法,支持传入name参数:

import typing
import logging


class GlobalLogger:
    MINIMUM_GLOBAL_LEVEL = logging.DEBUG
    GLOBAL_HANDLER = logging.StreamHandler()
    LOG_FORMAT = "[%(asctime)s] - %(levelname)s - [%(name)s.%(funcName)s:%(lineno)d] - %(message)s"
    LOG_DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S"

    # 新增name参数,默认值设为__name__(可选,若不传则用当前类所在模块名)
    def __init__(self, level: typing.Union[int, str] = MINIMUM_GLOBAL_LEVEL, name: str = __name__):
        self.level = level
        self.name = name
        self.logger = self._get_logger()
        self.log_format = self._log_formatter()
        self.GLOBAL_HANDLER.setFormatter(self.log_format)

    def _get_logger(self):
        # 使用传入的name创建Logger
        logger = logging.getLogger(self.name)
        logger.setLevel(self.level)
        # 避免重复添加Handler(关键优化点)
        if not logger.handlers:
            logger.addHandler(self.GLOBAL_HANDLER)
        return logger

    def _log_formatter(self):
        return logging.Formatter(fmt=self.LOG_FORMAT, datefmt=self.LOG_DATETIME_FORMAT)

使用方式:在其他模块中实例化时传入当前模块的__name__:

# 在routes/users/functiona.py中
logger = GlobalLogger(name=__name__)
logger.logger.info("test log")

此时日志输出的%(name)s会是routes.users.functiona。

方式二:自动获取调用模块的名称

通过inspect模块自动获取实例化代码所在的模块名,无需手动传参:

import typing
import logging
import inspect


class GlobalLogger:
    MINIMUM_GLOBAL_LEVEL = logging.DEBUG
    GLOBAL_HANDLER = logging.StreamHandler()
    LOG_FORMAT = "[%(asctime)s] - %(levelname)s - [%(name)s.%(funcName)s:%(lineno)d] - %(message)s"
    LOG_DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S"

    def __init__(self, level: typing.Union[int, str] = MINIMUM_GLOBAL_LEVEL):
        self.level = level
        # 自动获取调用模块的名称
        self.name = self._get_caller_module_name()
        self.logger = self._get_logger()
        self.log_format = self._log_formatter()
        self.GLOBAL_HANDLER.setFormatter(self.log_format)

    def _get_caller_module_name(self):
        # 获取调用栈,跳过当前类的初始化栈帧,拿到调用者的模块名
        stack = inspect.stack()
        try:
            # stack[0]是当前方法,stack[1]是__init__,stack[2]是实例化代码所在的栈帧
            caller_frame = stack[2]
            return caller_frame.f_globals['__name__']
        finally:
            del stack  # 释放栈引用,避免内存泄漏

    def _get_logger(self):
        logger = logging.getLogger(self.name)
        logger.setLevel(self.level)
        if not logger.handlers:
            logger.addHandler(self.GLOBAL_HANDLER)
        return logger

    def _log_formatter(self):
        return logging.Formatter(fmt=self.LOG_FORMAT, datefmt=self.LOG_DATETIME_FORMAT)

使用方式:直接实例化即可,无需传参:

# 在routes/users/functionb.py中
logger = GlobalLogger()
logger.logger.info("auto caller log")

健壮性优化建议

  1. 避免重复添加Handler
    原代码中每次实例化都会调用logger.addHandler(self.GLOBAL_HANDLER),而logging.getLogger是单例模式,多次实例化会导致同一个Logger被添加多个相同Handler,最终日志重复输出。解决方案是添加判断:if not logger.handlers:再添加Handler。

  2. 全局Handler的线程安全与隔离
    原代码中的GLOBAL_HANDLER是类级别的全局变量,若多个线程同时修改其Formatter(或其他属性)会出现并发问题。建议:

    • 若所有实例使用相同格式,可在类加载时一次性设置Formatter,无需在__init__中重复设置;
    • 若需要支持不同格式,可改为每个实例创建独立的Handler,或使用线程锁保护全局Handler的修改。
  3. 日志级别输入合法性校验
    若传入的level是字符串(如"INFO"),可统一转换为logging对应的级别常量,避免无效输入:

    def __init__(self, level: typing.Union[int, str] = MINIMUM_GLOBAL_LEVEL, name: str = __name__):
        # 处理字符串级别
        if isinstance(level, str):
            self.level = logging.getLevelName(level.upper())
        else:
            self.level = level
        # 确保级别在合法范围内
        valid_levels = [logging.DEBUG, logging.INFO, logging.WARNING, logging.ERROR, logging.CRITICAL]
        if not isinstance(self.level, int) or self.level not in valid_levels:
            self.level = self.MINIMUM_GLOBAL_LEVEL
        # 其他初始化代码...
    
  4. 添加日志方法封装
    原代码需要通过logger.logger.info()调用日志方法,不够便捷。可以在GlobalLogger类中封装常用日志方法:

    def debug(self, msg: str, *args, **kwargs):
        self.logger.debug(msg, *args, **kwargs)
    
    def info(self, msg: str, *args, **kwargs):
        self.logger.info(msg, *args, **kwargs)
    
    def warning(self, msg: str, *args, **kwargs):
        self.logger.warning(msg, *args, **kwargs)
    
    def error(self, msg: str, *args, **kwargs):
        self.logger.error(msg, *args, **kwargs)
    
    def critical(self, msg: str, *args, **kwargs):
        self.logger.critical(msg, *args, **kwargs)
    

    使用时直接调用:logger.info("test")。

  5. 资源清理
    对于文件Handler等需要关闭的资源,可添加__del__方法或显式关闭方法,但StreamHandler通常无需手动关闭。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 07:45:39