为FastAPI应用添加数据库查询日志中间件
🐍 如何在 FastAPI 中优雅地记录数据库查询日志?
让每一行 SQL 都“看得见”,让性能瓶颈无处隐藏。
在开发高性能 Web 应用时,数据库查询往往是性能瓶颈的第一嫌疑人。但在 FastAPI 这类现代异步框架中,如何在不破坏异步上下文、不影响性能的前提下,干净、可追踪、可过滤地记录每一次 SQL 执行?今天我们就来拆解一个实用的中间件实现。
为什么需要记录数据库查询日志?
- 调试复杂查询:快速定位慢查询或错误语句。
- 性能分析:结合 trace ID,将 SQL 与具体 API 请求关联。
- 安全审计:记录敏感操作(如 DELETE、UPDATE)。
- 避免 N+1 问题:直观发现重复或冗余查询。
但盲目打印所有 SQL 会带来两个问题:
- 大量系统查询干扰日志(如
SELECT current_schema()); - 异步上下文丢失,无法与请求日志关联。
接下来,我们通过一个自定义中间件,优雅解决这些问题。
核心思路:监听 SQLAlchemy 事件 + 异步日志
我们基于 SQLAlchemy 的事件系统,在每次执行 SQL 前触发钩子,同时利用 FastAPI 的请求上下文,将 SQL 日志与当前请求绑定。
第一步:监听数据库执行事件
1 | from sqlalchemy import event |
✅ 使用
before_cursor_execute事件,在 SQL 发送到数据库前拦截。
✅ 通过asyncio.create_task()异步记录,避免阻塞主流程。
第二步:过滤系统查询
PostgreSQL 等数据库在连接时会自动执行大量元数据查询。我们通过正则匹配过滤掉它们:
1 | def _is_system_query(self, statement: str) -> bool: |
📌 这能减少 80% 以上的无用日志,让日志清爽可读。
第三步:安全拼接 SQL 与参数
直接打印 statement 和 parameters 是分离的。我们需要将它们合并成可读的完整 SQL:
1 | def _format_sql_with_params(self, statement: str, parameters: Any) -> str: |
⚠️ 注意:这只是用于日志展示,绝不能用于实际执行!避免 SQL 注入误解。
第四步:与请求上下文绑定(Trace ID)
关键来了!如何让这条 SQL 日志和当前 API 请求关联?
我们借助 FastAPI 的 request.state 存储 trace ID,并在记录时复用统一的日志通道:
1 | async def _log_database_query(self, sql_statement: str): |
🌟 这样,你在日志中就能看到:
1
2
3 [trace=abc123] [event=request_start] ...
[trace=abc123] [event=database_query] {"sql": "SELECT * FROM users WHERE id = '1001'"}
[trace=abc123] [event=request_end] ...
为什么不用 logging 而用 loguru?
- 结构化日志支持更好;
- 异步友好;
- 无需配置即可高亮、旋转、过滤;
- 与你项目中已有的
loguru日志体系无缝集成。
最终效果
✅ 只记录业务 SQL,自动过滤系统查询
✅ SQL 语句可读(参数已注入)
✅ 与 API 请求通过 trace ID 关联
✅ 零阻塞、异步写入
✅ 无请求上下文时自动降级为普通日志
小结
数据库日志不是越多越好,而是精准、可追溯、低开销才最有价值。通过 SQLAlchemy 事件 + FastAPI 请求上下文 + loguru 异步日志,我们实现了一个生产可用的数据库查询追踪方案。
💡 建议:在开发/测试环境开启此日志,生产环境按需开启(可通过配置开关控制)。
源码已整理为可复用中间件,欢迎关注公众号,回复【dblog】获取完整代码模板!
如果你觉得这篇文章对你有帮助,欢迎点赞、转发,让更多 Python 开发者看到!也欢迎在评论区交流你的日志实践 👇
#FastAPI #Python #数据库优化 #日志系统 #后端开发 #SQLAlchemy #loguru





