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

Django重写Logger类如何自动选级别并记录真实调用行号

问题根源

Python标准库logging模块在收集调用位置信息(文件名、行号、函数名)时,会从当前执行栈向上回溯,默认跳过logging模块内部的栈帧,将第一层非logging源码的栈帧识别为日志调用位置。你之前的封装把所有日志输出逻辑都放在log函数内部,logger自然会把log函数里执行日志输出的行当成调用源,自然拿不到业务代码的真实行号。

最优实现方案

不需要复杂的自定义Logger继承,logging模块原生提供了stacklevel参数专门解决封装场景的调用位置偏移问题,只需要在调用日志输出方法时指定栈帧向上偏移的层数即可,侵入性极低,完全兼容你现有的日志格式化配置。

修正后的封装函数

import logging

logger = logging.getLogger(__name__)

# 日志级别映射表,减少冗余判断
LEVEL_FUNC_MAP = {
    "info": logger.info,
    "warning": logger.warning,
    "error": logger.error,
    "debug": logger.debug,
}

def log(request=None, status_code=None, level="info", message=""):
    """
    输出标准化格式Django请求日志
    格式:<请求路径> <请求方法> <状态码>: user <用户ID> <日志内容>
    """
    # 非法级别默认降级为info,避免抛错中断业务
    log_func = LEVEL_FUNC_MAP.get(level.lower(), logger.info)

    # 兼容非请求场景(后台任务、脚本、Celery等)不传request的情况
    method = getattr(request, "method", "-")
    path = getattr(request, "path", "-")
    user = getattr(request, "user", None)
    user_id = user.id if user and not user.is_anonymous else "Anonymous"

    log_content = f"{path} {method} {status_code or '-'}: user {user_id} {message}"

    # 核心配置:stacklevel=2 告诉logger向上跳过1层(当前log函数本身),定位到业务代码的真实调用位置
    log_func(log_content, stacklevel=2)

业务代码调用方式完全和你预期的一致,不需要做任何改动:

log(request, 200, "info", "Success message")

原有日志格式化器中配置的行号、文件名、调用函数等字段,会自动识别到调用log函数的业务代码位置,不需要修改formatter配置。

可选:自定义Logger类实现

如果后续需要扩展更多日志能力(比如全局追加trace_id、敏感信息脱敏、统一异常日志采集),可以通过继承logging.Logger实现自定义日志类,核心还是通过stacklevel控制栈偏移:

import logging

class DjangoAppLogger(logging.Logger):
    def structured_log(self, request=None, status_code=None, level=logging.INFO, message=""):
        method = getattr(request, "method", "-")
        path = getattr(request, "path", "-")
        user = getattr(request, "user", None)
        user_id = user.id if user and not user.is_anonymous else "Anonymous"
        log_content = f"{path} {method} {status_code or '-'}: user {user_id} {message}"

        if self.isEnabledFor(level):
            # 调用内部_log方法,指定stacklevel=2跳过当前方法帧
            self._log(level, log_content, None, stacklevel=2)

在Django配置文件settings.py的LOGGING配置中注册自定义Logger类即可,原有handler、formatter配置不需要改动:

LOGGING = {
    "version": 1,
    "disable_existing_loggers": False,
    # 指定全局使用自定义Logger类
    "loggerClass": "your_project.common.logging.DjangoAppLogger",
    # 以下保持原有配置即可
    "formatters": {},
    "handlers": {},
    "loggers": {},
}
注意事项
  • stacklevel的数值和封装层数对应:如果你的log函数外还有一层封装(比如加了敏感信息过滤、日志采样逻辑),每多一层封装就把stacklevel加1
  • 不要给log函数加会改变调用栈的装饰器(比如带参数的装饰器、类装饰器),否则需要对应调整stacklevel数值
  • 建议给level参数加枚举校验,避免业务代码传错字符串导致日志级别不符合预期
  • 异步视图、Celery任务等非HTTP请求场景调用时,可以不传request参数,封装逻辑已经做了空值兜底,不会抛出异常

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.03 09:33:41