架构师 Neeke:https://www.zhihu.com/people/ciogao

对付 PHP 内置的那两个日志函数,也便是 error_log() 、 syslog() 来说,虽然功能强大并且性能极好,但是并没有缺点级别的设置,也没有固定的格式,更分不了模块记录。
而 monolog 、 log4php 这类的日志程序在性能上又多少略有缺憾。
正由于这些各种各样的缘故原由,Neeke 大佬就开拓了这个 SeasLog 扩展,为的便是办理上面这些日志干系系统的问题。

由于是我们国人开拓的,以是它的中文文档很友好,在 Gibhub 和官方文档中都有详细的中文文档解释,非常方便我们利用。
安装过程也和普通的 PHP 扩展没有差异,并不须要什么别的分外的软件支持。

seaslogphp进修懂得PHP中的SeasLog日记扩大 SQL

日志记录格式

我们先来理解一下 SeasLog 的日志记录格式。
它的日志格式模板是在 php.ini 文件中配置的,无法动态修正,须要配置 seaslog.default_template 这个选项,默认值是 "%T | %L | %P | %Q | %t | %M" 。
个中,%T 代表韶光、%L 代表日志级别、%P 代表进程ID 、%Q 代表要求ID 、%t 代表韶光戳、%M 代表日志信息。

当然,除了默认的这些参数之外 ,它还有一些别的参数,文档中解释得很详细,我们不才面的文章中也会再提到两个。
详细的内容大家可以自行在官方文档中查阅,这里就不多说了。

干系属性参数设置

除了一些须要在 php.ini 中配置的选项参数之外,SeasLog 还可以在程序运行时配置及获取一些配置信息。

日志根目录

// 获取设置日志根目录var_dump(SeasLog::getBasePath()); // string(12) "/var/log/www"SeasLog::setBasePath("./");var_dump(SeasLog::getBasePath()); // string(2) "./"

这这天记所要记录到的文件目录信息,我们可以通过 getBasePath() 来获得当前配置的文件目录信息。
默认情形下是 "/var/log/www" 这个目录,当然,我们也可以在 php.ini 中配置 seaslog.default_basepath 这个选项来修正这个默认的目录。
而在代码中,我们可以通过 setBasePath() 函数来修正这个目录,比如我们在上面的测试代码中将日志的目录修正到当前目录下了。

Logger 信息

// 获取设置日志 Logger 名称var_dump(SeasLog::getLastLogger()); // string(7) "default"SeasLog::info("Test Logger Default");// ./default/20200106.log// 2021-01-06 01:01:53 | INFO | 5038 | 5ff50c019ebe9 | 1609894913.650 | Test Logger DefaultSeasLog::setLogger("mylog");var_dump(SeasLog::getLastLogger()); // string(5) "mylog"SeasLog::info("Test Logger MyLog");// ./mylog/20200106.log// 2021-01-06 01:01:53 | INFO | 5038 | 5ff50c019ebe9 | 1609894913.650 | Test Logger MyLog

Logger 信息可以看做是一个日志分类,它会把这个分类下的日志放到同一个文件夹中。
比如我们在默认情形这个 Logger 是 default ,如果在默认情形下直策应用 info() 记录日志的话,就会在当前日志根目录天生一个 default 目录,并且创建一个以当前日期命名的日志文件。

如果我们利用 setLogger() 设置了一个新的 Logger ,那么两次记录日志的时候,就会天生创建一个新的 Logger 目录并将日志记录到这个目录中。

日期格式

// 获取设置日志日期格式var_dump(SeasLog::getDatetimeFormat()); // string(11) "Y-m-d H:i:s"SeasLog::info("Test Datetime Default");// 2021-01-06 01:04:44 …………SeasLog::setDatetimeFormat("Y/m/d His");var_dump(SeasLog::getDatetimeFormat()); // string(9) "Y/m/d His"SeasLog::info("Test Datetime New");// 2021/01/06 010444 …………

日期格式的修正实在便是针对的我们的日志格式中的 %T 这个参数。
这个就不多阐明了,代码中已经演示得很清晰了,利用 getDatetimeFormat() 可以获得当前设置的日期信息,而 setDatetimeFormat() 方法则是设置日期的格式。

要求 ID

var_dump(SeasLog::getRequestID()); // string(13) "5ff50e4721a06"SeasLog::setRequestID("new_request_id" . uniqid());var_dump(SeasLog::getRequestID()); // string(27) "new_request_id5ff50e6519202"SeasLog::info("Test New Request ID");// 2021/01/06 011216 | INFO | 5250 | new_request_id5ff50e70337b8 | 1609895536.210 | Test New Request ID

