欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 财经 > 金融 > 纯干货!基于monolog增强laravel框架的日志系统

纯干货!基于monolog增强laravel框架的日志系统

2025/9/17 17:40:08 来源:https://blog.csdn.net/qq_39962403/article/details/142911420  浏览:    关键词:纯干货!基于monolog增强laravel框架的日志系统

背景

我们现在有一个项目,仅针对request请求增加了request-id,但是对于命令行脚本没有request-id,基于此,我重新梳理了一下laravel的Log系统并重新做了优化。

什么是日志

从数据层面来看,应用程序是一个将数据状态转换为另一种数据状态的过程。而日志,就是记录这个数据每一次的流转过程。日志是数据流转过程可视化的一种方式。在编程中,日志应该被放在优先级列表的首位。
日志信息可以包括程序的状态、错误消息、警告、调试信息等等。
日志通常被记录到文件中,但也可以发送到其他目的地,比如控制台或远程服务器。我们一般要记录的日志,分为三大块,业务日志,异常日志和数据库日志。

为什么使用日志

我们知道了什么是日志,日志的内容,那么我们为什么要使用日志呢?我认为主要有三点,第一个是当数据状态流转出现问题的时候,即业务处理失败了,我们需要知道哪一步处理出了问题,第二点是业务处理成功了,但是我们需要追溯数据、校验数据的准确性的时候,需要用到日志。第三点是基于日志的数据分析,可以把日志当做埋点,统计业务数据。

日志库monolog

Laravel的日志组件默认是使用 Seldaek/monolog 去实现,Monolog是一个高度灵活和流行的PHP日志库,Monolog 官方有提供 RedisHandler、MongoDBHandler、ElasticsearchHandler 等等,我们可以通过指定Handler 去改变日志处理的方式,将日志发送到文件,套接字,数据库,和其他网络服务。

使用日志

第一次使用monolog

