概述

Monolog 是一个流行的 PHP 日志记录库,它提供了强大的功能来帮助开发者在应用程序中进行日志记录。Monolog 支持将日志消息发送到多种目的地,包括文件、套接字、电子邮件、数据库以及其他各种 Web 服务。它实现了 PSR-3 日志接口,这意味着它与遵循该标准的其他日志库兼容,提供了一致的日志记录方法。

核心概念

  • 通道(Channels):每个 Monolog 日志记录器实例都有一个或多个通道,每个通道都有一个名称,用于区分不同类型的日志消息。
  • 处理器(Handlers):处理器负责实际的日志消息处理,例如将消息写入文件、发送到电子邮件或存储到数据库中。一个日志记录器可以有多个处理器,它们形成一个堆栈,日志消息会按顺序通过这些处理器。
  • 格式化器(Formatters):格式化器用于定义日志消息的输出格式。Monolog 提供了多种内置格式化器,如 LineFormatter 将日志格式化为单行字符串,JsonFormatter 将日志编码为 JSON 格式等。
  • 处理器(Processors):处理器可以修改日志记录或添加额外的数据,例如添加请求的 URI、用户信息等。

安装

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 提供了两种不同的方式来为简单的文本消息增加额外的信息

1. 使用上下文(context)

第一种方式是使用上下文(context),这允许你在传递记录的时候传递一个数组格式的数据

// ④ 添加额外的数据:1、使用上下文(context)
$logger->warning('④ 添加额外的数据:1、使用上下文(context): ',['username' => 'Tinywan']);

简单的处理器(比如StreamHandler)将只是把数组转换成字符串。而复杂的处理器则可以利用上下文的优点(如 FirePHP 则将以一种优美的方式显示数组)。

2. 使用加工程序(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-cascadeopen in new window。 它可以帮你使用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: CSRF1 [] []
[2022-04-09T09:06:46.285166+00:00] security.DEBUG: CSRF2 [] []
[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 格式写入记录

// ① 创建一个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 5424open in new window中的日志级别:

  • 调试 DEBUG (100): 详细的调试信息。
  • 信息 INFO (200): 有意义的事件,比如用户登录、SQL日志。
  • 提示 NOTICE (250): 正常但是值得注意的事件。
  • 警告 WARNING (300): 异常事件,但是并不是错误。比如使用了废弃了的API,错误地使用了一个API,以及其他不希望发生但是并非必要的错误。
  • 错误 ERROR (400): 运行时的错误,不需要立即注意到,但是需要被专门记录并监控到。
  • 严重 CRITICAL (500): 边界条件/危笃场景。比如应用组件不可用了,未预料到的异常。
  • 警报 ALERT (550): 必须立即采取行动。比如整个网站都挂了,数据库不可用了等。这种情况应该发送短信警报,并把你叫醒。
  • 紧急 EMERGENCY (600): 紧急请求:系统不可用了。

使用清单

常用 Handler

Monolog内置很多很实用的handler,它们几乎囊括了各种的使用场景,这里介绍一些使用的

  • StreamHandler:把记录写进PHP流,主要用于日志文件。
  • SyslogHandler:把记录写进syslog。
  • ErrorLogHandler:把记录写进PHP错误日志。
  • NativeMailerHandler:使用PHP的mail()函数发送日志记录。
  • SocketHandler:通过socket写日志。
  • AmqpHandler:把记录写进兼容amqp协议的服务。
  • BrowserConsoleHandler:把日志记录写到浏览器的控制台。由于是使用浏览器的console对象,需要看浏览器是否支持。
  • RedisHandler:把记录写进Redis。
  • MongoDBHandler:把记录写进Mongo。
  • ElasticSearchHandler:把记录写到ElasticSearch服务。
  • BufferHandler:允许我们把日志记录缓存起来一次性进行处理。

常用 Processor

前面说过,Processor可以为日志记录添加额外的信息,Monolog也提供了一些很实用的processor

  • IntrospectionProcessor:增加当前脚本的文件名和类名等信息
  • WebProcessor:增加当前请求的URI、请求方法和访问IP等信息
  • MemoryUsageProcessor:增加当前内存使用情况信息
  • MemoryPeakUsageProcessor:增加内存使用高峰时的信息

常用 Formatter

同样的,这里介绍几个自带的Formatter:

  • LineFormatter:把日志记录格式化成一行字符串。
  • HtmlFormatter:把日志记录格式化成HTML表格,主要用于邮件。
  • JsonFormatter:把日志记录编码成JSON格式。
  • LogstashFormatter:把日志记录格式化成logstash的事件JSON格式。
  • ElasticaFormatter:把日志记录格式化成ElasticSearch使用的数据格式。

核心概念

每一个日志服务实例 (Logger) 都有一个通道(名称),并有一个处理器 (Handler)栈. 无论何时你添加一条 记录 到对应的日志服务实例,这个处理器栈将被遍历一遍:每个处理器都将依次决定是否要处理这条记录,而如果要处理,则遍历结束(译注:类似DOM事件冒泡)。

这样子可以创建非常灵活的日志配置。比如一个 StreamHandler 可以把所有日志都写入磁盘,而上面加个MailHandler 可以把错误日志作为邮件发送出去。处理器还有一个 $bubble 属性定义了是否屏蔽某条记录或者处理了某条记录。在这个示例中,配置 MailHandler 的 $bubble 参数为 false 则意味着 MailHandler 将不会把自己已处理过的记录继续冒泡给 StreamHandler.

你可以创建许多日志服务实例(Logger),每一个则定义一个通道(比如数据库、请求、路由...)。而每一个日志服务实例都可以组合各种各样的处理器,可以共享处理器也可以不共享。这个通道将会在日志中反映出来,从而允许你可以很容易地查看或者筛选记录。

每一个处理还会有一个格式化器(Formatter)。如果你没有配置一个,则一个有意义的默认的格式化器将被创建。格式化器用来规范化并格式化输入的记录,以便处理器能输出一些有用的信息。

不支持自定义的严重性级别。只支持使用RFC 5424中定义的八个级别(调试/Debug、信息/Info、提示/Notice、警告/Warning、错误/Error、严重/Critical、警报/Alert、紧急/Emergency)来作为基本的筛选目的。不过,如果为了排序或者其他需要灵活性的使用场景,你可以添加加工程序(Processor)从而可以在(处理器)处理前添加额外的信息(标签、用户IP...)。

Last Updated:
贡献者: Tinywan