对 Hyperf 做的那些事 3(日志处理)
- 作为一个PHPer一直思考PHP怎么做高性能(个人基础比较弱),怎么微服务,之前真不知道,不是概念而是怎么落地,谈概念有用但是不落地有点扯
- Swoole没有实际项目用过,swoole相关框架也没了解过也不知道,获取知道了可能对于之前的问题就可能有一些答案了
- Hyperf的出现简直就是太及时了,文档清晰,框架灵活等等等,简直不要太好
- 虽然Hyperf已经这么好了,但是还是希望把它稍微按着自己喜欢的规范改造一下,这里就把多有对它的改造都定义为自己的开发规范,如果大家觉得有道理可以沿用
- 感谢swoole团队(韩老师等……),感谢Hyperf团队(黄老师等……)
- 看完文章是否可以点个赞!!!!
工欲善其事,必先利其器,它有个名字
HyperfCMS
开整(3)
- 学习一个框架,日志处理是一个很很很重要的事情,个人博客、门户网站、商城网站、公司内部管理系统等等,无论大小都应该需要日志处理,但是系统的复杂度应该对应有灵活的日志处理方案。谈一下个人的看法:
- 默认肯定支持文件日志,有业务需求可以在对文件日志进行收集,比如ELK,给公司写个门户一上来你就搞一套完整(复杂)的日志机制感觉没有真正的必要。如果不二次处理文件日志查看起来就没然后了……
- DB日志,用数据库存储日志,如果网站没有太多的请求,是没啥问题的。但是请求多了有可能数据表骤增至1个亿……
- 通过API的方式对接第三方日志系统(这里只是对接了AliyunSLS),注意是通过API方式也就是同步,在每次请求或者认为的位置调用API,当然这个会对业务有一些可以忽略不计的影响,如果遇到不可以忽略的影响的时候,针对业务的完整的日志替代方案也应该早就有了……
虽然logger配置很灵活,但是我只用处理handlers来实现修改,增加驱动判断:
$driver = env('LOG_DRIVER', 'file'); $handlers = []; if ($driver == 'file') { $handlers = [ // info、waring、notice日志等 [ 'class' => App\Core\Handler\LogFileHandler::class, 'constructor' => [ 'stream' => BASE_PATH . '/runtime/logs/hyperf.log', 'level' => Monolog\Logger::INFO, ], 'formatter' => [ 'class' => Monolog\Formatter\LineFormatter::class, 'constructor' => [ 'format' => "%datetime%||%channel%||%level_name%||%message%||%context%||%extra%\n", 'dateFormat' => null, 'allowInlineLineBreaks' => true, ], ] ], // debug日志 [ 'class' => App\Core\Handler\LogFileHandler::class, 'constructor' => [ 'stream' => BASE_PATH . '/runtime/logs/hyperf-debug.log', 'level' => Monolog\Logger::DEBUG, ], 'formatter' => [ 'class' => Monolog\Formatter\LineFormatter::class, 'constructor' => [ 'format' => "%datetime%||%channel%||%level_name%||%message%||%context%||%extra%\n", 'dateFormat' => null, 'allowInlineLineBreaks' => true, ], ] ], // error日志 [ 'class' => App\Core\Handler\LogFileHandler::class, 'constructor' => [ 'stream' => BASE_PATH . '/runtime/logs/hyperf-error.log', 'level' => Monolog\Logger::ERROR, ], 'formatter' => [ 'class' => Monolog\Formatter\LineFormatter::class, 'constructor' => [ 'format' => "%datetime%||%channel%||%level_name%||%message%||%context%||%extra%\n", 'dateFormat' => null, 'allowInlineLineBreaks' => true, ], ] ], ]; } if ($driver == 'db') { $handlers = [ // 数据库日志存储 [ 'class' => App\Core\Handler\LogDbHandler::class, 'formatter' => [ 'class' => Monolog\Formatter\LineFormatter::class, 'constructor' => [ 'format' => "%datetime%||%channel%||%level_name%||%message%||%context%||%extra%\n", 'dateFormat' => null, 'allowInlineLineBreaks' => true, ], ] ], ]; } if ($driver == 'sls') { $handlers = [ // 数据库日志存储 [ 'class' => App\Core\Handler\LogSlsHandler::class, 'formatter' => [ 'class' => Monolog\Formatter\LineFormatter::class, 'constructor' => [ 'format' => "%datetime%||%channel%||%level_name%||%message%||%context%||%extra%\n", 'dateFormat' => null, 'allowInlineLineBreaks' => true, ], ] ], ]; } return [ 'default' => [ // 配置多个hander,根据每个handel产生日志 'handlers' => $handlers ], ];
修改filelog的规则,将debug、error、其它类型,分别用三个不同文件存储。
namespace App\Core\Handler; use Monolog\Handler\StreamHandler; use Monolog\Logger; /** * LogFileHandler * 日志处理,存储文件 * 将info、warning、notic等类型存储一个文件,debug类型存储一个文件,error类型存储一个文件 * @package App\Core\Handler * User:YM * Date:2019/11/29 * Time:下午6:39 */ class LogFileHandler extends StreamHandler { /** * handle * 改写父类方法,增加判断日志输出,框架日志 * User:YM * Date:2019/12/21 * Time:下午7:16 * @param array $record * @return bool */ public function handle(array $record) { if (!$this->isHandling($record)) { return false; } $record = $this->processRecord($record); // 判断系统允许日志类型 if ( ! isStdoutLog($record['level_name']) ) { return false; } // 判断是否处理框架日志 if ( ! env('HF_LOG', false) && $record['channel'] == 'hyperf' ) { return false; } $record['formatted'] = $this->getFormatter()->format($record); $this->write($record); return false === $this->bubble; } /** * isHandling * 重写该方法,作用改变日志的存储文件的方式。 * 将debug,error,单独存储,其它的按着原来规则 * User:YM * Date:2019/11/29 * Time:下午6:49 * @param array $record * @return bool */ public function isHandling(array $record) { switch ($record['level']) { case Logger::DEBUG: return $record['level'] == $this->level; break; case $record['level'] == Logger::ERROR || $record['level'] == Logger::CRITICAL || $record['level'] == Logger::ALERT || $record['level'] == Logger::EMERGENCY: return Logger::ERROR <= $this->level && Logger::EMERGENCY >= $this->level; break; default: return Logger::INFO <= $this->level && Logger::WARNING >= $this->level; } } }
增加dblog的支持
namespace App\Core\Handler; use Monolog\Handler\StreamHandler; use Monolog\Logger; /** * LogFileHandler * 日志处理,存储文件 * 将info、warning、notic等类型存储一个文件,debug类型存储一个文件,error类型存储一个文件 * @package App\Core\Handler * User:YM * Date:2019/11/29 * Time:下午6:39 */ class LogFileHandler extends StreamHandler { /** * handle * 改写父类方法,增加判断日志输出,框架日志 * User:YM * Date:2019/12/21 * Time:下午7:16 * @param array $record * @return bool */ public function handle(array $record) { if (!$this->isHandling($record)) { return false; } $record = $this->processRecord($record); // 判断系统允许日志类型 if ( ! isStdoutLog($record['level_name']) ) { return false; } // 判断是否处理框架日志 if ( ! env('HF_LOG', false) && $record['channel'] == 'hyperf' ) { return false; } $record['formatted'] = $this->getFormatter()->format($record); $this->write($record); return false === $this->bubble; } /** * isHandling * 重写该方法,作用改变日志的存储文件的方式。 * 将debug,error,单独存储,其它的按着原来规则 * User:YM * Date:2019/11/29 * Time:下午6:49 * @param array $record * @return bool */ public function isHandling(array $record) { switch ($record['level']) { case Logger::DEBUG: return $record['level'] == $this->level; break; case $record['level'] == Logger::ERROR || $record['level'] == Logger::CRITICAL || $record['level'] == Logger::ALERT || $record['level'] == Logger::EMERGENCY: return Logger::ERROR <= $this->level && Logger::EMERGENCY >= $this->level; break; default: return Logger::INFO <= $this->level && Logger::WARNING >= $this->level; } } }
增加阿里云slslog的支持,需要使用适配hyperf框架的sdk支持,点这里!!!,需要注意:存储字段不能有空值,如果有自己处理一下0代替
namespace App\Core\Handler; use Hyperf\Di\Annotation\Inject; use Monolog\Handler\AbstractProcessingHandler; use Ym\AliyunSls\ClientInterface; /** * LogSlsHandler * 阿里云sls日志处理 * @package App\Core\LogHandler * User:YM * Date:2019/12/31 * Time:下午3:17 */ class LogSlsHandler extends AbstractProcessingHandler { /** * @Inject * @var ClientInterface */ protected $sls; /** * write * 记录日志 * User:YM * Date:2019/12/21 * Time:下午4:15 * @param array $record * @return bool|void */ public function write(array $record) { // 判断系统允许日志类型 if ( ! isStdoutLog($record['level_name']) ) { return false; } // 判断是否处理框架日志 if ( ! env('HF_LOG', false) && $record['channel'] == 'hyperf' ) { return false; } $saveData = $record['context']; $saveData['channel'] = $record['channel']; $saveData['message'] = is_array($record['message'])?json_encode($record['message']):$record['message']; $saveData['level_name'] = $record['level_name']; // 阿里云日志不能有空字段 foreach ($saveData as &$v) { if (!$v) { $v = 0; } } unset($v); $this->sls->putLogs($saveData); } }
hyperf框架默认日志时打印到控制台的使用(StdoutLoggerInterface),使用monolog需要使用(LoggerFactory),但是如果统一一下(然而我就这么做了),遇到一些问题以及解决
- 给dependencies.php文件增加
\Hyperf\Contract\StdoutLoggerInterface::class => \App\Core\HF\StdoutLoggerFactory::class
改变依赖,且默认channel为”hyperf”,后期用这个做了一些事情,工厂类实现了什么,可以看看hyperf官方文档,基本差不多 - db日志实现的时候,由于框架核心输出是
StdoutLoggerInterface
实现,而且已经被修改依赖,将不在控制台打印而是通过日志驱动,由于系统框架有数据库监听日志,所以死循环了……,当然已经处理了,看日志handler代码就好了 - 三个驱动handler已经对,config.php的
StdoutLoggerInterface::class
提供了适配
- 给dependencies.php文件增加
本作品采用《CC 协议》,转载必须注明作者和本文链接
本帖由系统于 4年前 自动加精
推荐文章: