Laravel 的日志系统 - monolog
8

日志的重要程度不言而喻, 不管是在开发过程中, 还是部署到生产环境后, 都是经常使用的.
随着 psr-3 的出现, 终于统一了 php 中日志的风格.
但是, 好用的记录日志系统, 也很重要.
monolog 是我遇到的最好的日志系统.
而且, laravel 中也是用的 monolog.

参考

psr-3

github.com/Seldaek/monolog

phpfudao 这可能是php世界中最好的日志库——monolog

Using Monolog

安装 (Install)

通过 composer 或者 github.

composer require monolog/monolog

文档

核心概念 (Core Concepts)

Every Logger instance has a channel (name) and a stack of handlers. Whenever you add a record to the logger, it traverses the handler stack. Each handler decides whether it fully handled the record, and if so, the propagation of the record ends there.

This allows for flexible logging setups, for example having a StreamHandler at the bottom of the stack that will log anything to disk, and on top of that add a MailHandler that will send emails only when an error message is logged. Handlers also have a $bubble property which defines whether they block the record or not if they handled it. In this example, setting the MailHandler's $bubble argument to false means that records handled by the MailHandler will not propagate to the StreamHandler anymore.

You can create many Loggers, each defining a channel (e.g.: db, request, router, ..) and each of them combining various handlers, which can be shared or not. The channel is reflected in the logs and allows you to easily see or filter records.

Each Handler also has a Formatter, a default one with settings that make sense will be created if you don't set one. The formatters normalize and format incoming records so that they can be used by the handlers to output useful information.

Custom severity levels are not available. Only the eight RFC 5424 levels (debug, info, notice, warning, error, critical, alert, emergency) are present for basic filtering purposes, but for sorting and other use cases that would require flexibility, you should add Processors to the Logger that can add extra information (tags, user ip, ..) to the records before they are handled.

每一个日志服务实例 (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...)。

给日志添加额外的信息

using the logging context

The first way is the context, allowing to pass an array of data along the record:

<?php
$logger->info('Adding a new user', array('username' => 'Seldaek'));

Simple handlers (like the StreamHandler for instance) will simply format the array to a string but richer handlers can take advantage of the context (FirePHP is able to display arrays in pretty way for instance).

using processors

The second way is to add extra data for all records by using a processor. Processors can be any callable. They will get the record as parameter and must return it after having eventually changed the extra part of it. Let's write a processor adding some dummy data in the record:

<?php

$logger->pushProcessor(function ($record) {
   $record['extra']['dummy'] = 'Hello world!';

   return $record;
});

Monolog provides some built-in processors that can be used in your project. Look at the dedicated chapter for the list.

Tip: processors can also be registered on a specific handler instead of the logger to apply only for this handler.

  • 使用上下文(context)

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

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

  • 使用加工程序(Processor)

第二种方式是使用加工程序来为所有的记录添加额外数据。加工程序可以是任何可以调用的函数。

加工程序接收日志记录作为参数,并且需要在修改了extra字段后再返回日志记录。

Monolog提供了一些内置的加工程序,你可以在你的项目中使用它们.

小技巧:加工程序可以被注册到一个特定的处理器上而不是直接在日志服务实例上,从而可以只在对应的处理器上生效.

mongolog中几个重要的概念

handler 日志处理器

存放 handler 的数据结构是一个“栈”,一个日志实例可以有多个 handler,通过 Logger 实例的 pushHandler 方法压入一个 handler,该方法接受一个 HandlerInterface 类型的参数.

如果你设置了多个 handler,当你新增一条日志的时候,他会从栈顶开始往下传播,关心这个级别日志的 handler 将会处理这条日志.

所有的 handler 都会继承 AbstractProcessingHandler 这个抽象类,并且只需要实现里面的抽象方法 write 就可以了.

同时这个抽象类会继承 AbstractHandler 这个抽象类,这个抽象类的构造函数有两个参数:levelbubble.

level:表示该 handler 关心的最低日志级别,是个整型.

bubble:表示日志被当前 handler 处理后是否接着向下传递.

formatter 设置日志格式

每个 handler 可以单独设置记录的日志格式,AbstractHandler 抽象类中有一个 setFormatter 方法,该参数接受一个 FormatterInterface 类型的参数.

可以看到 monolog 自带的 formatter 都继承自 ormalizerFormatter,该类实现了 format和formatBatch 方法。

