對 Hyperf 做的那些事 3(日誌處理)

xiaoyin199發表於2020-01-06
  1. 作為一個PHPer一直思考PHP怎麼做高效能(個人基礎比較弱),怎麼微服務,之前真不知道,不是概念而是怎麼落地,談概念有用但是不落地有點扯
  2. Swoole沒有實際專案用過,swoole相關框架也沒了解過也不知道,獲取知道了可能對於之前的問題就可能有一些答案了
  3. Hyperf的出現簡直就是太及時了,文件清晰,框架靈活等等等,簡直不要太好
  4. 雖然Hyperf已經這麼好了,但是還是希望把它稍微按著自己喜歡的規範改造一下,這裡就把多有對它的改造都定義為自己的開發規範,如果大家覺得有道理可以沿用
  5. 感謝swoole團隊(韓老師等……),感謝Hyperf團隊(黃老師等……)
  6. 看完文章是否可以點個贊!!!!

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

  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 協議》,轉載必須註明作者和本文連結

小尹你好!
成功細中取,寶貴險中求;細節決定成敗,態度決定一切。

相關文章