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

为何强大

  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强大的日志渠道隔离和按天切割功能,使得记录日志过程更加强大。

效果示例

[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":[]}

部署

#在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
//在app/Http/Kernel.php文件的protected $middleware数组中追加一行,用于注册全局中间件
\App\Http\Middleware\RequestMiddleware::class

编写

<?phpnamespace 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会产生精度误差问题,导致日志记录不精确,如下:
// [1.2345678912345678e+17]
echo json_encode([123456789123456789.123456789123456789]);
//Array ( [0] => 1.2345678912346E+17 )
print_r(json_decode('[123456789123456789.123456789123456789]', true));

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/118838.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

从0到1配置TensorRT环境

根据博文&#xff1a;TensorFlow2.x模型转onnx、TensorRT给出的环境来配置。 以下是该博文中给出的版本信息 TensorFlow 2.4 CUDA 11.1 CUDNN 8 TensorRT 8.2.1.8 tf2onnx 1.13.0 onnx 1.12.0 下载地址 包下载地址TensorRT 8.2.1.8https://developer.nvidia.com/nvidia-tenso…

Lua 事件触发机制(注册,触发)

日常工作中经常会用到触发机制&#xff0c;这里就提供一个注册触发机制&#xff0c;在代码中在也不用专门去调用各个模块的接口&#xff1b;只需要触发即可&#xff0c;触发后会自动调用接口 直接上代码 local _EventHandle {}; _EventHandle.listenerHandleIndex 0 _EventH…

【Overload游戏引擎细节分析】standard材质Shader

提示&#xff1a;Shader属于GPU编程&#xff0c;难写难调试&#xff0c;阅读本文需有一定的OpenGL基础&#xff0c;可以写简单的Shader&#xff0c;不适合不会OpenGL的朋友 一、Blinn-Phong光照模型 Blinn-Phong光照模型&#xff0c;又称为Blinn-phong反射模型&#xff08;Bli…

腾讯云抱歉不满足产品首购条件解决方法

购买腾讯云服务器提示“抱歉&#xff0c;检测到相同实名认证主体已购买&#xff0c;不满足产品首购条件&#xff0c;您可了解其他商品。”&#xff0c;购买腾讯云特价云服务器是有新用户首购限制的&#xff0c;说当前腾讯云认证主体的其他账号已经购买过云服务器&#xff0c;一…

【wespeaker】模型ECAPA_TDNN介绍

本次主要介绍开源项目wespeaker模型介绍 1. 模型超参数 model_args: feat_dim: 80 embed_dim: 192 pooling_func: “ASTP” projection_args: project_type: “softmax” # add_margin, arc_margin, sphere, softmax scale: 32.0 easy_margin: False 2. 模型结构 2.1 Layer…

【Python机器学习】零基础掌握FeatureHasher特征提取

如何高效地处理海量特征数据? 在大数据和机器学习的时代,处理海量的数据特征是一个常见但棘手的问题。特别是在文本分析、社交媒体挖掘或电子商务推荐系统中,数据维度经常会非常高。那么,如何在不损失太多信息的情况下,高效地处理这些高维数据呢? 想象一下,一个社交媒…

node-red常用包分析

node-red-contrib-opcua Use OpcUa-Item to define variables. Use OpcUa-Client to read / write / subscribe / browse OPC UA server. 需要想通过OpcUa-Item节点来指定一个数据点。 触发器-->opcua_item----->opcua_client opcua_client的Action项解析&#xff1a; …

【前端设计模式】之抽象工厂模式

抽象工厂模式是一种创建型设计模式&#xff0c;它提供了一种创建一系列相关或相互依赖对象的接口&#xff0c;而无需指定具体类。在前端开发中&#xff0c;抽象工厂模式可以帮助我们更好地组织和管理代码&#xff0c;提高代码的可维护性和可扩展性。 抽象工厂模式特性 抽象工…

HugeGraph Hubble 配置 https 协议的操作步骤

背景 HugeGraph 图数据库的 Server 端支持 https 配置&#xff0c;官方文档中有说明相对比较容易&#xff0c;而 Hubble 部署过程都是 http的。 我们有一个应用要嵌入 hubble 页面&#xff0c;而且部署为 https &#xff0c;那么 Hubble 是否支持配置 https 呢&#xff1f;网…

大数据技术学习笔记(三)—— Hadoop 的运行模式

目录 1 本地模式2 伪分布式模式3 完全分布式模式3.1 准备3台客户机3.2 同步分发内容3.2.1 分发命令3.2.2 执行分发操作 3.3 集群配置3.3.1 集群部署规划3.3.2 配置文件说明3.3.3 修改配置文件3.3.4 分发配置信息 3.4 SSH无密登录配置3.4.1 配置ssh3.4.2 无密钥配置 3.5 单点启动…

python的多线程介绍之thread

python的多线程介绍之thread Python的thread模块是Python标准库中的一个模块&#xff0c;用于创建和管理线程。它是Python早期支持多线程的方式之一&#xff0c;但在Python 2.4之后&#xff0c;推荐使用threading模块来实现多线程。 以下是thread模块的一些基本功能和用法&…

随笔 | 写在剑桥学习一个月的这一天

目录 来剑桥的第一天各种意外正式的冒险社恐直接表达放轻松&#xff0c;勇敢面对后记. 来剑桥的第一天 来剑桥的第一天&#xff0c;还是那么印象深刻。 那是来英国的第二天&#xff0c;伦敦的天气还行&#xff0c;偶尔多云偶尔天晴。 和他纠结半天&#xff0c;最终还是选择了…

C++【多态】

文章目录&#xff1a; C 多态1. 多态的概念2. 多态的定义和实现2.1 构成多态的必要条件2.2 虚函数和重写2.3 虚函数重写的两个例外2.4 例题运用2.5 final 和 override2.6 重载、重写、重定义 3. 抽象类4. 多态的原理4.1 虚表指针4.2 多态和非多态调用4.3 回想满足条件4.4 虚函数…

Selenium中WebDriver最新Chrome驱动安装教程

&#x1f60f;作者简介&#xff1a;博主是一位测试管理者&#xff0c;同时也是一名对外企业兼职讲师。 &#x1f4e1;主页地址&#xff1a;【Austin_zhai】 &#x1f646;目的与景愿&#xff1a;旨在于能帮助更多的测试行业人员提升软硬技能&#xff0c;分享行业相关最新信息。…

【广州华锐互动】智能家居设计3D虚拟还原系统

随着科技的飞速发展&#xff0c;人们对家居生活的需求也在不断提高。智能家居作为一种新兴的生活方式&#xff0c;正逐渐成为现代人追求的理想居住环境。而智能家居设计3D虚拟还原系统&#xff0c;正是为了让人们更好地了解和体验智能家居带来的便捷与舒适&#xff0c;让未来生…

聚观早报 |2024年春节连休8天;RTE2023开幕

【聚观365】10月26日消息 2024年春节连休8天 RTE2023开幕 一加12首发“东方屏” 微软公布2024财年第一财季财报 Alphabet Q3业绩好于预期 2024年春节连休8天 国务院办公厅发布关于2024年部分节假日安排的通知。2024年春节&#xff0c;2月10日至17日放假调休&#xff0c;共…

1023 组个最小数

给定数字 0-9 各若干个。你可以以任意顺序排列这些数字&#xff0c;但必须全部使用。目标是使得最后得到的数尽可能小&#xff08;注意 0 不能做首位&#xff09;。例如&#xff1a;给定两个 0&#xff0c;两个 1&#xff0c;三个 5&#xff0c;一个 8&#xff0c;我们得到的最…

面向边缘场景的 PWA 实践

背景 随着5G技术的发展&#xff0c;物联网边缘侧主要应用于数据传输量大、安全要求高以及数据实时处理等行业与应用场景中。其中&#xff0c;边缘计算是一种分布式计算模式&#xff0c;其将计算资源和数据处理能力推向接近数据源的边缘设备&#xff0c;以减少延迟并提高响应速度…

信息系统架构的设计理论与实践

信息系统架构的设计理论与实践 信息系统架构概述 信息系统架构的定义和发展 信息系统架构的定义 骚戴理解&#xff1a;这里只要背定义即可 信息系统架构的发展 信息系统架构的分类&#xff08;集中式和分布式&#xff09; 集中式结构 分布式结构 信息系统常用的四种架构模型…

番外8.2---配置/管理硬盘

""" Step1&#xff1a;清楚磁盘、硬盘&#xff08;HDD&#xff09;、光驱的概念及是否具有包含关系。 Step2&#xff1a;硬件设备&#xff08;IDE、SCSI、SATA、NVMe、软驱等&#xff09;命名方式及在linux系统里对应的文件名称。 Step3&#xff1a;&#xff1…