如何实现Laravel中间件来有效记录请求日志?
- 内容介绍
- 文章标签
- 相关推荐
本文共计918个文字,预计阅读时间需要4分钟。
由于日志配置错误导致日志输出异常,常见情况是在中间件中使用了`Log::info()`,但`config/logging.php`中对相应`channel`的`driver`配置有误。例如,错误配置可能如下:
- 检查
config/logging.php中你用的 channel 是否存在、driver和必要参数(如path、level)是否齐全 - 避免在中间件里硬编码 channel 名,优先复用默认
stack:直接用Log::info(),它走的是config('logging.default') - 调试时临时加一行
Log::stack(['single'])->info('test');看是否能落盘,确认是配置问题还是中间件执行时机问题
app/Http/Middleware/LogRequest.php 怎么拿到完整请求体?
Laravel 默认把 $request->getContent() 读过一次后清空,中间件里再调用就是空字符串——这是最常踩的坑。不是你代码写错了,是 PSR-7 的 immutable request 特性在起作用。
- 必须在中间件第一行就调用
$request->getContent()并存到变量,后续都用这个副本 - 如果要记录 JSON 请求体,别直接
json_decode($request->getContent(), true)后又想原样记录,先$raw = $request->getContent(),再 decode,最后 log 用$raw - 表单请求(
application/x-www-form-urlencoded)不能只靠$request->all(),它不包含原始 raw body;需配合$request->getContentType()判断类型,再决定取数方式
记录响应内容时 $response->getContent() 返回空?
响应对象在中间件中可能还没渲染(尤其是返回 View 或 JsonResponse 时),getContent() 拿不到真实输出。这不是 bug,是 Laravel 响应生命周期设计如此。
- 不要试图在「前置」中间件里读响应内容,必须放在「后置」中间件(即
handle方法里return $next($request);之后) - 即便如此,
StreamedResponse、文件下载响应等类型仍无法通过getContent()获取,它们压根不缓存内容到内存 - 真要记录响应体,优先考虑在
AppServiceProvider的boot()里监听Illuminate\Http\Events\ResponsePrepared事件,它比后置中间件更可靠
高并发下日志中间件拖慢接口怎么办?
每请求都写磁盘日志,I/O 成为瓶颈。Laravel 默认 single 和 daily driver 都是同步写入,中间件里一记日志,PHP 进程就得等磁盘返回。
- 开发环境可保留同步,生产务必切到
stack+monolog的syslog或errorlog,或者用papertrail等远程驱动 - 避免在中间件里记录全量请求头或 body,只记关键字段:
$request->method()、$request->fullUrl()、$request->ip()、耗时(用microtime(true)差值) - 日志 level 控制好,
Log::debug()在生产应关闭,config/logging.php中对应 channel 的level设为'notice'或更高
真正难的不是怎么写进日志,是怎么在不干扰正常流程的前提下,让日志既够用又不拖垮服务——尤其是当你要查一个超时请求,却发现日志本身让那个请求更慢了。
本文共计918个文字,预计阅读时间需要4分钟。
由于日志配置错误导致日志输出异常,常见情况是在中间件中使用了`Log::info()`,但`config/logging.php`中对相应`channel`的`driver`配置有误。例如,错误配置可能如下:
- 检查
config/logging.php中你用的 channel 是否存在、driver和必要参数(如path、level)是否齐全 - 避免在中间件里硬编码 channel 名,优先复用默认
stack:直接用Log::info(),它走的是config('logging.default') - 调试时临时加一行
Log::stack(['single'])->info('test');看是否能落盘,确认是配置问题还是中间件执行时机问题
app/Http/Middleware/LogRequest.php 怎么拿到完整请求体?
Laravel 默认把 $request->getContent() 读过一次后清空,中间件里再调用就是空字符串——这是最常踩的坑。不是你代码写错了,是 PSR-7 的 immutable request 特性在起作用。
- 必须在中间件第一行就调用
$request->getContent()并存到变量,后续都用这个副本 - 如果要记录 JSON 请求体,别直接
json_decode($request->getContent(), true)后又想原样记录,先$raw = $request->getContent(),再 decode,最后 log 用$raw - 表单请求(
application/x-www-form-urlencoded)不能只靠$request->all(),它不包含原始 raw body;需配合$request->getContentType()判断类型,再决定取数方式
记录响应内容时 $response->getContent() 返回空?
响应对象在中间件中可能还没渲染(尤其是返回 View 或 JsonResponse 时),getContent() 拿不到真实输出。这不是 bug,是 Laravel 响应生命周期设计如此。
- 不要试图在「前置」中间件里读响应内容,必须放在「后置」中间件(即
handle方法里return $next($request);之后) - 即便如此,
StreamedResponse、文件下载响应等类型仍无法通过getContent()获取,它们压根不缓存内容到内存 - 真要记录响应体,优先考虑在
AppServiceProvider的boot()里监听Illuminate\Http\Events\ResponsePrepared事件,它比后置中间件更可靠
高并发下日志中间件拖慢接口怎么办?
每请求都写磁盘日志,I/O 成为瓶颈。Laravel 默认 single 和 daily driver 都是同步写入,中间件里一记日志,PHP 进程就得等磁盘返回。
- 开发环境可保留同步,生产务必切到
stack+monolog的syslog或errorlog,或者用papertrail等远程驱动 - 避免在中间件里记录全量请求头或 body,只记关键字段:
$request->method()、$request->fullUrl()、$request->ip()、耗时(用microtime(true)差值) - 日志 level 控制好,
Log::debug()在生产应关闭,config/logging.php中对应 channel 的level设为'notice'或更高
真正难的不是怎么写进日志,是怎么在不干扰正常流程的前提下,让日志既够用又不拖垮服务——尤其是当你要查一个超时请求,却发现日志本身让那个请求更慢了。

