用microtime(true)在中间件中记录耗时最轻量可靠;$_SERVER['REQUEST_TIME_FLOAT']不可靠,不包含Laravel启动前开销,且CLI或FastCGI下可能失效。

直接上结论:用中间件记录请求耗时,microtime(true) 是最轻量、最可靠的方式;别依赖 $_SERVER['REQUEST_TIME_FLOAT'],它在 CLI 或某些 SAPI 下不可靠,且不包含 Laravel 启动前的开销。
为什么不用 $_SERVER['REQUEST_TIME_FLOAT']
这个值是 PHP 接收请求时记录的时间戳,但 Laravel 的启动(如服务容器加载、配置读取、中间件栈初始化)发生在它之后。你真正想测的是「从请求进入 Laravel 到响应发出」的完整耗时,而不是「PHP 开始处理那一刻起」。
- CLI 环境下
$_SERVER['REQUEST_TIME_FLOAT']可能为0或未定义 - 某些 FastCGI 配置中该值精度不足或被覆盖
- 它无法反映中间件执行、路由匹配、控制器调用等框架层耗时
microtime(true) + 中间件生命周期控制
在中间件 handle() 方法开头打点,结尾再打点,相减即得真实框架内耗时。这是 Laravel 官方日志组件(Log::debug() 带上下文)和 Telescope 内部也采用的方式。
示例中间件(app/Http/Middleware/LogRequestDuration.php):
public function handle(Request $request, Closure $next)
{
$startTime = microtime(true);
<pre class="brush:php;toolbar:false;">$response = $next($request);
$duration = round((microtime(true) - $startTime) * 1000, 2); // 毫秒,保留两位小数
Log::channel('slow')->info('request_duration', [
'uri' => $request->fullUrl(),
'method' => $request->method(),
'status' => $response->getStatusCode(),
'duration_ms' => $duration,
'ip' => $request->ip(),
]);
return $response;}
- 务必用
Log::channel('slow')单独通道,避免污染主日志;提前在config/logging.php中配好该 channel,指向daily或stack驱动 - 不要在
$response->getContent()上做耗时操作(比如大 JSON 解析),否则会把响应渲染时间也算进「框架耗时」 - 若需更高精度(微秒级),可改用
hrtime(true)(PHP 7.3+),但毫秒对 HTTP 请求已足够
如何过滤出真正慢的请求
全量记录所有请求耗时会快速撑爆磁盘。应只记录超过阈值的请求,并允许动态调整。
- 在中间件构造函数或配置文件中定义阈值,例如
env('SLOW_REQUEST_THRESHOLD_MS', 500) - 只在
$duration > $threshold时写日志,避免低价值噪音 - 配合
status_code过滤:4xx/5xx 错误请求即使快也值得记(可能是异常短路) - 注意:Laravel 的
APP_DEBUG=true会显著拖慢响应,测试阈值时务必关掉
真正容易被忽略的是「日志通道的驱动选择」——用 single 驱动写慢请求日志,在高并发下可能因文件锁导致请求阻塞;必须用 daily 或 stack(底层走 Monolog 的 RotatingFileHandler),否则你以为在排查性能问题,实际在制造性能瓶颈。











