应从ThinkPHP框架入口埋点采集真实接口耗时,用microtime(true)在PerformanceMiddleware中计时、ExceptionHandle中结构化记录,结合动态基线(如7天同小时段P95±25%)告警,避免固定阈值误报,并统一时间戳与request_id关联SQL与PHP日志。

接口响应变慢不是“突然发生”的,而是基线悄悄漂移后才被感知到的——所以不能等用户投诉才反应,得用可量化的基线偏离做主动告警。
怎么在ThinkPHP里埋点采集真实接口耗时
别依赖浏览器 Network 面板或 Nginx 日志里的 $request_time,它们不包含 PHP 应用层逻辑耗时(比如 ORM 查询、模板渲染、中间件执行)。必须从框架入口开始计时,到响应发送前结束。
- 在
app/middleware/PerformanceMiddleware.php中添加前后时间戳,用microtime(true)记录,存入 Request 对象或全局静态变量 - 在
app/ExceptionHandle.php的render()方法末尾统一写入日志,确保异常路径也覆盖 - 避免在控制器里零散
dump()或Log::info(),易漏、难聚合;推荐统一打结构化日志,字段至少含:uri、method、status_code、duration_ms、trace_id - 注意:不要用
APP_DEBUG=true时的 Trace 面板数据做基线——它自带开销,且只在 HTML 响应中输出,API 接口根本不会触发
为什么不能直接用固定阈值(比如 >500ms)告警
固定阈值在 ThinkPHP 场景下极易误报。一个 /api/v1/order/list 接口,在分页查 10 条和查 1000 条时,耗时天然差 3 倍;促销期间并发翻倍,平均响应涨 40% 也属正常。
- 错误做法:
if ($duration > 500) { trigger_alert(); }—— 这会把业务高峰当成故障 - 正确思路:按接口维度维护动态基线,例如用过去 7 天同小时段的 P95 耗时作为当日基准,允许 ±25% 浮动;工作日/节假日需分开建模
- ThinkPHP 特有风险点:FPM 子进程生命周期短,
slowlog只记录单次请求,但“缓慢”可能是累积内存泄漏导致后续请求越来越慢,需结合pm.status_path中的slow_requests比率判断 - 建议用 Prometheus +
client_php暴露指标,Grafana 里用histogram_quantile(0.95, sum(rate(http_request_duration_seconds_bucket[1h])) by (le, uri))算动态 P95
如何让告警真正“有用”,而不是刷屏
告警不是越响越好,而是要让人一眼看懂“哪里慢、为什么慢、现在要不要动”。光说“/api/user/info 超时了”等于没说。
立即学习“PHP免费学习笔记(深入)”;
- 必须关联上下文:带上该请求的
sql_count、redis_calls、memory_peak_usage(可用memory_get_peak_usage()获取),否则无法区分是 DB 慢还是代码循环卡住 - 避免单指标驱动:比如只监控耗时,忽略错误率。实际场景中,
duration ↑ + status_code=500 ↑ + slow_requests ↑同时发生,才值得立刻介入 - ThinkPHP 生态常见坑:开启
app_trace=true且未关闭时,每次请求会额外写 trace 文件,磁盘 IO 拉高间接拖慢所有接口——生产环境务必确认APP_DEBUG=false且app_trace未启用 - 告警渠道分级:P0 级(如慢请求率 >3% 且活跃进程达
pm.max_children)走电话;P1(单接口 P95 漂移超 40%)走企业微信;其余聚合日报即可
最常被跳过的一步,是没把数据库慢查询日志和 PHP 耗时日志对齐时间戳。ThinkPHP 的 SQL 日志默认带毫秒级时间,但 FPM 的 slowlog 只精确到秒——这会导致你看到“接口耗时 800ms”,却查不到对应那条慢 SQL。务必统一用 microtime(true) 打点,并在日志里加 request_id 关联。


















