怎样用Laravel框架的中间件实现请求日志记录功能

wen PHP项目 40

本文目录导读:

怎样用Laravel框架的中间件实现请求日志记录功能

  1. 创建日志中间件
  2. 基础版本实现
  3. 配置日志通道
  4. 高级版本(支持数据库记录)
  5. 创建数据库模型
  6. 注册中间件
  7. 使用示例
  8. 优化建议

我来介绍几种在 Laravel 中通过中间件实现请求日志记录的方法:

创建日志中间件

php artisan make:middleware RequestLogger

基础版本实现

<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
class RequestLogger
{
    /**
     * 处理请求
     */
    public function handle(Request $request, Closure $next)
    {
        // 请求前记录
        $startTime = microtime(true);
        $response = $next($request);
        // 请求后记录
        $duration = microtime(true) - $startTime;
        $this->logRequest($request, $response, $duration);
        return $response;
    }
    /**
     * 记录请求日志
     */
    protected function logRequest($request, $response, $duration)
    {
        $logData = [
            'method'     => $request->method(),
            'url'        => $request->fullUrl(),
            'ip'         => $request->ip(),
            'user_agent' => $request->userAgent(),
            'status'     => $response->status(),
            'duration'   => round($duration * 1000, 2) . 'ms',
            'timestamp'  => now()->toDateTimeString(),
        ];
        // 记录到日志文件
        Log::channel('request_log')->info('Request Log', $logData);
    }
}

配置日志通道

config/logging.php 中添加专门通道:

'channels' => [
    'request_log' => [
        'driver' => 'daily',
        'path' => storage_path('logs/requests/request.log'),
        'level' => 'info',
        'days' => 30,
        'formatter' => Monolog\Formatter\LineFormatter::class,
        'formatter_with' => [
            'format' => "[%datetime%] %message% %context%\n",
        ],
    ],
],

高级版本(支持数据库记录)

<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use App\Models\RequestLog;
use Illuminate\Support\Str;
class AdvancedRequestLogger
{
    // 需要排除的路径
    protected $excludedPaths = [
        'livewire/*',
        'telescope/*',
        'horizon/*',
        '_debugbar/*',
    ];
    // 敏感字段(记录时需隐藏)
    protected $sensitiveFields = [
        'password',
        'password_confirmation',
        'token',
        'secret',
    ];
    public function handle(Request $request, Closure $next)
    {
        // 检查是否需要排除
        if ($this->shouldExclude($request)) {
            return $next($request);
        }
        $startTime = microtime(true);
        $requestId = (string) Str::uuid();
        // 添加请求ID到请求头
        $request->headers->set('X-Request-Id', $requestId);
        $response = $next($request);
        $duration = microtime(true) - $startTime;
        $this->logRequest($request, $response, $duration, $requestId);
        // 添加响应头
        $response->headers->set('X-Request-Id', $requestId);
        $response->headers->set('X-Response-Time', round($duration * 1000, 2) . 'ms');
        return $response;
    }
    protected function logRequest($request, $response, $duration, $requestId)
    {
        $requestData = $request->all();
        $this->hideSensitiveData($requestData);
        $logData = [
            'request_id'   => $requestId,
            'method'       => $request->method(),
            'url'          => $request->fullUrl(),
            'ip'           => $request->ip(),
            'user_agent'   => $request->userAgent(),
            'user_id'      => auth()->id(),
            'headers'      => $this->getRelevantHeaders($request),
            'input_data'   => $requestData,
            'response_status' => $response->status(),
            'response_body' => $this->getResponseBody($response),
            'duration'     => round($duration * 1000, 2),
            'memory_usage' => memory_get_peak_usage(true) / 1024 / 1024 . 'MB',
        ];
        // 记录到文件
        Log::channel('request_log')->info('API Request', $logData);
        // 可选:记录到数据库
        try {
            RequestLog::create($logData);
        } catch (\Exception $e) {
            Log::error('Failed to save request log to database', ['error' => $e->getMessage()]);
        }
    }
    protected function shouldExclude($request)
    {
        foreach ($this->excludedPaths as $pattern) {
            if ($request->is($pattern)) {
                return true;
            }
        }
        return false;
    }
    protected function hideSensitiveData(&$data)
    {
        foreach ($data as $key => &$value) {
            if (in_array($key, $this->sensitiveFields)) {
                $value = '******';
            }
        }
    }
    protected function getRelevantHeaders($request)
    {
        $relevantHeaders = [
            'content-type',
            'accept',
            'authorization',
            'x-requested-with',
        ];
        $headers = [];
        foreach ($relevantHeaders as $header) {
            if ($value = $request->header($header)) {
                $headers[$header] = $header === 'authorization' ? '******' : $value;
            }
        }
        return $headers;
    }
    protected function getResponseBody($response)
    {
        $content = $response->getContent();
        // 限制响应体大小
        if (strlen($content) > 10000) {
            return substr($content, 0, 10000) . '...';
        }
        return $content;
    }
}

创建数据库模型

<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class RequestLog extends Model
{
    protected $fillable = [
        'request_id',
        'method',
        'url',
        'ip',
        'user_agent',
        'user_id',
        'headers',
        'input_data',
        'response_status',
        'response_body',
        'duration',
        'memory_usage',
    ];
    protected $casts = [
        'headers' => 'array',
        'input_data' => 'array',
    ];
    // 按用户范围查询
    public function scopeByUser($query, $userId)
    {
        return $query->where('user_id', $userId);
    }
    // 按状态码查询
    public function scopeByStatus($query, $status)
    {
        return $query->where('response_status', $status);
    }
}

注册中间件

app/Http/Kernel.php 中注册:

protected $middlewareGroups = [
    'web' => [
        // ... 其他中间件
        \App\Http\Middleware\RequestLogger::class, // 基础版本
        // \App\Http\Middleware\AdvancedRequestLogger::class, // 高级版本
    ],
    'api' => [
        // ... 其他中间件
        \App\Http\Middleware\AdvancedRequestLogger::class,
    ],
];
// 或者注册为路由中间件
protected $routeMiddleware = [
    // ... 其他中间件
    'log.request' => \App\Http\Middleware\RequestLogger::class,
];

使用示例

在路由中使用:

// 应用到整个路由组
Route::middleware(['log.request'])->group(function () {
    Route::get('/users', [UserController::class, 'index']);
    Route::post('/users', [UserController::class, 'store']);
});
// 应用到单个路由
Route::get('/special-page', [PageController::class, 'show'])
    ->middleware('log.request');

查看日志:

# 查看日志文件
tail -f storage/logs/requests/request-2024-01-01.log
# 查询数据库
DB::table('request_logs')
    ->where('response_status', '>=', 500)
    ->orderBy('created_at', 'desc')
    ->get();

优化建议

// 1. 异步记录日志(使用队列)
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
class LogRequestJob implements ShouldQueue
{
    use Queueable, Dispatchable, InteractsWithQueue, SerializesModels;
    public function handle($logData)
    {
        // 处理日志记录
    }
}
// 2. 使用事件系统
Event::listen('request.logged', function ($logData) {
    // 发送到外部日志系统
});
// 3. 条件记录
if (config('app.log_requests')) {
    $this->logRequest($request, $response, $duration);
}

这样实现后,你的 Laravel 应用就会自动记录所有请求的详细信息,便于调试、监控和审计。

抱歉,评论功能暂时关闭!