对 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 协议》,转载必须注明作者和本文链接

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

xiaoyin199
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

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