You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

132 lines
4.4 KiB
PHP

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Schema;
use Symfony\Component\HttpFoundation\Response;
class LogRequest
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
$request->attributes->set('start_time', microtime(true));
return $next($request);
}
// 响应发送给客户端后执行
public function terminate(Request $request, Response $response): void
{
$startTime = $request->attributes->get('start_time');
$endTime = microtime(true);
// 收集日志数据
$data = [
'method' => $request->method(),
'url' => $request->fullUrl(),
'ip' => $request->ip(),
'headers' => json_encode($this->getLoggableHeaders($request), JSON_UNESCAPED_UNICODE),
'input' => $this->truncate(json_encode($request->all(), JSON_UNESCAPED_UNICODE), 5000),
// 这里应用截断逻辑:如果 response 超过 10KB将直接存储 'Data too long for column'
'response' => $this->truncate((string) $response->getContent(), 10000),
'status_code' => $response->status(),
'response_time' => round($endTime - $startTime, 3),
'created_at' => now(),
'updated_at' => now()
];
$tableName = 'zz_request_' . date('Ym');
$this->createTableIfNotExists($tableName);
DB::table($tableName)->insert($data);
}
/**
* 检查并创建月度表
*/
protected function createTableIfNotExists($tableName)
{
if (!Schema::hasTable($tableName)) {
// 使用 Schema 构建器创建表
Schema::create($tableName, function (Blueprint $table) {
$table->increments('id');
$table->string('method', 10)->comment('请求方法');
$table->text('url')->comment('请求URL');
$table->string('ip', 45)->comment('客户端IP');
$table->text('headers')->nullable()->comment('请求头');
$table->text('input')->nullable()->comment('请求参数');
$table->text('response');
$table->integer('status_code')->comment('响应状态码');
$table->decimal('response_time', 10, 3)->comment('响应时间(毫秒)');
$table->timestamps();
$table->index('created_at', 'idx_created_at');
});
}
}
/**
* 简单的长度检查:要么全要,要么给个提示
* 不再进行复杂的截断操作
*/
protected function truncate($string, $maxBytes = 5000)
{
// 如果长度没超标,直接返回原数据
if (strlen($string) <= $maxBytes) {
return $string;
}
// 如果超标了,直接返回一个固定的提示字符串
return 'Data too long for column';
}
protected function getLoggableHeaders(Request $request)
{
$whitelist = [
'authorization',
'content-type',
'user-agent',
'x-requested-with',
'x-forwarded-for',
'x-real-ip',
'x-correlation-id',
'trace-id',
'request-id',
'host'
];
$result = [];
foreach ($whitelist as $header) {
if (!$request->headers->has($header)) {
continue;
}
$value = $request->headers->get($header);
// 脱敏处理
if (in_array(strtolower($header), ['authorization', 'cookie'])) {
$result[$header] = '[' . strtoupper(substr($header, 0, 3)) . ']';
}
// 或者更精细控制
elseif (strtolower($header) === 'authorization' && str_starts_with($value, 'Bearer ')) {
$token = substr($value, 7);
$result[$header] = 'Bearer ' . substr($token, 0, 6) . '...' . substr($token, -4);
} else {
$result[$header] = mb_strlen($value) > 200
? substr($value, 0, 197) . '...'
: $value;
}
}
return $result;
}
}