用before_request和after_request可精准统计flask接口耗时:before_request用time.perf_counter()记录起始时间并存入g,after_request计算差值;须避坑response.data重复读取、teardown_request时机不准、日志缓冲等问题,推荐结合装饰器与采样策略。

用 before_request 和 after_request 拦截并计算耗时
Flask 本身不自带接口耗时统计,但靠两个钩子函数就能干净实现:在 before_request 记下起始时间,在 after_request 算差值。关键不是“怎么加日志”,而是“时间怎么取才准”——必须用 time.perf_counter(),不能用 time.time(),后者受系统时钟调整影响,可能产生负数或跳变。
实操建议:
- 把
perf_counter()的返回值存在g对象里(from flask import g),它是每个请求独立的上下文容器 -
after_request函数必须接收并返回response参数,否则会中断响应链 - 别在
teardown_request里算耗时——它可能在响应已发送后才执行,拿到的时间不准
记录日志时要避开 response.data 导致的重复读取
想打日志时顺手打印响应体?小心 response.data 被多次访问会触发 IOError: Response body already streamed。Flask 的响应对象是流式设计,data 属性只可读一次。
安全做法:
- 只记录状态码、URL、方法、耗时,不碰
response.data或response.get_data() - 真要记录响应内容,得提前在
after_request中用response.get_data(cache=True)缓存一份(但注意内存开销) - 如果用了 Gunicorn 或 uWSGI,还要考虑日志是否被 worker 进程缓冲,加
flush=True更稳妥
用装饰器方式给特定接口单独加耗时统计
全局钩子适合全站监控,但有时只想盯住某个慢接口(比如 /api/report),这时装饰器更轻量、更可控。
写法要点:
- 定义一个带
@functools.wraps的装饰器,内部用perf_counter()包裹视图函数调用 - 日志直接打在函数内部,避免和全局钩子冲突
- 注意装饰器要兼容带参数的路由(如
@app.route('/user/<uid>')</uid>),别漏掉*args, **kwargs
示例片段:
from functools import wraps
import time
import logging
<p>def log_duration(f):
@wraps(f)
def decorated_function(*args, *<em>kwargs):
start = time.perf_counter()
result = f(</em>args, **kwargs)
duration = time.perf_counter() - start
logging.info(f"API {f.<strong>name</strong>} took {duration:.3f}s")
return result
return decorated_function</p><p>@app.route("/api/slow")
@log_duration
def slow_api():
time.sleep(1.5)
return {"ok": True}
</p>
生产环境要注意日志级别和采样率
全量打耗时日志在高并发下会拖慢性能、撑爆磁盘。上线前必须控制输出频率。
建议配置:
- 默认用
logging.INFO,但对耗时超过阈值(比如 >500ms)的请求升为WARNING - 加简单采样逻辑,比如只记录每 100 个请求中的第一个:
if request.environ.get('HTTP_X_REQUEST_ID', '')[-2:] == '00' - 避免在日志格式中拼接字符串(如
f"{url} {duration}"),改用logging.info("API %s took %.3fs", url, duration),延迟格式化能减少无用计算
真正难的是平衡可观测性和性能损耗——不是所有请求都值得记,也不是所有慢请求都能靠日志定位到根因。先设好阈值和采样,再配合 APM 工具看调用链,比单靠日志强得多。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