官方给了一个基础的使用示范

    public function testLog(){// create a log channel$log = new Logger('name');$log->pushHandler(new StreamHandler('path/to/your.log', Logger::WARNING));// add records to the log$log->warning('Foo');$log->error('Bar');}

执行完毕之后,发现在执行目录下生成了对应的日志文件
在这里插入图片描述
但是我们前面说过,laravel是默认使用monolog的,能不能使用faced模式来记录日志呢,我们修改一下这段代码

    public function testLog(){Log::info('test log');}

果然,报错了,

RuntimeException : A facade root has not been set.

为什么facade不能用呢,查看Test类发现并没有初始化laravel框架,框架初始化我是知道的,在public/index.php里面就实现了对框架的初始化,核心代码就是$app = require_once __DIR__.'/../bootstrap/app.php';,很明显,我引入这个文件,就完成了框架的初始化。
在这里插入图片描述
于是我搜索了一下,看看有几个地方会实现框架的初始化,我发现一共有三个文件会初始化框架,一个是入口文件index.php,一个是artisan,laravel提供的命令行工具,还有一个是Tests文件

<?phpnamespace Tests;use Illuminate\Contracts\Console\Kernel;trait CreatesApplication
{/*** Creates the application.** @return \Illuminate\Foundation\Application*/public function createApplication(){$app = require __DIR__.'/../bootstrap/app.php';$app->make(Kernel::class)->bootstrap();return $app;}
}

原来这是一个trait,看样子就是给phpunit使用的,用来初始化框架,太好了。于是,我引入这个trait,并在__construct中实现框架初始化

use Tests\CreatesApplication;class RequestTest extends TestCase
{use CreatesApplication;public function __construct(){parent::__construct();$this->createApplication();}public function testLog(){Log::info('test log');}
}

解决了框架没有初始化的问题。

日志的配置文件

  • laravel官方文档
    那么框架的日志保存到哪里去了呢?通过阅读官方文档,我们得知配置文件在config/logging.php文件中。

  • 日志驱动类型
    在这里插入图片描述
    默认使用的是stack日志堆栈,我们看到,stack支持将日志发给多个channel。里面的path就是日志的生成路径,这里修改一下日志的存储目录

'channels' => ['stack' => ['driver' => 'stack','channels' => ['single'],'ignore_exceptions' => false,],'daily' => ['driver' => 'daily','tap' => [CustomizeFormatter::class],'path' => config('common.log_dir').'/laravel.log','level' => 'debug','days' => 14,],'sql' => ['driver' => 'daily','tap' => [CustomizeFormatter::class],'path' => config('common.log_dir').'/sql.log','level' => 'debug','days' => 14,],
],

日志的类型

在这里插入图片描述

上下文信息

我们知道,记录日志最重要的一项就是关联的请求 ID,不能只记录一个简单的文本

[2024-10-15 15:49:41] local.INFO: test log  

那么这里我们就可以使用Log的上下文信息来实现这个功能。我们现在来实现一下。

添加request-id

我们添加request-id的时候,要考虑到不仅仅只支持request,而且要支持命令行工具,而且我们要指定日志的格式,所以我们使用tab来配置Monolog的实现,并在里面配置。在官方文档上,我们看到,添加extra-data的方式有两种,根据官方文档的说明,我们在logging.php中这样配置

        'daily' => ['driver' => 'daily','tap' => [\App\Lib\CustomizeFormatter::class],'path' => storage_path('logs/laravel.log'),'level' => 'debug','days' => 14,],
  • 并新建CustomizeFormatter.php文件
    /*** 自定义给定的日志记录器实例。*/public function __invoke(Logger $logger): void{$requestId = GenerateRequestId::getInstance()->requestId;foreach ($logger->getHandlers() as $handler) {$handler->pushProcessor(function ($record) use ($requestId) {//对应log的数组$record['context']['request_id'] = $requestId;return $record;});$handler->setFormatter(new LineFormatter("[%datetime%] %channel%.%level_name%: %message% %context% %extra% \n", "Y-m-d H:i:s"));}}
Adding extra data in the records

我们前面的pushProcessor是基于logger添加extra-data的方法,pushProcessor即在进程中添加extra-data。
在这里插入图片描述

  • 我们执行一下测试代码
   public function testLog(){Log::info('this is test1');Log::info('this is test2');}

查看日志文件发现两个问题,第一个是格式问题,所有的log都被组装成了一条,很不清晰,第二个是同一个请求的requestId不一致。

[2024-10-15T17:21:48.287502+08:00] local.INFO: this is test1 [] {"requestId":"0d5dc39c-ff74-4e43-9f15-18474991f0a4"}[2024-10-15T17:21:48.295217+08:00] local.INFO: this is test2 [] {"requestId":"cc11effa-0e79-4c52-bcad-5b699180b704"}

我们接下来优化一下,针对第一个问题,我们在setFormatter的时候加一个换行符即可,针对第二个问题,我们可以在每次实例化的时候只初始化一次request-id即可

<?phpnamespace App\Lib;use Illuminate\Log\Logger;
use Illuminate\Support\Str;
use Monolog\Formatter\LineFormatter;class CustomizeFormatter
{/*** 自定义给定的日志记录器实例。*/public function __invoke(Logger $logger): void{//增加请求ID$requestId=Str::uuid()->toString();foreach ($logger->getHandlers() as $handler) {$handler->pushProcessor(function ($record)use($requestId) {$record['extra']['requestId'] =$requestId ;return $record;});$handler->setFormatter(new LineFormatter("[%datetime%] %channel%.%level_name%: %message% %context% %extra% \n"));}}
}

优化后,我们执行一版并查询日志

[2024-10-15T17:36:54.685797+08:00] local.INFO: this is test1 [] {"requestId":"e45be761-289b-413b-940c-bcc36fa1b9a9"} 
[2024-10-15T17:36:54.686320+08:00] local.INFO: this is test2 [] {"requestId":"e45be761-289b-413b-940c-bcc36fa1b9a9"} 

添加request-id成功,最后,我们优化一下时间格式

<?phpnamespace App\Lib;use Illuminate\Log\Logger;
use Monolog\Formatter\LineFormatter;class CustomizeFormatter
{/*** 自定义给定的日志记录器实例。*/public function __invoke(Logger $logger): void{$requestId = GenerateRequestId::getInstance()->requestId;foreach ($logger->getHandlers() as $handler) {$handler->pushProcessor(function ($record) use ($requestId) {//如果有request_id 不在重新生成if (empty($record['context']['request_id'])){$record['context']['request_id'] = $requestId;}return $record;});$handler->setFormatter(new LineFormatter("[%datetime%] %channel%.%level_name%: %message% %context% %extra% \n", "Y-m-d H:i:s"));}}
}

日志输出

[2024-10-15 17:42:55] local.INFO: this is test1 [] {"requestId":"28c06563-40ac-4b18-bb05-13101859fe58"} 
[2024-10-15 17:42:55] local.INFO: this is test2 [] {"requestId":"28c06563-40ac-4b18-bb05-13101859fe58"} 

丰富日志

针对一次请求的话,我们不能简单的记录请求路径和requestId

[2024-10-15 18:06:09] local.INFO: GET http://acurd1.com/blog/list [] [] {"requestId":"8a95c1f2-9484-4e27-8afe-c302de3c56de"} 

一个完整的request请求我们要记录request和response信息。那么下面我们封装一下日志。

使用中间件记录request和response日志

  • 创建一个中间件 php artisan make:middleware AccessLog,将这个中间件注册到Http模块的Kenel.php中,全局注册。
  • AccessLog.php
<?phpnamespace App\

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词