日志

    hyperf-skeleton 项目内默认提供了一些日志配置,默认情况下,日志的配置文件为 config/autoload/logger.php ,示例如下:

    1. <?php
    2. return [
    3. 'default' => [
    4. 'handler' => [
    5. 'class' => \Monolog\Handler\StreamHandler::class,
    6. 'constructor' => [
    7. 'stream' => BASE_PATH . '/runtime/logs/hyperf.log',
    8. 'level' => \Monolog\Logger::DEBUG,
    9. ],
    10. ],
    11. 'formatter' => [
    12. 'class' => \Monolog\Formatter\LineFormatter::class,
    13. 'constructor' => [
    14. 'dateFormat' => null,
    15. 'allowInlineLineBreaks' => true,
    16. ]
    17. ],
    18. ],
    19. ];
    1. <?php
    2. declare(strict_types=1);
    3. namespace App\Service;
    4. use Psr\Container\ContainerInterface;
    5. use Hyperf\Logger\LoggerFactory;
    6. class DemoService
    7. {
    8. /**
    9. */
    10. protected $logger;
    11. {
    12. // 第一个参数对应日志的 name, 第二个参数对应 config/autoload/logger.php 内的 key
    13. $this->logger = $loggerFactory->get('log', 'default');
    14. }
    15. public function method()
    16. {
    17. // Do somthing.
    18. $this->logger->info("Your log message.");
    19. }
    20. }

    我们结合代码来看一些 monolog 中所涉及到的基础概念:

    • 首先, 实例化一个 Logger, 取个名字, 名字对应的就是 channel
    • 可以为 Logger 绑定多个 Handler, Logger 打日志, 交由 Handler 来处理
    • Handler 可以指定需要处理哪些 日志级别 的日志, 比如 Logger::WARNING, 只处理日志级别 >=Logger::WARNING 的日志
    • 谁来格式化日志? Formatter, 设置好 Formatter 并绑定到相应的 Handler
    • 日志包含哪些部分: "%datetime%||%channel||%level_name%||%message%||%context%||%extra%\n"
    • 区分一下日志中添加的额外信息 contextextra: context 由用户打日志时额外指定, 更加灵活; extra 由绑定到 Logger 上的 Processor 固定添加, 比较适合收集一些 常见信息
    namespace App;
    
    use Hyperf\Logger\Logger;
    use Hyperf\Utils\ApplicationContext;
    
    class Log
    {
        public static function get(string $name = 'app')
        {
            return ApplicationContext::getContainer()->get(\Hyperf\Logger\LoggerFactory::class)->get($name);
        }
    }
    

    默认使用 Channel 名为 app 来记录日志,您也可以通过使用 Log::get($name) 方法获得不同 Channel 的 , 强大的 容器(Container) 帮您解决了这一切

    框架组件所输出的日志在默认情况下是由 Hyperf\Contract\StdoutLoggerInterface 接口的实现类 Hyperf\Framework\Logger\StdoutLogger 提供支持的,该实现类只是为了将相关的信息通过 print_r() 输出在 标准输出(stdout),即为启动 Hyperf终端(Terminal) 上,也就意味着其实并没有使用到 monolog 的,那么如果想要使用 monolog 来保持一致要怎么处理呢?

    • 首先, 实现一个 StdoutLoggerFactory 类,关于 Factory 的用法可在 依赖注入 章节获得更多详细的说明。
    <?php
    declare(strict_types=1);
    
    namespace App;
    
    use Psr\Container\ContainerInterface;
    
    class StdoutLoggerFactory
    {
        public function __invoke(ContainerInterface $container)
        {
            return Log::get('sys');
        }
    }
    
    • 申明依赖, 使用 StdoutLoggerInterface 的地方, 由实际依赖的 StdoutLoggerFactory 实例化的类来完成

    上面这么多的使用, 都还只在 monolog 中的 Logger 这里打转, 这里来看看 HandlerFormatter

    // config/autoload/logger.php
    $appEnv = env('APP_ENV', 'dev');
    if ($appEnv == 'dev') {
        $formatter = [
            'class' => \Monolog\Formatter\LineFormatter::class,
            'constructor' => [
                'format' => "||%datetime%||%channel%||%level_name%||%message%||%context%||%extra%\n",
                'allowInlineLineBreaks' => true,
                'includeStacktraces' => true,
            ],
        ];
    } else {
        $formatter = [
            'class' => \Monolog\Formatter\JsonFormatter::class,
            'constructor' => [],
        ];
    }
    
    return [
        'default' => [
            'handler' => [
                'class' => \Monolog\Handler\StreamHandler::class,
                'constructor' => [
                    'stream' => 'php://stdout',
                    'level' => \Monolog\Logger::INFO,
                ],
            ],
            'formatter' => $formatter,
        ],
    ]
    
    • 默认配置了名为 defaultHandler, 并包含了此 Handler 及其 Formatter 的信息
    • 获取 Logger 时, 如果没有指定 Handler, 底层会自动把 default 这一 Handler 绑定到 Logger
    • dev(开发)环境: 日志使用 php://stdout 输出到 标准输出(stdout), 并且 Formatter 中设置 allowInlineLineBreaks, 方便查看多行日志
    • 非 dev 环境: 日志使用 JsonFormatter, 会被格式为 json, 方便投递到第三方日志服务

    如果您希望日志文件可以按照日期轮转,可以通过 Mongolog 已经提供了的 Monolog\Handler\RotatingFileHandler 来实现,配置如下:

    <?php
    
    return [
        'default' => [
            'handler' => [
                'class' => Monolog\Handler\RotatingFileHandler::class,
                'constructor' => [
                    'filename' => BASE_PATH . '/runtime/logs/hyperf.log',
                    'level' => Monolog\Logger::DEBUG,
                ],
            ],
            'formatter' => [
                'class' => Monolog\Formatter\LineFormatter::class,
                'constructor' => [
                    'format' => null,
                    'dateFormat' => null,
                    'allowInlineLineBreaks' => true,
                ],
            ],
        ],
    ];
    

    如果您希望再进行更细粒度的日志切割,也可通过继承 Monolog\Handler\RotatingFileHandler 类并重新实现 rotate() 方法实现。