本文目录导读:

在Laravel中,使用中间件记录请求和响应日志是一个很常见的需求,以下是几种实现方式:
创建自定义中间件(推荐)
创建中间件
php artisan make:middleware RequestResponseLogger
编写中间件逻辑
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Symfony\Component\HttpFoundation\Response;
class RequestResponseLogger
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle(Request $request, Closure $next)
{
// 记录请求日志
$this->logRequest($request);
// 继续处理请求
$response = $next($request);
// 记录响应日志
$this->logResponse($request, $response);
return $response;
}
/**
* 记录请求日志
*/
protected function logRequest(Request $request)
{
Log::channel('api')->info('Request', [
'method' => $request->method(),
'url' => $request->fullUrl(),
'headers' => $request->headers->all(),
'body' => $request->all(),
'ip' => $request->ip(),
'user_agent' => $request->userAgent(),
]);
}
/**
* 记录响应日志
*/
protected function logResponse(Request $request, Response $response)
{
Log::channel('api')->info('Response', [
'url' => $request->fullUrl(),
'status' => $response->getStatusCode(),
'headers' => $response->headers->all(),
'body' => $response->getContent(),
]);
}
}
创建日志频道(可选)
在 config/logging.php 中添加:
'channels' => [
// ... 其他频道
'api' => [
'driver' => 'daily',
'path' => storage_path('logs/api.log'),
'level' => 'info',
'days' => 14,
'formatter' => Monolog\Formatter\LineFormatter::class,
'formatter_with' => [
'format' => "[%datetime%] %channel%.%level_name%: %message% %context%\n",
],
],
],
注册中间件
在 app/Http/Kernel.php 中注册:
全局中间件(所有请求):
protected $middleware = [
// ...
\App\Http\Middleware\RequestResponseLogger::class,
];
路由组中间件:
protected $middlewareGroups = [
'web' => [
// ...
],
'api' => [
// ...
\App\Http\Middleware\RequestResponseLogger::class,
],
];
单个路由中间件:
protected $routeMiddleware = [
// ...
'log.api' => \App\Http\Middleware\RequestResponseLogger::class,
];
使用Terminable中间件(异步日志)
如果希望在响应发送后记录日志,可以使用Terminable中间件:
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
class AsyncRequestLogger
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle(Request $request, Closure $next)
{
// 保存请求数据以便后续使用
$request->attributes->set('start_time', microtime(true));
return $next($request);
}
/**
* Handle tasks after the response has been sent to the browser.
*/
public function terminate(Request $request, $response)
{
$duration = microtime(true) - $request->attributes->get('start_time', 0);
Log::channel('api')->info('API Request Log', [
'method' => $request->method(),
'url' => $request->fullUrl(),
'status' => $response->getStatusCode(),
'duration' => round($duration * 1000, 2) . 'ms',
'ip' => $request->ip(),
]);
}
}
记录敏感信息过滤
在实际应用中,可能需要过滤敏感信息(如密码、token等):
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
class SecureRequestLogger
{
protected $hiddenFields = [
'password',
'password_confirmation',
'token',
'secret',
'credit_card',
];
public function handle(Request $request, Closure $next)
{
$requestData = $this->filterSensitiveData($request->all());
Log::info('Request', [
'method' => $request->method(),
'url' => $request->fullUrl(),
'body' => $requestData,
]);
$response = $next($request);
Log::info('Response', [
'url' => $request->fullUrl(),
'status' => $response->getStatusCode(),
]);
return $response;
}
protected function filterSensitiveData(array $data)
{
foreach ($this->hiddenFields as $field) {
if (isset($data[$field])) {
$data[$field] = '*** FILTERED ***';
}
}
return $data;
}
}
使用事件监听器(高级用法)
也可以使用Laravel的事件系统来实现:
// app/Providers/EventServiceProvider.php
protected $listen = [
\Illuminate\Foundation\Http\Events\RequestHandled::class => [
\App\Listeners\LogRequestResponse::class,
],
];
创建监听器:
php artisan make:listener LogRequestResponse
<?php
namespace App\Listeners;
use Illuminate\Foundation\Http\Events\RequestHandled;
use Illuminate\Support\Facades\Log;
class LogRequestResponse
{
public function handle(RequestHandled $event)
{
$request = $event->request;
$response = $event->response;
Log::info('Request handled', [
'method' => $request->method(),
'url' => $request->fullUrl(),
'status' => $response->getStatusCode(),
'duration' => microtime(true) - LARAVEL_START,
]);
}
}
使用建议
- 性能考虑:生产环境中建议使用异步日志记录
- 日志级别:根据不同环境设置不同的日志级别
- 敏感信息:确保过滤掉密码、token等敏感信息
- 日志轮转:使用
daily驱动自动管理日志文件大小 - 条件记录:可以根据需求只记录特定状态码或错误的请求
// 条件记录示例
if ($response->getStatusCode() >= 400) {
Log::warning('Error response', [...]);
}
选择哪种方式取决于你的具体需求,一般推荐使用方法一(自定义中间件),因为它最灵活且易于维护。