日志
在 hyperf-skeleton
项目内默认提供了一些日志配置,默认情况下,日志的配置文件为 config/autoload/logger.php
,示例如下:
<?php
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' => [
'dateFormat' => null,
'allowInlineLineBreaks' => true,
]
],
],
];
<?php
declare(strict_types=1);
namespace App\Service;
use Psr\Container\ContainerInterface;
use Hyperf\Logger\LoggerFactory;
class DemoService
{
/**
*/
protected $logger;
{
// 第一个参数对应日志的 name, 第二个参数对应 config/autoload/logger.php 内的 key
$this->logger = $loggerFactory->get('log', 'default');
}
public function method()
{
// Do somthing.
$this->logger->info("Your log message.");
}
}
我们结合代码来看一些 monolog
中所涉及到的基础概念:
- 首先, 实例化一个
Logger
, 取个名字, 名字对应的就是channel
- 可以为
Logger
绑定多个Handler
,Logger
打日志, 交由Handler
来处理 Handler
可以指定需要处理哪些 日志级别 的日志, 比如Logger::WARNING
, 只处理日志级别>=Logger::WARNING
的日志- 谁来格式化日志?
Formatter
, 设置好 Formatter 并绑定到相应的Handler
上 - 日志包含哪些部分:
"%datetime%||%channel||%level_name%||%message%||%context%||%extra%\n"
- 区分一下日志中添加的额外信息
context
和extra
: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
这里打转, 这里来看看 Handler
和 Formatter
// 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,
],
]
- 默认配置了名为
default
的Handler
, 并包含了此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()
方法实现。