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
相关产品推荐
相关产品推荐

