对 Hyperf 做的那些事 3(日志处理)

  1. 作为一个PHPer一直思考PHP怎么做高性能(个人基础比较弱),怎么微服务,之前真不知道,不是概念而是怎么落地,谈概念有用但是不落地有点扯
  2. Swoole没有实际项目用过,swoole相关框架也没了解过也不知道,获取知道了可能对于之前的问题就可能有一些答案了
  3. Hyperf的出现简直就是太及时了,文档清晰,框架灵活等等等,简直不要太好
  4. 虽然Hyperf已经这么好了,但是还是希望把它稍微按着自己喜欢的规范改造一下,这里就把多有对它的改造都定义为自己的开发规范,如果大家觉得有道理可以沿用
  5. 感谢swoole团队(韩老师等……),感谢Hyperf团队(黄老师等……)
  6. 看完文章是否可以点个赞!!!!

工欲善其事,必先利其器,它有个名字HyperfCMS

开整(3)

  1. 学习一个框架,日志处理是一个很很很重要的事情,个人博客、门户网站、商城网站、公司内部管理系统等等,无论大小都应该需要日志处理,但是系统的复杂度应该对应有灵活的日志处理方案。谈一下个人的看法:
  2. 默认肯定支持文件日志,有业务需求可以在对文件日志进行收集,比如ELK,给公司写个门户一上来你就搞一套完整(复杂)的日志机制感觉没有真正的必要。如果不二次处理文件日志查看起来就没然后了……
  3. DB日志,用数据库存储日志,如果网站没有太多的请求,是没啥问题的。但是请求多了有可能数据表骤增至1个亿……
  4. 通过API的方式对接第三方日志系统(这里只是对接了AliyunSLS),注意是通过API方式也就是同步,在每次请求或者认为的位置调用API,当然这个会对业务有一些可以忽略不计的影响,如果遇到不可以忽略的影响的时候,针对业务的完整的日志替代方案也应该早就有了……
  1. 虽然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
     ],
    ];
  2. 修改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;
         }
     }
    }
  3. 增加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;
         }
     }
    }
  4. 增加阿里云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);
     }
    }
  5. hyperf框架默认日志时打印到控制台的使用(StdoutLoggerInterface),使用monolog需要使用(LoggerFactory),但是如果统一一下(然而我就这么做了),遇到一些问题以及解决

    1. 给dependencies.php文件增加\Hyperf\Contract\StdoutLoggerInterface::class => \App\Core\HF\StdoutLoggerFactory::class改变依赖,且默认channel为”hyperf”,后期用这个做了一些事情,工厂类实现了什么,可以看看hyperf官方文档,基本差不多
    2. db日志实现的时候,由于框架核心输出是StdoutLoggerInterface实现,而且已经被修改依赖,将不在控制台打印而是通过日志驱动,由于系统框架有数据库监听日志,所以死循环了……,当然已经处理了,看日志handler代码就好了
    3. 三个驱动handler已经对,config.php的StdoutLoggerInterface::class提供了适配
本作品采用《CC 协议》,转载必须注明作者和本文链接

小尹你好!
成功细中取,宝贵险中求;细节决定成败,态度决定一切。

本帖由系统于 1周前 自动加精
xiaoyin199
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

请勿发布不友善或者负能量的内容。与人为善,比聪明更重要!