日志文件是开发人员佣有的最有价值的资产之一。和core dump
文件一样可以起到一个死后验尸的功能。
在开发和测试阶段可以依靠日志来定位,重现问题和解决错误,提升开发效率。 当线上有异常问题出现时可以通过日志文件来帮助你分析、定位出可能出问题的地方,从而可以快速解决问题,对于紧急严重的问题更是如此,精确定位问题并快速解决能够最大化的降低影响面及损失。不然,出问题后只能靠经验去猜测可能出问题的地方。
日志监控系统能确认系统的正确性,像医师的听诊器一样,它是我们开发人员的电子哨兵。
本篇日志系统整体架构思路是:
- 扩展 PHP 第三方的日志库Monolog把日志发送到
Beanstalkd
队列里 - 通过后台的一个
常驻内存脚本
来消费队列中的消息,并把消息索引到Elasticsearch
中 - 配置
Kibana
来连通ES
从而来搜索展示日志记录
为什么选择这个设计方案呢?
1)选择Monolog
是因为它现在是PHP
最流行的日志记录器,它实现了PSR-3
并且多个开源框架都在使用,但目前的版本(^2.0
)还不支持发送日志到Beanstalkd
,所以我们对它进行扩展,写一个BeanstalkdHandler
注:本身Monolog
已经支持发送日志到RabbitMQ
中
2)为什么不选择通过Monolog
直接发送日志到ES
中?因为性能问题。通过把日志记录到队列中的好处是,记录日志时不会阻塞你的应用程序,能使服务器快速响应用户请求,而不必立即执行大量耗时耗资源的程序
3)毫无疑问Elasticsearch
和Kibana
是现在最使用最广泛的日志分析平台,当然选择它
整个系统还有一个关键点要考虑就是:如果后台的常驻脚本因为程序意外退出后系统能够自动拉起,这样异常发生时消息才不会堆积,不会导致内存使用暴涨从而拖垮机器。对于这点我们使用的是daemontools
这个进程守护工具来达到我们的目的。
备注:
对于上面系统中描述用到的开源组件的使用感兴趣的话,可以参考以下我的几篇文章:
├── composer.json
├── composer.lock
├── daemons
│ └── log-daemon.php
├── log.php
├── phpunit.xml.dist
├── tests
│ ├── bootstrap.php
│ └── Modules
│ └── Helper
│ └── UtilsTest.php
└── walle
├── Modules
│ ├── Helper
│ │ └── Utils.php
│ ├── Log
│ │ └── Log.php
│ └── Queue
│ └── BeanstalkdQueue.php
└── Monolog
└── Handler
└── BeanstalkdHandler.php
daemons/log-daemon.php
该文件是就是我们的守护进程脚本,负责消费队列中的消息并把消息索引到ES
中log.php
测试文件,模拟上层业务系统调用Log
类tests
单元测试文件存放目录walle/Modules
包装好的通用业务和系统功能组件,或者对第三方库封装的模块可以放到这个目录下。比如:walle/Modules/Helper/Utils.php
是我们自己封装的辅助类,再比如:walle/Modules/Log/Log.php
对扩展后的monolog
库再次包装的类,它是直接提供给上层业务系统调用的walle/Monolog
存放对monolog
扩展的处理器(Handler
)、加工器(Processor
)、格式化器(Formatter
)
$ composer install
- 构建
log daemontools Service
# cd /scratch/service/
# mkdir log
# cd log/
创建一个run文件,其中包含:
#!/bin/sh
exec 2>&1
exec su - root -c "php /mnt/hgfs/github/log/src/daemons/log-daemon.php" 1>> /data1/log/php-scripts/log-daemon.php
赋予执行权限
# chmod u+x run
安装log服务并实际开始运行它
# ln -s /scratch/service/log/ /service/log
确认进程正在运行
# ps -ef | grep log-daemon.php
到这里我们的守护进程已经在后台运行了,而且被daemontools
监护着,我们通过beanstalk_console
可以看到名为log
的tube
已经产生
- 使用
Walle\Modules\Log\Log
类来记录日志
运行我们的测试程序,使用我们封装的Log
类来记录日志,日志被发送到Beanstalkd
的log
队列中
$ php log.php
再次查看beanstalk_console
,可以看到日志已经被消费了
- 使用
Kinaba
来搜索日志
在Kinaba
上创建log-*
索引模式
这是创建完成后的log-*
索引的字段类型信息
搜索日志看看,我们搜索最近30分钟,日志级别是error
以上的所有日志
再次运行测试程序
$ php log.php
现在直接去Kibana
里搜索看看,我们搜索最近30分钟,日志级别是error
以上,上下文内容(context
)包含“张权九”关键字并且extra
中进程ID是 8968 的所有日志
$ phpunit
PHPUnit 5.7.25 by Sebastian Bergmann and contributors.
.. 2 / 2 (100%)
Time: 384 ms, Memory: 3.25MB
OK (2 tests, 4 assertions)
至此,我们设计并开发了一个还不错的日志系统,回过头来想想我们系统有什么优点?还有什么可以改善的地方?有什么使用中要留意的坑?
-
使用方便,开发人员只要一行代码就能在代码中记录想记录的信息,然后通过
Kinaba
可以直观的观察统计等 -
性能好,日志首先记录到队列,调用时不会阻塞你客户端程序的执行。我们也可以把
Beanstalkd
换成RabbitMQ
或Kafka
来玩玩看 -
方便扩展和维护,基于
Monolog
我们可以扩展我们自己内部的处理器(Handler
)和加工器(Processor
)
-
可以在消费者程序中加入日志报警功能,比如对于
error
级别以上的日志,可以发送短信、邮件、企业微信等给相应的开发人员 -
完善剩下的单元测试
-
消费者程序
log-daemon.php
是常驻内存的,程序运行后就把我们的代码加载到内存了,无论后期我们怎么修改磁盘上的代码,重新再次发起请求的时候,永远都是内存中的代码生效,所以我们修改代码后要杀死log-daemon.php
进程,释放掉内存,重新把新的代码加载内存中。我们只要kill
就行,Daemontools
会自动帮我们重新拉起。 -
还是因为
log-daemon.php
是常驻内存的,所以编写代码时一定要小心,防止内存泄漏。