hyperf 如何實現按日期分割日誌

fengchezhi發表於2020-07-13

首先安裝hyperf composer create-project hyperf/hyperf-skeleton
需要修改的檔案如下所示

hyperf-skeleton
├── app
│   ├── Controller
│   │   ├── AbstractController.php
│   │   └── IndexController.php // 增加丟擲錯誤的程式碼 throw new Exception("hello error!");
│   ├── Exception
│   │   └── Handler
│   │   │   └── AppExceptionHandler.php // 使用daily通道寫入日誌
│   ├── Helper
│   │   └── RotatingFileHandler.php // 複製monolog的RotatingFileHandler並且修改支援hyperf
├── config
│   ├── autoload
│   │   ├── logger.php // 增加daily通道日誌配置
├── runtime
│   └── logs
│       ├── daily-2020-07-12.log // 執行出來的效果
│       ├── daily-2020-07-13.log
│       ├── daily-2020-07-14.log
│       └── hyperf.log

增加丟擲異常

// 所在檔案:app/Controller/IndexController.php
class IndexController extends AbstractController
{
    /**
     * @var ContainerInterface
     */
    protected $container;

    public function index()
    {
        throw new Exception("hello error!"); //增加了這一行程式碼

        $user = $this->request->input('user', 'Hyperf');
        $method = $this->request->getMethod();

        return [
            'method' => $method,
            'message' => "Hello {$user}.",
        ];
    }
}

複製monolog的RotatingFileHandler.php並且修改支援hyperf

// 所在檔案:app/Helper/RotatingFileHandler.php
<?php declare(strict_types=1);

namespace App\Helper;

use InvalidArgumentException;
use Monolog\Logger;
use Monolog\Utils;
use Monolog\Handler\StreamHandler;

class RotatingFileHandler extends StreamHandler
{
    ... // 此處省略

    protected function write(array $record): void
    {
        // on the first record written, if the log is new, we should rotate (once per day)
        if (null === $this->mustRotate) {
            $this->mustRotate = !file_exists($this->url);
        }

        if ($this->nextRotation <= $record['datetime']) {
            $this->mustRotate = true;
            $this->close();
        }
        // 增加以下四行程式碼
        if($this->url != $this->getTimedFilename()){
           $this->url = $this->getTimedFilename();
           $this->stream = null;
        }

        parent::write($record);
    }
    ... // 此處忽略
}

增加daily通道日誌配置

// 所在檔案:config/autoload/logger.php
<?php

declare(strict_types=1);
/**
 * This file is part of Hyperf.
 *
 * @link     https://www.hyperf.io
 * @document https://doc.hyperf.io
 * @contact  group@hyperf.io
 * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
 */
return [
    'default' => [
        'handler' => [
            'class' => Monolog\Handler\StreamHandler::class,
            'constructor' => [
                'stream' => BASE_PATH . '/runtime/logs/hyperf.log',
                'level' => Monolog\Logger::DEBUG,
            ],
        ],
        'formatter' => [
            'class' => Monolog\Formatter\LineFormatter::class,
            'constructor' => [
                'format' => null,
                'dateFormat' => 'Y-m-d H:i:s',
                'allowInlineLineBreaks' => true,
            ],
        ],
    ],
    // 增加daily日誌通道
    'daily' => [
        'handler' => [
            'class' => App\Helper\RotatingFileHandler::class,
            'constructor' => [
                'filename' => BASE_PATH . '/runtime/logs/daily.log',
                'maxFiles' => 14, //最多顯示最近的15個檔案
            ],
        ],
        'formatter' => [
            'class' => Monolog\Formatter\LineFormatter::class,
            'constructor' => [
                'format' => null,
                'dateFormat' => 'Y-m-d H:i:s',
                'allowInlineLineBreaks' => true,
            ],
        ],
    ],
];
使用daily通道寫入日誌
<?php declare(strict_types=1);

namespace App\Exception\Handler;

use Hyperf\Contract\StdoutLoggerInterface;
use Hyperf\ExceptionHandler\ExceptionHandler;
use Hyperf\HttpMessage\Stream\SwooleStream;
use Psr\Http\Message\ResponseInterface;
use Throwable;
use Hyperf\Logger\LoggerFactory;
use Psr\Container\ContainerInterface;

class AppExceptionHandler extends ExceptionHandler
{
    /**
     * @var StdoutLoggerInterface
     */
    protected $logger;

    /**
     * @var ContainerInterface
     */
    protected $container;

    public function __construct(StdoutLoggerInterface $logger, ContainerInterface $container)
    {
        $this->logger = $logger;
        $this->container = $container;
    }

    public function handle(Throwable $throwable, ResponseInterface $response)
    {
        $this->logger->error(sprintf('%s[%s] in %s', $throwable->getMessage(), $throwable->getLine(), $throwable->getFile()));
        $this->logger->error($throwable->getTraceAsString());

        // 從容器獲取到日誌物件
        $log = $this->container->get(LoggerFactory::class)->get('debug', 'daily'); // 關鍵程式碼
        $log->info(sprintf('%s[%s] in %s', $throwable->getMessage(), $throwable->getLine(), $throwable->getFile()));
        $log->info($throwable->getTraceAsString());

        return $response->withHeader('Server', 'Hyperf')->withStatus(500)->withBody(new SwooleStream('Internal Server Error.'));
    }

    public function isValid(Throwable $throwable): bool
    {
        return true;
    }
}

效果圖

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章