基于Laravel封装一个强大的请求响应日志记录中间件

1,219次阅读
没有评论

为何强大

  1. 记录全面: 包含请求路径、请求方法、客户端IP、设备标识、荷载数据、文件上传、请求头、业务逻辑处理时间、业务逻辑所耗内存、用户id、以及响应数据。
  2. 配置简单: 默认不需要写任何逻辑可开箱即用,靠前4个方法,就可指定某些url不记录日志,或不记录某些请求头,不记录某些荷载数据,或决定是否返回非json类型的相应数据。
  3. 清晰简洁: 返回的每项数据都是json或者字符串,一行一项数据,且缩进一致,清晰明了。该有的展示项都有,该忽略的展示项已经被忽略。
  4. 规范统一: 无论请求数据是什么格式,最后到日志的数据之有字符串或json两种格式,避免五花八门的数据造成日志格式混乱。
  5. 强兼容性: 无论是什么请求方式(GET、POST、DELETE、PATCH、PUT、OPTIONS等),或者传递什么内容类型(x-www-form-urlencoded、multipart/form-data、json、xml、纯文本),只要通过路由,上游无断点或死循环,日志都可记录,适用于任何项目的场景。
  6. 灵活扩展: 对中间件前4个配置相关的方法,引入了Request对象,方便根据此对象实现更复杂的逻辑。
  7. 方便调试: 当项目出问题时,有日志参考是必须的,结合”tail -f”,或者日志查看器插件更是如虎添翼。
  8. 日志隔离: 利用laravel强大的日志渠道隔离和按天切割功能,使得记录日志过程更加强大。

效果示例

text

复制代码[2023-10-18 18:14:48] local.INFO: url : http://xxx/api?framework=laravel&language=php method : POST ip : 127.0.0.1 ua : PostmanRuntime-ApipostRuntime/1.1.0 payload : {"key":"val","k":"v"} file : [] header : {"content-type":"application\/x-www-form-urlencoded"} time : 16.90 mem : 19.16 MB user_id : 0 response : {"code":0,"msg":"","data":[]}

部署

bash

复制代码#在config/logging.php中的channels项添加如下配置 'req' => [ 'driver' => 'daily', 'path' => storage_path('logs/request.log'), 'level' => env('LOG_LEVEL', 'debug'), 'days' => 3, 'permission' => 0777 ], #进入laravel所在目录,用artisan命令创建中间件 php artisan make:middleware RequestMiddleware

php

复制代码//在app/Http/Kernel.php文件的protected $middleware数组中追加一行,用于注册全局中间件 \App\Http\Middleware\RequestMiddleware::class

编写

php

