
本文介绍如何在 fastapi 中结合 loguru,通过 `contextualize` 机制为每次 http 请求动态注入 url 等上下文信息,并自动体现在日志格式中,无需修改每条日志语句即可实现请求级精细化追踪。
在 FastAPI 应用中集成 Loguru 进行请求级日志监控时,一个常见且关键的需求是:让每条日志自动携带当前请求的 URL、请求 ID 或其他上下文信息,而无需在每个 logger.debug() 调用中手动拼接。直接修改全局 format 字符串不可行(因为它是静态配置),但 Loguru 提供了更优雅、线程安全的解决方案——contextualize()。
✅ 核心原理:利用 extra + contextualize
Loguru 允许在日志格式中引用 {extra[key]} 占位符,并通过 logger.contextualize() 为当前执行上下文(包括异步任务)临时注入键值对。该上下文会自动透传至该作用域内所有日志记录,且天然支持 asyncio 任务隔离(即不同请求的日志不会互相污染)。
? 实现步骤
-
初始化 Logger 并声明 extra 默认字段
在应用启动时配置 logger,显式声明 request_url 作为可选上下文字段(设默认值避免格式渲染报错):
from loguru import logger
import sys
LOG_LEVEL = "DEBUG"
# 移除默认 handler,重置配置
logger.remove()
log_format = (
"{time:YYYY-MM-DD HH:mm:ss.SSS zz} | "
"{extra[request_url]} | " # ← 动态插入请求路径
"{level: <8} | "
"Line {line: >4} ({file}): {message}"
)
# 设置默认 extra 值(必须!否则未 contextualize 时会报 KeyError)
logger.configure(extra={"request_url": "N/A"})
# 添加控制台与文件 handler,均使用同一 format
logger.add(sys.stdout, level=LOG_LEVEL, format=log_format, colorize=True, backtrace=True, diagnose=True)
logger.add("app.log", rotation="2 MB", level=LOG_LEVEL, format=log_format, colorize=False, backtrace=True, diagnose=True)-
在中间件中注入请求上下文
使用 with logger.contextualize(...) 包裹 call_next(request),确保整个请求生命周期(含路由处理、依赖注入、异常处理等)内的所有日志均携带该 URL:
from fastapi import FastAPI, Request, Response
app = FastAPI()
@app.middleware("http")
async def log_request_context(request: Request, call_next):
# 提取路径(或 request.url 含协议/域名,按需选择)
url_path = str(request.url.path)
with logger.contextualize(request_url=url_path):
logger.debug(f"Request started: {request.method} {url_path}")
try:
response = await call_next(request)
logger.debug(f"Request completed with status {response.status_code}")
return response
except Exception as e:
logger.exception("Unhandled exception during request")
raise-
任意位置调用日志,自动携带上下文
此后,在路由函数、依赖、工具函数中直接使用 logger.info("Processing user"),输出日志将自动包含已注入的 request_url:
@app.get("/users/{user_id}")
def get_user(user_id: int):
logger.info(f"Fetching user {user_id}") # ← 自动带上 request_url 前缀
return {"id": user_id, "name": "Alice"}✅ 示例日志输出:
2024-06-15 14:22:31.892 CST | /users/123 | DEBUG | Line 123 (main.py): Request started: GET /users/123 2024-06-15 14:22:31.895 CST | /users/123 | INFO | Line 128 (main.py): Fetching user 123 2024-06-15 14:22:31.897 CST | /users/123 | DEBUG | Line 125 (main.py): Request completed with status 200
⚠️ 注意事项与最佳实践
- contextualize 是上下文管理器:务必使用 with 语法包裹 call_next(),否则上下文仅作用于中间件自身,不传递至后续处理链。
- 避免敏感信息泄露:request.url 可能含查询参数(如 token),生产环境建议使用 request.url.path 或正则脱敏。
- 扩展更多上下文字段:可同时注入 request_id(配合 uuid.uuid4())、client_ip(request.client.host)、user_agent 等,只需在 extra 和 format 中同步声明。
- 性能无损:contextualize 是轻量级字典合并操作,实测对 QPS 影响可忽略。
- 错误隔离性:即使某次请求抛出异常并中断,其上下文也不会污染其他请求日志。
通过该方案,你无需侵入业务代码即可实现请求维度的结构化日志追踪,大幅提升分布式环境下的问题定位效率。










