Python日志格式化变量获取及Django中request_id提取问题
Python日志变量识别与Django请求ID故障展示方案
先说说Python日志里的变量怎么来的:
一、日志变量的识别逻辑
1. 内置变量(比如asctime)
Python的logging模块在生成日志时会自动创建一个LogRecord对象,这个对象自带一堆内置属性——asctime就是其中之一,它是日志记录的时间,会按照你配置的datefmt格式输出。像levelname(日志级别)、message(日志内容)这些也都是LogRecord自带的,不用额外配置,Formatter会自动从里面取值替换格式字符串里的%(xxx)s。
2. 自定义变量(比如request_id)
自定义变量需要你手动把值塞到LogRecord里,Python没有Java的ThreadContext,但有替代方案,核心是用线程局部存储(因为Django每个请求对应单独线程),结合日志过滤器或者适配器来实现:
做法1:用logging.LoggerAdapter手动传参
每次打日志的时候,把request_id作为上下文参数传给适配器:
import logging logger = logging.getLogger(__name__) # 把request_id封装到适配器里 adapter = logging.LoggerAdapter(logger, {'request_id': request.request_id}) adapter.info('用户登录操作完成')
这样格式里的%(request_id)s就会被替换成你传的具体值。
做法2:用线程局部存储+日志过滤器(模拟ThreadContext)
这个方法更省心,不用每次打日志都传参,自动给所有日志加上请求ID:
- 第一步:创建线程局部存储对象,用来存当前请求的上下文
# utils/context.py import threading # 这个对象会为每个线程单独存一份数据,互不干扰 local_context = threading.local()
- 第二步:写Django中间件,生成并存储request_id
# middleware/request_id.py import uuid from django.utils.deprecation import MiddlewareMixin from utils.context import local_context class RequestIDMiddleware(MiddlewareMixin): def process_request(self, request): # 生成唯一请求ID,也可以从请求头(比如X-Request-ID)里取 request_id = uuid.uuid4().hex request.request_id = request_id # 把ID存到线程局部存储里 local_context.request_id = request_id
- 第三步:写日志过滤器,把request_id注入到LogRecord
# logging_filters.py from utils.context import local_context class RequestIDFilter(logging.Filter): def filter(self, record): # 从线程存储里取ID,没有就设默认值 record.request_id = getattr(local_context, 'request_id', 'unknown') return True
- 第四步:修改日志配置,绑定过滤器
# settings.py里的日志配置 'formatters': { 'simple': { 'format': '%(asctime)s SCORPIO [%(request_id)s] ' + os.environ['ENVIRONMENT'] + ': %(message)s', 'datefmt': '%Y-%m-%dT%H:%M:%S', }, }, 'filters': { 'request_id': { '()': 'myapp.logging_filters.RequestIDFilter', }, }, 'handlers': { 'console': { 'class': 'logging.StreamHandler', 'formatter': 'simple', 'filters': ['request_id'], # 给处理器绑定过滤器 }, },
这样所有日志都会自动带上当前请求的request_id,不用每次手动传。
二、故障时获取request_id并展示在页面
当Django抛500错误时,你可以这么做:
1. 自定义500错误处理器
在urls.py里指定自定义的错误视图:
# urls.py handler500 = 'myapp.views.custom_500'
然后写对应的视图函数:
# views.py from django.shortcuts import render from utils.context import local_context def custom_500(request): # 从线程存储里拿request_id request_id = getattr(local_context, 'request_id', 'unknown') # 把ID传给错误页面模板 return render(request, '500.html', {'request_id': request_id}, status=500)
2. 在错误模板里展示
创建templates/500.html:
<!DOCTYPE html> <html> <head> <title>服务器出错了</title> </head> <body> <h1>服务器内部错误</h1> <p>麻烦记录这个请求ID,方便我们排查问题:<strong>{{ request_id }}</strong></p> </body> </html>
内容的提问来源于stack exchange,提问作者Madhav Kauntia
相关产品推荐
相关产品推荐