复制代码<?php namespace App\Http\Middleware; use Closure; use Illuminate\Http\Request; use Illuminate\Support\Facades\Log; /** * @Class RequestMiddleware 记录请求日志,方便开发者调试 * @package App\Http\Middleware */ class RequestMiddleware { /** * @function 设置不记录日志的url * @param \Illuminate\Http\Request $request * @return array * @other 排除规则依照request()->is()方法 */ private function setExceptUrl($request) { return [ 'admin/logs*', 'admin/logs/*', ]; } /** * @function 设置不记录的荷载项 * @param \Illuminate\Http\Request $request * @return array * @other 比如防止CSRF的_token */ private function setExceptPayload($request) { return [ '_token' ]; } /** * @function 设置不记录日志的请求头 * @param \Illuminate\Http\Request $request * @return array */ private function setExceptHeader($request) { return [ //官方 'accept', 'accept-encoding', 'accept-language', 'authorization', 'cache-control', 'charset', 'connection', 'content-length', 'content-type_except', 'cookie', 'host', 'origin', 'pragma', 'referer', 'sec-ch-ua', 'sec-ch-ua-mobile', 'sec-ch-ua-platform', 'sec-fetch-dest', 'sec-fetch-mode', 'sec-fetch-site', 'upgrade-insecure-requests', 'user-agent', 'x-forwarded-for', 'x-forwarded-host', 'x-forwarded-port', 'x-forwarded-proto', 'x-requested-with', //自定义 'encrypteddata', 'ivstr', ]; } /** * @function 是否记录非json格式的响应的数据 * @param \Illuminate\Http\Request $request * @return bool */ private function isRecordHttpResponseData($request) { return false; } //------------------------------------------------此分割线以下代码无需修改------------------------------------------------ /** * @function 请求日志中间件 * @param \Illuminate\Http\Request $request * @param \Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse) $next * @return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse */ public function handle(Request $request, Closure $next) { if($this->hasExceptUrl($request)) { return $next($request); } $start = microtime(true); $response = $next($request); $end = microtime(true); $res = $this->requestDataFormat([ 'url' => $this->getFullUrl($request), 'method' => $this->getRequestMethod($request), 'ip' => $this->getClientIp($request), 'ua' => $this->getUa($request), 'payload' => $this->getRequestPayload($request), 'file' => $this->getClearRequestFile($request), 'header' => $this->getClearRequestHeader($request), 'time' => bcmul(bcsub($end, $start, 6), 1000, 2), 'mem' => $this->getUsageMemory(), 'user_id' => $this->getIdWithToken($request), 'response' => $this->responseFormat($request, $response), ]); Log::channel('req')->info($res); return $response; } /** * @function 请求记录黑名单,在黑名单内的规则不记录日志 * @param \Illuminate\Http\Request $request * @return bool */ private function hasExceptUrl($request) { $blacklist = array_filter($this->setExceptUrl($request)); if(! $blacklist) { return false; } foreach($blacklist as $every_blacklist) { if($request->is($every_blacklist)) { return true; } } return false; } /** * @function 获取全路径 * @param \Illuminate\Http\Request $request * @return string */ private function getFullUrl($request) { return urldecode($request->fullUrl()); } /** * @function 获取请求方式 * @param \Illuminate\Http\Request $request * @return string * @other 由于laravel存在_method覆盖机制,若有括号,则括号内的为真正的请求方式 */ private function getRequestMethod($request) { $real_method = $_SERVER['REQUEST_METHOD']; //防止乱传参导致的错误 try{ $laravel_method = $request->method(); } catch (\Exception $exception) { $laravel_method = $real_method; } if($real_method === $laravel_method) { return $real_method; } return "{$laravel_method}({$real_method})"; } /** * @function 获取客户端的IP * @param \Illuminate\Http\Request $request * @return string */ private function getClientIp($request) { return $request->getClientIp(); } /** * @function 获取用户代理 * @param \Illuminate\Http\Request $request * @return string */ private function getUa($request) { return $request->header('user-agent') ?? '""'; } /** * @function 获取请求荷载,包含x-www-form-urlencoded、multipart/form-data、json、xml等纯文本荷载数据 * @param \Illuminate\Http\Request $request * @return array */ private function getRequestPayload($request) { if($request->method() === 'GET') { return []; } $except = collect($request->query())->keys()->merge($this->setExceptPayload($request))->filter(); $input = collect($request->input())->except($except)->map(function ($val) { if (is_null($val)) { return ''; } return $val; })->toArray(); if($input) { return $input; } $raw = $request->getContent(); if($request->header('content-type') === 'application/xml') { if(! $raw) { return []; } if(! $this->isXml($raw)) { return [$raw]; } return json_decode(json_encode(simplexml_load_string(str_replace(["\r", "\n"], '', $raw))), true); } return array_filter([$raw]); } /** * @function 获取简洁的文件上传数据 * @param \Illuminate\Http\Request $request * @return array */ private function getClearRequestFile($request) { return collect($request->allFiles())->map(function($val) { if(is_array($val)) { $res = collect($val)->map(function($v) { return $v->getClientOriginalName(); }); } else { $res = $val->getClientOriginalName(); } return $res; })->toArray(); } /** * @function 获取干净的请求头 * @param \Illuminate\Http\Request $request * @return array */ private function getClearRequestHeader($request) { $except_header = array_filter($this->setExceptHeader($request)); return collect($request->header())->except($except_header)->toArray(); } /** * @function 获取脚本使用的内存 * @return string * @other void */ function getUsageMemory() { $bytes = memory_get_usage(); $units = ['B', 'KB', 'MB', 'GB', 'TB']; $bytes /= pow(1024, ($i = floor(log($bytes, 1024)))); return round($bytes, 2) . ' ' . $units[$i]; } /** * @function 通过token获取user_id,这个有伪造的风险 * @param \Illuminate\Http\Request $request * @return int */ private function getIdWithToken($request) { $token = $request->header('authorization'); if(! $token) { return 0; } $payload = (explode('.', $token)[1]) ?? null; if(is_null($payload)) { return 0; } $json = base64_decode($payload); $arr = json_decode($json, true); if(is_null($arr)) { return 0; } return $arr['sub'] ?? 0; } /** * @function 格式化响应数据 * @param \Illuminate\Http\Request $request * @param \Illuminate\Http\JsonResponse|\Illuminate\Http\Response $response * @return string|array */ private function responseFormat($request, $response) { if($response instanceof \Illuminate\Http\JsonResponse){ return collect($response->getData())->toArray(); } if(! $this->isRecordHttpResponseData($request)) { return '""'; } if($response instanceof \Illuminate\Http\Response) { return $response->getContent(); } return '""'; } /** * @function 格式化数组并转换为字符串 * @param $request_data array * @return string */ private function requestDataFormat($request_data) { $str = "\n"; foreach($request_data as $k => $v) { //格式化请求头 if(($k == 'header') && $v) { foreach($v as $key => $val) { if(count($val) == 1) { $v[$key] = collect($val)->values()->first(); } else { $v[$key] = $val; } } } //格式化数据 $v = is_array($v) ? json_encode($v, JSON_UNESCAPED_UNICODE) : $v; $k = str_pad($k, 9, ' ', STR_PAD_RIGHT); $str .= "{$k}: {$v}\n"; } return $str; } /** * @function 判断是否是xml * @param $str string 要判断的xml数据 * @return bool */ private function isXml($str) { libxml_use_internal_errors(true); simplexml_load_string($str); $errors = libxml_get_errors(); libxml_clear_errors(); return ! $errors; } }

说明

  1. 文章的每个方法都加了清晰的注释,且拆分的非常详细,便于二次开发,像是getUsageMemory(),isXml(),方法都可以封装到公共的工具库中。
  2. user_id项是因为项目使用jwt,为了方便调试,临时加的。考虑到性能问题没做验签,所以user_id有被篡改的可能。
  3. 此模块经受过时间的考验,目前没有因为不兼容导致此中间件报错的情况,每个项目值得拥有。
  4. json精度问题: 前后端分离的架构,大数据在传参时时使用json会产生精度误差问题,导致日志记录不精确,如下:

php

复制代码// [1.2345678912345678e+17] echo json_encode([123456789123456789.123456789123456789]); //Array ( [0] => 1.2345678912346E+17 ) print_r(json_decode('[123456789123456789.123456789123456789]', true));

这个是编程语言层面的问题,所以在传输大数据时一定要转化为字符串去解决精度问题。

php

复制代码// ["123456789123456789.123456789123456789"] echo json_encode(["123456789123456789.123456789123456789"]); //Array ( [0] => 123456789123456789.123456789123456789) print_r(json_decode('["123456789123456789.123456789123456789"]', true));

正文完
可以使用微信扫码关注公众号(ID:xzluomor)
post-qrcode
 0
评论(没有评论)

文心AIGC

2023 年 10 月
 1
2345678
9101112131415
16171819202122
23242526272829
3031  
文心AIGC
文心AIGC
人工智能ChatGPT,AIGC指利用人工智能技术来生成内容,其中包括文字、语音、代码、图像、视频、机器人动作等等。被认为是继PGC、UGC之后的新型内容创作方式。AIGC作为元宇宙的新方向,近几年迭代速度呈现指数级爆发,谷歌、Meta、百度等平台型巨头持续布局
文章搜索
热门文章
潞晨尤洋:日常办公没必要上私有模型,这三类企业才需要 | MEET2026

潞晨尤洋:日常办公没必要上私有模型,这三类企业才需要 | MEET2026

潞晨尤洋:日常办公没必要上私有模型,这三类企业才需要 | MEET2026 Jay 2025-12-22 09...
面向「空天具身智能」,北航团队提出星座规划新基准丨NeurIPS’25

面向「空天具身智能」,北航团队提出星座规划新基准丨NeurIPS’25

面向「空天具身智能」,北航团队提出星座规划新基准丨NeurIPS’25 鹭羽 2025-12-13 22:37...
5天连更5次,可灵AI年末“狂飙式”升级

5天连更5次,可灵AI年末“狂飙式”升级

5天连更5次,可灵AI年末“狂飙式”升级 思邈 2025-12-10 14:28:37 来源:量子位 让更大规...
钉钉又发新版本!把 AI 搬进每一次对话和会议

钉钉又发新版本!把 AI 搬进每一次对话和会议

钉钉又发新版本!把 AI 搬进每一次对话和会议 梦晨 2025-12-11 15:33:51 来源:量子位 A...
商汤Seko2.0重磅发布,合作短剧登顶抖音AI短剧榜No.1

商汤Seko2.0重磅发布,合作短剧登顶抖音AI短剧榜No.1

商汤Seko2.0重磅发布,合作短剧登顶抖音AI短剧榜No.1 十三 2025-12-15 14:13:14 ...
最新评论
ufabet ufabet มีเกมให้เลือกเล่นมากมาย: เกมเดิมพันหลากหลาย ครบทุกค่ายดัง
tornado crypto mixer tornado crypto mixer Discover the power of privacy with TornadoCash! Learn how this decentralized mixer ensures your transactions remain confidential.
ดูบอลสด ดูบอลสด Very well presented. Every quote was awesome and thanks for sharing the content. Keep sharing and keep motivating others.
ดูบอลสด ดูบอลสด Pretty! This has been a really wonderful post. Many thanks for providing these details.
ดูบอลสด ดูบอลสด Pretty! This has been a really wonderful post. Many thanks for providing these details.
ดูบอลสด ดูบอลสด Hi there to all, for the reason that I am genuinely keen of reading this website’s post to be updated on a regular basis. It carries pleasant stuff.
Obrazy Sztuka Nowoczesna Obrazy Sztuka Nowoczesna Thank you for this wonderful contribution to the topic. Your ability to explain complex ideas simply is admirable.
ufabet ufabet Hi there to all, for the reason that I am genuinely keen of reading this website’s post to be updated on a regular basis. It carries pleasant stuff.
ufabet ufabet You’re so awesome! I don’t believe I have read a single thing like that before. So great to find someone with some original thoughts on this topic. Really.. thank you for starting this up. This website is something that is needed on the internet, someone with a little originality!
ufabet ufabet Very well presented. Every quote was awesome and thanks for sharing the content. Keep sharing and keep motivating others.
热评文章
读懂2025中国AI走向!公司×产品×人物×方案,最值得关注的都在这里了

读懂2025中国AI走向!公司×产品×人物×方案,最值得关注的都在这里了

读懂2025中国AI走向!公司×产品×人物×方案,最值得关注的都在这里了 衡宇 2025-12-10 12:3...
5天连更5次,可灵AI年末“狂飙式”升级

5天连更5次,可灵AI年末“狂飙式”升级

5天连更5次,可灵AI年末“狂飙式”升级 思邈 2025-12-10 14:28:37 来源:量子位 让更大规...
戴尔 x OpenCSG,推出⾯向智能初创企业的⼀体化 IT 基础架构解决方案

戴尔 x OpenCSG,推出⾯向智能初创企业的⼀体化 IT 基础架构解决方案

戴尔 x OpenCSG,推出⾯向智能初创企业的⼀体化 IT 基础架构解决方案 十三 2025-12-10 1...
九章云极独揽量子位三项大奖:以“一度算力”重构AI基础设施云格局

九章云极独揽量子位三项大奖:以“一度算力”重构AI基础设施云格局

九章云极独揽量子位三项大奖:以“一度算力”重构AI基础设施云格局 量子位的朋友们 2025-12-10 18:...
乐奇Rokid这一年,一路狂飙不回头

乐奇Rokid这一年,一路狂飙不回头

乐奇Rokid这一年,一路狂飙不回头 梦瑶 2025-12-10 20:41:15 来源:量子位 梦瑶 发自 ...