如何实现按用户和日期生成独立日志,解决多用户并发日志错位问题
问题根源
Python标准库logging模块默认采用单例模式,你调用logging.basicConfig修改的是全局共享的root logger配置,多线程并发场景下,后执行的log_setting调用会直接覆盖之前的配置,导致所有后续日志都写入最新配置的文件路径。单用户顺序访问时没有配置冲突所以功能正常,多用户并行访问时必然出现配置互窜。
解决方案
推荐两种可直接落地的实现方式:
方案1:ContextVar + 自定义Handler(业务代码无侵入,优先推荐)
利用Python 3.7+内置的contextvars实现请求上下文隔离,不需要修改原有业务代码的日志调用逻辑。
修改custom_logger.py代码如下:
import os import datetime import logging from contextvars import ContextVar # 定义上下文变量存储当前请求的用户、页面信息,天然支持线程/协程隔离 current_user = ContextVar('current_user', default=None) current_page = ContextVar('current_page', default=None) class UserFileHandler(logging.Handler): def emit(self, record): user = current_user.get() page = current_page.get() # 无用户信息的请求可以按需求走默认日志或直接丢弃 if not user or not page: return # 动态构造日志路径 log_path = f'./logs/{user}' os.makedirs(log_path, exist_ok=True) full_log_path = os.path.join(log_path, f"{datetime.date.today()}.log") # 构造日志格式 formatter = logging.Formatter( f'%(asctime)s : %(levelname)s : {page} : %(pathname)s : %(lineno)s: %(funcName)s : %(message)s' ) # 写入日志 with open(full_log_path, 'a', encoding='utf-8') as f: f.write(formatter.format(record) + '\n') class LogClass: def __init__(self): root_logger = logging.getLogger() root_logger.setLevel(logging.DEBUG) # 避免重复添加Handler if not any(isinstance(h, UserFileHandler) for h in root_logger.handlers): root_logger.addHandler(UserFileHandler()) def log_setting(self, user_name, page_name): # 仅更新当前请求的上下文变量,不修改全局日志配置 current_user.set(str(user_name)) current_page.set(str(page_name))
原有中间件、业务代码的日志调用逻辑完全不需要修改,并发场景下不会出现日志串写问题。
方案2:独立Logger实例(逻辑简单,适合小项目)
给每个用户创建独立的Logger实例,配置完全隔离,不会互相干扰。
修改custom_logger.py代码如下:
import os import datetime import logging class LogClass: def log_setting(self, user_name, page_name): # 用用户名作为Logger唯一标识,每个用户对应独立实例 user_logger = logging.getLogger(f"user_{user_name}") # 避免重复添加Handler if user_logger.handlers: return user_logger # 构造日志路径 log_path = f'./logs/{user_name}' os.makedirs(log_path, exist_ok=True) full_log_path = os.path.join(log_path, f"{datetime.date.today()}.log") # 配置独立Handler handler = logging.FileHandler(full_log_path, encoding='utf-8') formatter = logging.Formatter( f'%(asctime)s : %(levelname)s : {page_name} : %(pathname)s : %(lineno)s: %(funcName)s : %(message)s' ) handler.setFormatter(formatter) user_logger.addHandler(handler) user_logger.setLevel(logging.DEBUG) # 禁止日志向上传递到root Logger,避免重复打印 user_logger.propagate = False return user_logger
修改中间件代码,将用户专属Logger绑定到请求对象:
from .custom_logger import LogClass log_obj=LogClass() def simple_middleware(get_response): def middleware(request): user_token=request.headers['userid'] user_name=request.headers['username'] page_name=request.headers['pagename'] # 将用户专属Logger绑定到request request.user_logger = log_obj.log_setting(str(user_name),str(page_name)) response = get_response(request) return response return middleware
业务代码中需要将原有全局logging.info(xxx)的调用,修改为request.user_logger.info(xxx)即可。
内容的提问来源于stack exchange,提问作者BetaTester
相关产品推荐
相关产品推荐

