最轻量可靠的埋点方式是在全局中间件中用 microtime(true) 计时,记录请求耗时、sql 执行时间并结构化日志,配合慢查询过滤、异步上报及响应头输出。

直接在中间件里用 microtime(true) 埋点,是最轻量、最可靠的方式——它覆盖所有请求路径(含异常 500、404),不依赖调试模式,也不干扰业务逻辑。
全局中间件统一计时
在 app/middleware.php 中注册自定义中间件类,例如 \app\middleware\TimingRecord::class。注意必须写完整命名空间路径,开头带反斜杠,且该文件中禁止使用 use 语句。
- 中间件类的
handle()方法接收$request和$next - 开头调用
$start = microtime(true)记录浮点秒级起始时间 - 执行
$response = $next($request)后,再算耗时:$cost_ms = round((microtime(true) - $start) * 1000, 2) - 用
think\facade\Log::info()写结构化日志,包含uri、method、status_code和cost_ms - 避免开启
app_debug = true,否则 Debugbar 等组件会额外引入 20–50ms 干扰
关联数据库查询细节
总耗时只是表象,SQL 往往占大头。单靠中间件无法拆解 DB 环节,需配合事件监听补全。
- 在
app/provider/EventServiceProvider.php的listen数组中添加:think\db\Events\Querying::class => [app\listener\LogQueryStart::class]和think\db\Events\Queried::class => [app\listener\LogQueryEnd::class] - 两个监听器分别记录查询发起与返回时刻,计算差值得到真实 SQL 执行毫秒数
- 把
$sql、$params、$time、调用位置(debug_backtrace()取前两层)一并写入独立日志通道(如query_slow.log) - 别用
Db::getQueryTime(),它只返回最后一次查询耗时,无法绑定当前请求上下文
输出耗时到响应头(可选)
方便前端或网关快速感知,但要注意规范和性能影响。
- 在中间件
handle()末尾,给$response添加头:$response->header('X-Response-Time', sprintf('%.3f', $cost_ms)) - 头名必须是 ASCII 字符,推荐标准命名
X-Response-Time或X-Process-Time - 值保留 3 位小数(单位毫秒),避免超长或非数字字符引发代理拦截
- 确保该中间件注册顺序早于 Gzip、CORS 等可能修改响应头的中间件
慢请求过滤与上报
高频接口(如健康检查)无需全量统计,高耗时才值得记录和告警。
- 对
/health、/ping等路径做白名单跳过:if (stripos($request->url(), '/health') === 0) { return $next($request); } - 只对
$cost_ms > 1000的请求打Log::warning(),避免日志爆炸 - 上报行为必须异步:用
think\facade\Queue推送至消息队列,或交由 Swoole 定时任务批量处理,严禁同步 HTTP 上报 - 慢查阈值建议按业务动态设定(如核心订单接口设 300ms,后台报表设 3000ms)