要求ID 针对的是 %Q 这个参数的内容。
默认情形下它便是调用 uniqid() 天生的一个唯一ID数据,我们可以通过 setRequestID() 来指定当前这条日志要天生的要求ID是什么内容。

其它日志变量

除了上面的这些固定功能的函数外,我们还可以设置一些别的要求参数信息。

ar_dump(SeasLog::getRequestVariable(SEASLOG_REQUEST_VARIABLE_DOMAIN_PORT));var_dump(SeasLog::getRequestVariable(SEASLOG_REQUEST_VARIABLE_REQUEST_URI));var_dump(SeasLog::getRequestVariable(SEASLOG_REQUEST_VARIABLE_REQUEST_METHOD));var_dump(SeasLog::getRequestVariable(SEASLOG_REQUEST_VARIABLE_CLIENT_IP));// string(3) "cli"// string(5) "1.php"// string(9) "/bin/bash"// string(5) "local"// seaslog.default_template="%T | %L | %P | %Q | %t | %M | %H | %m"SeasLog::info("Test Other Request Variable");// 2021/01/06 012512 | INFO | 5496 | new_request_id5ff5117888f86 | 1609896312.561 | Test Other Request Variable | localhost.localdomain | /bin/bashSeasLog::setRequestVariable(SEASLOG_REQUEST_VARIABLE_REQUEST_METHOD, "Get");var_dump(SeasLog::getRequestVariable(SEASLOG_REQUEST_VARIABLE_REQUEST_METHOD)); // string(3) "Get"SeasLog::info("Test New Other Request Variable");// 2021/01/06 012625 | INFO | 5520 | new_request_id5ff511c1367c2 | 1609896385.223 | Test New Other Request Variable | localhost.localdomain | Get

getRequestVariable() 和 setRequestVariable() 方法便是用于获取和设置其它要求变量信息的方法。
它们都只支持四个常量,也便是代码中演示的这些,分别代表要求的端口号、URI、方法和客户端IP。
不过这些内容并没有配置在默认日志模板中,以是我们须要默认的日志模板来查看这些内容。

在这段代码中,我们测试的是给默认模板添加了 %H 和 %m 两个参数,%H 代表的是主机名,%m 代表的是要求的 method ,也便是要求方法。
个中 %H 是无法设置修正的,这里只是测试一下格式的配置,而重点在于 %m 的内容。
默认情形下,在日志中记录的这个要求方法是 /bin/bash ,实在便是由于我们利用的是命令行来运行的测试代码,它的要求方法便是命令环境 bash 了。
这时,我们通过 setRequestVariable() 将要求方法的值修正为 "Get" ,在之后的日志记录中所有的 %m 参数中输出的就都是 Get 了。

日志记录

通过上面的学习,我们已经看到了一个 info() 方法的利用。
它便是记录普通的日志信息的一个函数,通过它记录的日志信息中的 %L 信息都会显示为 "info" 。
后面还有其它的日志类型方法,我们先来看看这个 info() 方法还有什么可以深挖的东西。

SeasLog::info("Test Info Log");// 2021/01/06 013018 | INFO | 5583 | new_request_id5ff512aaafcde | 1609896618.720 | Test Info Log | localhost.localdomain | GetSeasLog::info("Test {name} Log", ['name'=>'Info1']);// 2021/01/06 013018 | INFO | 5583 | new_request_id5ff512aaafcde | 1609896618.720 | Test Info1 Log | localhost.localdomain | GetSeasLog::info("Test {name} Log", ['name'=>'Info1'], 'default');// ./default/20210106.log// 2021/01/06 013140 | INFO | 5609 | new_request_id5ff512fc264f3 | 1609896700.156 | Test Info1 Log | localhost.localdomain | GetSeasLog::info("Test {name} Log", ['name'=>'Info1'], 'defaultNew');// defaultNew/20210106.log// 2021/01/06 013230 | INFO | 5631 | new_request_id5ff5132e3e143 | 1609896750.254 | Test Info1 Log | localhost.localdomain | Get

没错,这个 info() 方法除了第一个参数这天记的内容信息之外,它还有两个参数。

第二个参数的浸染是可以定义一个数组用来更换第一个参数中的一些占位符。
是不是觉得和数据库中的预编译占位符很像。
第三个参数则是可以直接指定一个 Logger ,也便是要记录的日志分类目录。
这个参数可就方便了,我们当前测试环境是前面设置过的 "mysql" ,以是前两条日志都记录在 mysql 目录下的日志文件中了。
而后面两条文是一条记录在 default 目录下,而另一条文是一个新的 defaultNew 目录下。
这个新的 Logger 目录我们并没有通过 setLogger() 来创建,但这里也会直接和 setLogger() 一样去创建新的目录和日志文件,非常方便。

除了 info() 外,常见的日志类型还有 notice 、warning、error、alter、debug、critical、emergency 这些。
相信只假如用过框架开拓或者利用过 monolog 的同学都不会陌生。
在 SeasLog 中,这些对应的方法函数也都是存在的,并且所有的参数和 info() 都是一样的。

SeasLog::alert("Test {name} Log", ['name'=>'Alert'], 'defaultNew');SeasLog::error("Test {name} Log", ['name'=>'Error'], 'defaultNew');SeasLog::debug("Test {name} Log", ['name'=>'Debug'], 'defaultNew');SeasLog::critical("Test {name} Log", ['name'=>'Critical'], 'defaultNew');SeasLog::emergency("Test {name} Log", ['name'=>'Emergency'], 'defaultNew');SeasLog::notice("Test {name} Log", ['name'=>'Notice'], 'defaultNew');SeasLog::warning("Test {name} Log", ['name'=>'Warning'], 'defaultNew');// 2021/01/06 014106 | ALERT | 5762 | new_request_id5ff5153200b30 | 1609897266.2 | Test Alert Log | localhost.localdomain | Get// 2021/01/06 014106 | ERROR | 5762 | new_request_id5ff5153200b30 | 1609897266.2 | Test Error Log | localhost.localdomain | Get// 2021/01/06 014106 | DEBUG | 5762 | new_request_id5ff5153200b30 | 1609897266.2 | Test Debug Log | localhost.localdomain | Get// 2021/01/06 014106 | CRITICAL | 5762 | new_request_id5ff5153200b30 | 1609897266.2 | Test Critical Log | localhost.localdomain | Get// 2021/01/06 014106 | EMERGENCY | 5762 | new_request_id5ff5153200b30 | 1609897266.2 | Test Emergency Log | localhost.localdomain | Get// 2021/01/06 014106 | NOTICE | 5762 | new_request_id5ff5153200b30 | 1609897266.2 | Test Notice Log | localhost.localdomain | Get// 2021/01/06 014106 | WARNING | 5762 | new_request_id5ff5153200b30 | 1609897266.2 | Test Warning Log | localhost.localdomain | Get

可以看到,它们记录的日志信息中的 %L 内容都是和自身相对应的。
当然,除了这些固定的方法函数之外,我们还有一个通用的记录日志的方法。

SeasLog::log(SEASLOG_INFO, "Test log() {name} Log", ['name'=>"Info"], 'defaultNew');// 2021/01/06 014330 | INFO | 5809 | new_request_id5ff515c2ddd5d | 1609897410.908 | Test log() Info Log | localhost.localdomain | Get

便是这个 log() 方法,它和上面的 info() 那些方法比较,便是多了一个须要指定当前日志类型的参数,而且这个参数也供应了常量。

日志信息查看

接下来便是对日志信息的一些数据的查看了。
就像上面的测试中我们来回测试实在已经记了不少日志数据了,有没有什么方法函数可以方便地查看这些日志信息内容呢?

SeasLog::setLogger('defaultNew');var_dump(SeasLog::analyzerCount());// array(8) {// ["DEBUG"]=>// int(40)// ["INFO"]=>// int(80)// ["NOTICE"]=>// int(40)// ["WARNING"]=>// int(40)// ["ERROR"]=>// int(40)// ["CRITICAL"]=>// int(40)// ["ALERT"]=>// int(40)// ["EMERGENCY"]=>// int(40)// }

首先便是这个 analyzerCount() ,它用来统计日志的数量。
可以看到在默认情形下它是返回一个数组并且显示的是所有日志类型记录的日志数量的。
当然,它可以增加参数,但如果增加参数指定日志类型之后,它返回的便是一个普通的数字值了,也便是这个指定的日志类型的数量信息。

var_dump(SeasLog::analyzerCount(SEASLOG_WARNING)); // int(10)var_dump(SeasLog::analyzerCount(SEASLOG_ERROR, null)); // int(10)var_dump(SeasLog::analyzerCount(SEASLOG_ERROR, null, "1609897995.939")); // int(1)

此外,它还有其余两个参数,第二个参数是指定日志的路径信息,也便是在当前 Logger 目录下如果还有别的目录的话,可以通过这个参数指定,如果是设置为 null 则忽略这个函数。
第三个参数则是过滤信息,就有点像模糊查找的观点,比如我们这里只查找指定的这个韶光戳的日志,返回的结果便是只有一条。

var_dump(SeasLog::analyzerDetail(SEASLOG_ALL, 'secpath/', null, 1, 2, SEASLOG_DETAIL_ORDER_DESC));// array(2) {// [0]=>// string(125) "2021/01/06 020212 | INFO | 6840 | new_request_id5ff51a248e1e2 | 1609898532.582 | Test Info1 Log | localhost.localdomain | Get"// [1]=>// string(124) "2021/01/06 020212 | INFO | 6840 | new_request_id5ff51a248e1e2 | 1609898532.582 | Test Info Log | localhost.localdomain | Get"// }

analyzerDetail() 是可以根据条件展示详细的日志信息。
它也是可以指定日志类型、路径目录、关键词信息的,其余它还能够指定返回的条数、排序等,也便是自带范围查找及翻页功能。
是不是非常地刁悍。
同 analyzerCount() 一样,它的参数也都可以设置为 null 表示当前这个参数不启用走默认的值。
比如我们这个测试代码中的 null 便是关键字那个参数位置,表示的便是不用去模糊匹配关键字。

内存缓冲区日志

除了可以直接记录文件日志外,SeasLog 还可以在 php.ini 中通过配置来将文件发送到指定的 tcp 或 ftp 做事器,这个大家可以自行理解下。
而接下来要学习的是在内存中缓存日志的操作。
通过这个配置,我们可以更进一步地提升 SeasLog 的性能表现。

如果每条日志都是实时地写入磁盘或者发送的外部,无疑都会带来额外的磁盘IO或者网络IO开销,为了进一步提升性能,SeasLog 中供应的内存缓冲能力就可以帮我们办理问题,它的意思实在便是先将日志保存在内存中,然后到达一定数量之后再一次性地写入到磁盘或者发送给外部。

var_dump(SeasLog::getBufferEnabled()); // bool(false)// seaslog.use_buffer=1// seaslog.buffer_disabled_in_cli=0// seaslog.buffer_size=100var_dump(SeasLog::getBufferEnabled()); // bool(true)

我们须要在 php.ini 中配置 seaslog.use_buffer 为 1 ,也便是开启内存缓冲的功能。
如果是命令行脚本运行的话,还须要将 seaslog.buffer_disabled_in_cli 设置为 0 ,关闭“内存缓冲在cli环境下不启用”的功能。
末了,设置一下 seaslog.buffer_size ,也便是缓冲区保存多少条数据。
当这些设置完成之后,利用 getBufferEnabled() 就可以看到返回的内容是 true 了,也便是我们已经开启了内存缓冲的能力。

接下来,利用 getBuffer() 方法就可以看到目前在内存缓冲区中的所有日志信息了。

var_dump(SeasLog::getBuffer());// array(3) {// [".//default/20210106.log"]=>// array(2) {// [0]=>// string(125) "2021-01-06 02:21:43 | INFO | 8006 | 5ff51eb7e1a54 | 1609899703.924 | Test Logger Default | localhost.localdomain | /bin/bash// "// ……………………// }// [".//mylog/20210106.log"]=>// array(8) {// [0]=>// string(123) "2021-01-06 02:21:43 | INFO | 8006 | 5ff51eb7e1a54 | 1609899703.924 | Test Logger MyLog | localhost.localdomain | /bin/bash// "// ……………………// }// [".//defaultNew/20210106.log"]=>// array(9) {// [0]=>// string(126) "2021/01/06 022143 | INFO | 8006 | new_request_id5ff51eb7e1b30 | 1609899703.924 | Test Info1 Log | localhost.localdomain | Get// "// ……………………// }// }

当开启了内存缓冲的功能之后,我们前面的测试代码中的所有记录日志的操作都会将日志记录在内存中了,以是 getBuffer() 方法返回的数组中会有非常多的数据。
当脚本结束运行或者要求终止的时候,这些内存中的数据就会写入磁盘或者发送走。
当然,我们也可以手动地将这些数据刷新到磁盘上。

// 刷新内存缓冲区SeasLog::flushBuffer();var_dump(SeasLog::getBuffer());// array(0) {// }

调用 flushBuffer() 方法就可以手动地将内存数据刷新掉,这时再调用 getBuffer() 就没有任何数据了。
同时我们的测试文件中也一次性地写入了所有的日志数据。

总结

不得不说,通过这个扩展的学习貌似又创造了一个大宝藏。
这种日志系统在底层扩展上进行操作,效率肯定是没有问题,但是麻烦的也是须要安装底层的扩展,而不像 monolog 之类的可以直策应用 Composer 就完成安装利用。
事物总有利弊,详细得当哪种还是要根据我们的业务场景来深入剖析判断。

测试代码:

https://github.com/zhangyue0503/dev-blog/blob/master/php/2021/01/source/2.学习理解PHP中的SeasLog日志扩展.php

参考文档:

https://www.php.net/manual/zh/book.seaslog.php

Github文档 [https://github.com/SeasX/SeasLog/blob/master/README_zh.md]