processor 日志加工程序,用来给日志添加额外信息

存放 processor 的结构也是一个“栈”,意味着你也可以通过 pushProcessor 方法给一个 Logger 实例配置多个 processor

我们注意到,这里 pushProcesso 接受一个 callable ,也就是需要一个函数或者类方法,但是官方自带的这些 processor 都是类,

随便点进去一个源码就会发现,其实这些类都用到了 __invoke 魔术方法,所以在被当做 callable 调用的时候会自动调用 __invoke.

代码实践

代码在我的test仓库中可以下载.

github 测试代码

<?php
/**
 * monolog package usage
 * User: qiuyu
 * Date: 2018/2/14
 * Time: 下午1:44
 */

require dirname(__FILE__)."/../vendor/autoload.php";

use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Processor\UidProcessor;
use Monolog\Processor\ProcessIdProcessor;
use Monolog\Formatter\JsonFormatter;

// 实例化一个日志实例, 参数是 channel name
$logger = new Logger('qiuyuhome');

// StreamHandler_1
$streamHander1 = new StreamHandler(__DIR__.'/testLog1.log', Logger::INFO);
// 设置日志格式为json
$streamHander1->setFormatter(new JsonFormatter());
// 入栈, 往 handler stack 里压入 StreamHandler 的实例
$logger->pushHandler($streamHander1);

// StreamHandler_2
// 如果第三个参数为false, 则只会执行这个一个Handler. 默认是true
$streamHander2 = new StreamHandler(__DIR__.'/testLog2.log', Logger::INFO);
// 入栈, 往 handler stack 里压入 StreamHandler 的实例
$logger->pushHandler($streamHander2);

/**
 * processor 日志加工程序,用来给日志添加额外信息.
 *
 * 这里调用了内置的 UidProcessor 类和 ProcessIdProcessor 类.
 * 在生成的日志文件中, 会在最后面显示这些额外信息.
 */
$logger->pushProcessor(new UidProcessor());
$logger->pushProcessor(new ProcessIdProcessor());
$logger->pushProcessor(function ($record) {
    $record['message'] = 'Hello ' . $record['message'];
    return $record;
});

/**
 * 设置记录到日志的信息.
 *
 * 开始遍历 handler stack.
 * 先入后出, 后压入的最先执行. 所以先执行 FirePHPHandler, 再执行 StreamHandler
 * 如果设置了 ErrorLogHandler 的 $bubble = false, 会停止冒泡, StreamHandler 不会执行.
 * 第二个参数为数组格式, 通过使用使用上下文(context)添加了额外的数据.
 * 简单的处理器(比如StreamHandler)将只是把数组转换成字符串。而复杂的处理器则可以利用上下文的优点(如 FirePHP 则将以一种优美的方式显示数组).
 */
$logger->info('Welcome to QiuYu Blog.', ['username' => 'QiuYu']);

运行结果

运行文件的同目录下, 会生成 testLog1.logtestLog2.log 日志文件. 内容为:

testLog1.log

{"message":"Hello Welcome to QiuYu Blog.","context":{"username":"QiuYu"},"level":200,"level_name":"INFO","channel":"qiuyuhome","datetime":{"date":"2018-02-14 23:33:28.928112","timezone_type":3,"timezone":"Asia/Shanghai"},"extra":{"process_id":21520,"uid":"54a8a7d"}}

testLog2.log

[2018-02-14 23:33:28] qiuyuhome.INFO: Hello Welcome to QiuYu Blog. {"username":"QiuYu"} {"process_id":21520,"uid":"54a8a7d"}

当才华还支持不起理想时,就应该静下心来好好学习了。

《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
讨论数量: 1

收藏mark

3个月前

  • 请注意单词拼写,以及中英文排版,参考此页
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`, 更多语法请见这里 Markdown 语法
  • 支持表情,使用方法请见 Emoji 自动补全来咯,可用的 Emoji 请见 :metal: :point_right: Emoji 列表 :star: :sparkles:
  • 上传图片, 支持拖拽和剪切板黏贴上传, 格式限制 - jpg, png, gif
  • 发布框支持本地存储功能,会在内容变更时保存,「提交」按钮点击时清空
  请勿发布不友善或者负能量的内容。与人为善,比聪明更重要!
PHP组长 @ 北京众晶锐驰科技有限公司
文章
1
粉丝
3
喜欢
8
收藏
2
排名:288
访问:2070
私信