Monolog 是一个流行的 PHP 日志记录库,它提供了强大的功能来帮助开发者在应用程序中进行日志记录。Monolog 支持将日志消息发送到多种目的地,包括文件、套接字、电子邮件、数据库以及其他各种 Web 服务。它实现了 PSR-3 日志接口,这意味着它与遵循该标准的其他日志库兼容,提供了一致的日志记录方法。
LineFormatter
将日志格式化为单行字符串,JsonFormatter
将日志编码为 JSON 格式等。Monolog 的安装通常通过 Composer 进行,使用以下命令即可安装到项目中:
composer require monolog/monolog
使用 Monolog 时,你可以创建一个或多个记录器实例,为每个实例配置不同的通道和处理器,以满足不同场景下的日志记录需求。Monolog 的灵活性和可扩展性使其成为 PHP 应用程序中进行日志记录的理想选择。
<?php
declare(strict_types=1);
use Monolog\Handler\FirePHPHandler;
use Monolog\Handler\StreamHandler;
use Monolog\Logger;
require_once dirname(__DIR__).'/vendor/autoload.php';
// ① 创建日志服务实例
$logger = new Logger('tinywan');
// ② 添加日志处理器
$logger->pushHandler(new StreamHandler('./test.log', Logger::WARNING));
$logger->pushHandler(new FirePHPHandler());
// ③ 添加日志记录
$logger->warning('③ 添加日志记录 ');
// 注意:由于StreamHandler的第二个参数是:Logger::WARNING,所以这条日志是不记录的
$logger->info('③ 添加日志记录 ');
// ④ 添加额外的数据:1、使用上下文(context)
$logger->warning('④ 添加额外的数据:1、使用上下文(context): ',['username' => 'Tinywan']);
// ④ 添加额外的数据:2、使用加工程序(Processor)
$logger->pushProcessor(function ($record){
$record['extra'] = [
'username' => 'Tinywan',
'age' => 24
];
return $record;
});
$logger->warning('④ 添加额外的数据:2、使用加工程序(Processor): ');
以上执行输出
[2022-04-09T08:43:14.860308+00:00] tinywan.WARNING: ③ 添加日志记录 [] []
[2022-04-09T08:43:14.861232+00:00] tinywan.WARNING: ④ 添加额外的数据:1、使用上下文(context): {"username":"Tinywan"} []
[2022-04-09T08:43:14.861272+00:00] tinywan.WARNING: ④ 添加额外的数据:2、使用加工程序(Processor): [] {"username":"Tinywan","age":24}
这个实例后将在代码中用到。唯一的参数是通道的名称,它在你有多个日志服务实例的时候很有用。
上面的代码中注册了两个处理器到栈中,以便允许使用两种不同的方式来处理日志记录。
添加顺序是:冒泡方式,按照栈【先进后出(First-In/Last-Out)】顺序添加。
StreamHandler
在栈的最底部,它会把记录都保存到硬盘上。注意:这个日志服务实例自己是不是知道如何处理一条日志记录的。它把记录代理给了一些处理器。
注意 FirePHPHandler 是被先调用的,因而它被添加到了栈顶。这允许你临时添加一个禁止冒泡的处理器从而允许你覆盖其他配置的日志(处理器)。
添加简单的文本消息
注意:由于StreamHandler的第二个参数是:
Logger::WARNING
,所以只会记录warning的日志,其他日志不会被记录。即:$logger->info('③ 添加日志记录 ');
不会被记录
为记录添加额外的数据。monolog 提供了两种不同的方式来为简单的文本消息增加额外的信息
第一种方式是使用上下文(context
),这允许你在传递记录的时候传递一个数组格式的数据
// ④ 添加额外的数据:1、使用上下文(context)
$logger->warning('④ 添加额外的数据:1、使用上下文(context): ',['username' => 'Tinywan']);
简单的处理器(比如StreamHandler
)将只是把数组转换成字符串。而复杂的处理器则可以利用上下文的优点(如 FirePHP 则将以一种优美的方式显示数组)。
Processor
)第二种方式是使用加工程序来为所有的记录添加额外数据。加工程序可以是任何可以调用的函数。
加工程序接收日志记录作为参数,并且需要在修改(设置)了extra
字段后,再返回日志记录。再次记录日志,则新日志会添加新的额外的日志。让我们来写一个添加一些假数据的加工程序
// ④ 添加额外的数据:2、使用加工程序(Processor)
$logger->pushProcessor(function ($record){
$record['extra'] = [
'username' => 'Tinywan',
'age' => 24
];
return $record;
});
$logger->warning('④ 添加额外的数据:2、使用加工程序(Processor): ');
Monolog提供了一些内置的加工程序,你可以在你的项目中使用它们。
[warning] 小技巧:加工程序可以被注册到一个特定的处理器上而不是直接在日志服务实例上,从而可以只在对应的处理器上生效。
如果你单独使用 Monolog, 并且在寻找一种简单的方式来配置许多处理器,那可以用 theorchard/monolog-cascade。 它可以帮你使用PHP数组、YAML或者JSON来构建复杂的日志配置。
通道是一种非常棒的方式来区分是应用的哪个部分的日志被记录下来的。这通常在大型项目中非常有用(而且被Symfony2的MonologBundle所使用)。
假设有两个日志服务实例共享了一个处理器,这个处理器将日志写入单个日志文件。通道则将允许你来区分是哪个日志服务实例记录了哪条日志。你可以很简单地通过通道来筛选日志。
use Monolog\Handler\FirePHPHandler;
use Monolog\Handler\StreamHandler;
use Monolog\Logger;
require_once dirname(__DIR__).'/vendor/autoload.php';
// ① 创建日志处理器
$stream = new StreamHandler('./test2.log', Logger::DEBUG);
$fire = new FirePHPHandler();
// ② 创建安全相关的日志服务通道
$securityLogger = new Logger('security');
$securityLogger->pushHandler($stream);
$securityLogger->pushHandler($fire);
// 添加日志记录
$securityLogger->debug('CSRF:1');
$securityLogger->debug('CSRF:2');
// ③ 创建支付相关的日志服务通道
$payLogger = new Logger('pay');
$payLogger->pushHandler($stream);
$payLogger->pushHandler($fire);
// 添加日志记录
$payLogger->debug('支付宝支付');
$payLogger->debug('微信支付');
// ④ 使用克隆方式创建培训相关通道
$trainLogger = $payLogger->withName('train');
$trainLogger->pushHandler($stream);
$trainLogger->pushHandler($fire);
// 添加日志记录
$trainLogger->debug('[克隆方式] 电商培训');
$trainLogger->debug('[克隆方式] 直播培训');
以上输出日志结果
[2022-04-09T09:06:46.284206+00:00] security.DEBUG: CSRF:1 [] []
[2022-04-09T09:06:46.285166+00:00] security.DEBUG: CSRF:2 [] []
[2022-04-09T09:06:46.285215+00:00] pay.DEBUG: 支付宝支付 [] []
[2022-04-09T09:06:46.285250+00:00] pay.DEBUG: 微信支付 [] []
[2022-04-09T09:06:46.285279+00:00] train.DEBUG: [克隆方式] 电商培训 [] []
[2022-04-09T09:06:46.285279+00:00] train.DEBUG: [克隆方式] 电商培训 [] []
[2022-04-09T09:06:46.285329+00:00] train.DEBUG: [克隆方式] 直播培训 [] []
[2022-04-09T09:06:46.285329+00:00] train.DEBUG: [克隆方式] 直播培训 [] []
在monolog中,可以很简单地来自定义日志的格式,无论是写入文件、套接字、邮件、数据库还是其他处理器。大多数处理器都是用
$record['formatted']
这个值来自动写入日志设备。
这个值依赖格式化器的配置。你可以选择预定义的格式化器类,也可以自己写一个(比如一个可读的多行文本文件)。
// 默认的日期格式是 "Y-m-d H:i:s"
$dateFormat = "Y-m-d H:i:s";
// 默认的输出格式是 "[%datetime%] %channel%.%level_name%: %message% %context% %extra%\n"
$output = "[%datetime%] [%channel%] [%level_name%] %message% %context% %extra% \n";
// ① 创建一个格式化器
$formatter = new LineFormatter($output, $dateFormat);
// ② 创建日志处理器
$stream = new StreamHandler('./test3.log', Logger::DEBUG);
$stream->setFormatter($formatter);
// ③ 创建安全相关的日志服务通道
$trainLogger = new Logger('train');
$trainLogger->pushHandler($stream);
// 添加日志记录
$trainLogger->debug('[克隆方式] 电商培训');
$trainLogger->debug('[克隆方式] 直播培训');
日志输出
[2022-04-09 09:17:10] [train] [DEBUG] [克隆方式] 电商培训 [] []
[2022-04-09 09:17:10] [train] [DEBUG] [克隆方式] 直播培训 [] []
可以在多个处理器之间复用同一个格式化器,并且在多个日志服务实例间共享这些处理器。
// ① 创建一个Json格式化器
$jsonFormatter = new JsonFormatter();
// ② 创建日志处理器
$stream = new StreamHandler('./test4.log', Logger::DEBUG);
$stream->setFormatter($jsonFormatter);
// ③ 创建安全相关的日志服务通道
$trainLogger = new Logger('train');
$trainLogger->pushHandler($stream);
// 添加日志记录
$trainLogger->debug('[克隆方式] 电商培训',[
'username' => 'Tinywan',
'age' => 24
]);
输出日志
{
"message": "[克隆方式] 电商培训",
"context": {
"username": "Tinywan",
"age": 24
},
"level": 100,
"level_name": "DEBUG",
"channel": "train",
"datetime": "2022-04-09T10:02:52.568338+00:00",
"extra": {}
}
Monolog 支持一下RFC 5424中的日志级别:
DEBUG (100)
: 详细的调试信息。INFO (200)
: 有意义的事件,比如用户登录、SQL日志。NOTICE (250)
: 正常但是值得注意的事件。WARNING (300)
: 异常事件,但是并不是错误。比如使用了废弃了的API,错误地使用了一个API,以及其他不希望发生但是并非必要的错误。ERROR (400)
: 运行时的错误,不需要立即注意到,但是需要被专门记录并监控到。CRITICAL (500)
: 边界条件/危笃场景。比如应用组件不可用了,未预料到的异常。ALERT (550)
: 必须立即采取行动。比如整个网站都挂了,数据库不可用了等。这种情况应该发送短信警报,并把你叫醒。EMERGENCY (600)
: 紧急请求:系统不可用了。Monolog内置很多很实用的handler,它们几乎囊括了各种的使用场景,这里介绍一些使用的
前面说过,Processor可以为日志记录添加额外的信息,Monolog也提供了一些很实用的processor
同样的,这里介绍几个自带的Formatter:
每一个日志服务实例 (Logger) 都有一个通道(名称),并有一个处理器 (Handler)栈. 无论何时你添加一条 记录 到对应的日志服务实例,这个处理器栈将被遍历一遍:每个处理器都将依次决定是否要处理这条记录,而如果要处理,则遍历结束(译注:类似DOM事件冒泡)。
这样子可以创建非常灵活的日志配置。比如一个 StreamHandler 可以把所有日志都写入磁盘,而上面加个MailHandler 可以把错误日志作为邮件发送出去。处理器还有一个
bubble 参数为 false 则意味着 MailHandler 将不会把自己已处理过的记录继续冒泡给 StreamHandler.
你可以创建许多日志服务实例(Logger),每一个则定义一个通道(比如数据库、请求、路由...)。而每一个日志服务实例都可以组合各种各样的处理器,可以共享处理器也可以不共享。这个通道将会在日志中反映出来,从而允许你可以很容易地查看或者筛选记录。
每一个处理还会有一个格式化器(Formatter)。如果你没有配置一个,则一个有意义的默认的格式化器将被创建。格式化器用来规范化并格式化输入的记录,以便处理器能输出一些有用的信息。
不支持自定义的严重性级别。只支持使用RFC 5424中定义的八个级别(调试/Debug、信息/Info、提示/Notice、警告/Warning、错误/Error、严重/Critical、警报/Alert、紧急/Emergency)来作为基本的筛选目的。不过,如果为了排序或者其他需要灵活性的使用场景,你可以添加加工程序(Processor)从而可以在(处理器)处理前添加额外的信息(标签、用户IP...)。