2019-12-12 16:24:04 +08:00
|
|
|
|
# 日誌
|
2019-03-30 12:29:38 +08:00
|
|
|
|
|
2019-12-12 16:24:04 +08:00
|
|
|
|
`hyperf/logger` 元件是基於 [psr/logger](https://github.com/php-fig/log) 實現的,預設使用 [monolog/monolog](https://github.com/Seldaek/monolog) 作為驅動,在 `hyperf-skeleton` 專案內預設提供了一些日誌配置,預設使用 `Monolog\Handler\StreamHandler`, 由於 `Swoole` 已經對 `fopen`, `fwrite` 等函式進行了協程化處理,所以只要不將 `useLocking` 引數設定為 `true`,就是協程安全的。
|
2019-03-30 12:29:38 +08:00
|
|
|
|
|
2019-12-12 16:24:04 +08:00
|
|
|
|
## 安裝
|
2019-03-30 12:29:38 +08:00
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
composer require hyperf/logger
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## 配置
|
|
|
|
|
|
2019-12-12 16:24:04 +08:00
|
|
|
|
在 `hyperf-skeleton` 專案內預設提供了一些日誌配置,預設情況下,日誌的配置檔案為 `config/autoload/logger.php` ,示例如下:
|
2019-03-30 12:29:38 +08:00
|
|
|
|
|
|
|
|
|
```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' => [
|
|
|
|
|
'format' => null,
|
|
|
|
|
'dateFormat' => null,
|
|
|
|
|
'allowInlineLineBreaks' => true,
|
|
|
|
|
]
|
|
|
|
|
],
|
|
|
|
|
],
|
|
|
|
|
];
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## 使用
|
|
|
|
|
|
|
|
|
|
```php
|
|
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
|
|
|
|
namespace App\Service;
|
|
|
|
|
|
2023-02-24 09:50:27 +08:00
|
|
|
|
use Psr\Log\LoggerInterface;
|
2019-03-30 12:29:38 +08:00
|
|
|
|
use Hyperf\Logger\LoggerFactory;
|
|
|
|
|
|
|
|
|
|
class DemoService
|
|
|
|
|
{
|
2021-12-01 16:19:47 +08:00
|
|
|
|
|
|
|
|
|
protected LoggerInterface $logger;
|
2019-03-30 12:29:38 +08:00
|
|
|
|
|
2019-05-23 18:02:03 +08:00
|
|
|
|
public function __construct(LoggerFactory $loggerFactory)
|
2019-03-30 12:29:38 +08:00
|
|
|
|
{
|
2019-12-12 16:24:04 +08:00
|
|
|
|
// 第一個引數對應日誌的 name, 第二個引數對應 config/autoload/logger.php 內的 key
|
2019-09-11 11:00:56 +08:00
|
|
|
|
$this->logger = $loggerFactory->get('log', 'default');
|
2019-03-30 12:29:38 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function method()
|
|
|
|
|
{
|
2021-07-06 17:34:40 +08:00
|
|
|
|
// Do something.
|
2019-03-30 12:29:38 +08:00
|
|
|
|
$this->logger->info("Your log message.");
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-05-16 20:18:43 +08:00
|
|
|
|
```
|
|
|
|
|
|
2019-12-12 16:24:04 +08:00
|
|
|
|
## 關於 monolog 的基礎知識
|
2019-05-16 20:18:43 +08:00
|
|
|
|
|
2019-12-12 16:24:04 +08:00
|
|
|
|
我們結合程式碼來看一些 `monolog` 中所涉及到的基礎概念:
|
2019-05-16 20:18:43 +08:00
|
|
|
|
|
|
|
|
|
```php
|
|
|
|
|
use Monolog\Formatter\LineFormatter;
|
|
|
|
|
use Monolog\Handler\FirePHPHandler;
|
|
|
|
|
use Monolog\Handler\StreamHandler;
|
2019-05-23 18:02:03 +08:00
|
|
|
|
use Monolog\Logger;
|
2019-05-16 20:18:43 +08:00
|
|
|
|
|
2019-12-12 16:24:04 +08:00
|
|
|
|
// 建立一個 Channel,引數 log 即為 Channel 的名字
|
2019-05-16 20:18:43 +08:00
|
|
|
|
$log = new Logger('log');
|
|
|
|
|
|
2019-12-12 16:24:04 +08:00
|
|
|
|
// 建立兩個 Handler,對應變數 $stream 和 $fire
|
2019-05-16 20:18:43 +08:00
|
|
|
|
$stream = new StreamHandler('test.log', Logger::WARNING);
|
|
|
|
|
$fire = new FirePHPHandler();
|
|
|
|
|
|
2019-12-12 16:24:04 +08:00
|
|
|
|
// 定義時間格式為 "Y-m-d H:i:s"
|
2019-05-16 20:18:43 +08:00
|
|
|
|
$dateFormat = "Y n j, g:i a";
|
2019-12-12 16:24:04 +08:00
|
|
|
|
// 定義日誌格式為 "[%datetime%] %channel%.%level_name%: %message% %context% %extra%\n"
|
2019-05-16 20:18:43 +08:00
|
|
|
|
$output = "%datetime%||%channel||%level_name%||%message%||%context%||%extra%\n";
|
2019-12-12 16:24:04 +08:00
|
|
|
|
// 根據 時間格式 和 日誌格式,建立一個 Formatter
|
2019-05-16 20:18:43 +08:00
|
|
|
|
$formatter = new LineFormatter($output, $dateFormat);
|
|
|
|
|
|
2019-12-12 16:24:04 +08:00
|
|
|
|
// 將 Formatter 設定到 Handler 裡面
|
2019-05-16 20:18:43 +08:00
|
|
|
|
$stream->setFormatter($formatter);
|
|
|
|
|
|
2019-12-12 16:24:04 +08:00
|
|
|
|
// 將 Handler 推入到 Channel 的 Handler 佇列內
|
2019-05-16 20:18:43 +08:00
|
|
|
|
$log->pushHandler($stream);
|
|
|
|
|
$log->pushHandler($fire);
|
|
|
|
|
|
|
|
|
|
// clone new log channel
|
|
|
|
|
$log2 = $log->withName('log2');
|
|
|
|
|
|
|
|
|
|
// add records to the log
|
|
|
|
|
$log->warning('Foo');
|
|
|
|
|
|
|
|
|
|
// add extra data to record
|
|
|
|
|
// 1. log context
|
|
|
|
|
$log->error('a new user', ['username' => 'daydaygo']);
|
|
|
|
|
// 2. processor
|
|
|
|
|
$log->pushProcessor(function ($record) {
|
|
|
|
|
$record['extra']['dummy'] = 'hello';
|
|
|
|
|
return $record;
|
|
|
|
|
});
|
|
|
|
|
$log->pushProcessor(new \Monolog\Processor\MemoryPeakUsageProcessor());
|
|
|
|
|
$log->alert('czl');
|
|
|
|
|
```
|
|
|
|
|
|
2019-12-12 16:24:04 +08:00
|
|
|
|
- 首先, 例項化一個 `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` 固定新增, 比較適合收集一些 **常見資訊**
|
2019-05-16 20:18:43 +08:00
|
|
|
|
|
2019-05-23 18:02:03 +08:00
|
|
|
|
## 更多用法
|
|
|
|
|
|
2019-12-12 16:24:04 +08:00
|
|
|
|
### 封裝 `Log` 類
|
2019-05-16 20:18:43 +08:00
|
|
|
|
|
2022-12-27 13:37:04 +08:00
|
|
|
|
可能有些時候您更想保持大多數框架使用日誌的習慣,那麼您可以在 `App` 下建立一個 `Log` 類,並透過 `__callStatic` 魔術方法靜態方法呼叫實現對 `Logger` 的取用以及各個等級的日誌記錄,我們透過程式碼來演示一下:
|
2019-05-16 20:18:43 +08:00
|
|
|
|
|
|
|
|
|
```php
|
|
|
|
|
namespace App;
|
|
|
|
|
|
2021-07-06 18:54:56 +08:00
|
|
|
|
use Hyperf\Logger\LoggerFactory;
|
2023-04-11 15:57:32 +08:00
|
|
|
|
use Hyperf\Context\ApplicationContext;
|
2019-05-16 20:18:43 +08:00
|
|
|
|
|
|
|
|
|
class Log
|
|
|
|
|
{
|
2019-07-14 19:01:07 +08:00
|
|
|
|
public static function get(string $name = 'app')
|
2019-05-16 20:18:43 +08:00
|
|
|
|
{
|
2021-07-06 18:54:56 +08:00
|
|
|
|
return ApplicationContext::getContainer()->get(LoggerFactory::class)->get($name);
|
2019-05-16 20:18:43 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2022-12-27 13:37:04 +08:00
|
|
|
|
預設使用 `Channel` 名為 `app` 來記錄日誌,您也可以透過使用 `Log::get($name)` 方法獲得不同 `Channel` 的 `Logger`, 強大的 `容器(Container)` 幫您解決了這一切
|
2019-05-16 20:18:43 +08:00
|
|
|
|
|
2019-12-12 16:24:04 +08:00
|
|
|
|
### stdout 日誌
|
2019-05-16 20:18:43 +08:00
|
|
|
|
|
2022-12-27 13:37:04 +08:00
|
|
|
|
框架元件所輸出的日誌在預設情況下是由 `Hyperf\Contract\StdoutLoggerInterface` 介面的實現類 `Hyperf\Framework\Logger\StdoutLogger` 提供支援的,該實現類只是為了將相關的資訊透過 `print_r()` 輸出在 `標準輸出(stdout)`,即為啟動 `Hyperf` 的 `終端(Terminal)` 上,也就意味著其實並沒有使用到 `monolog` 的,那麼如果想要使用 `monolog` 來保持一致要怎麼處理呢?
|
2019-05-16 20:18:43 +08:00
|
|
|
|
|
2022-12-27 13:37:04 +08:00
|
|
|
|
是的, 還是透過強大的 `容器(Container)`.
|
2019-05-16 20:18:43 +08:00
|
|
|
|
|
2019-12-12 16:41:17 +08:00
|
|
|
|
- 首先, 實現一個 `StdoutLoggerFactory` 類,關於 `Factory` 的用法可在 [依賴注入](zh-tw/di.md) 章節獲得更多詳細的說明。
|
2019-05-16 20:18:43 +08:00
|
|
|
|
|
|
|
|
|
```php
|
2019-05-23 18:02:03 +08:00
|
|
|
|
<?php
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
2019-05-16 20:18:43 +08:00
|
|
|
|
namespace App;
|
|
|
|
|
|
|
|
|
|
use Psr\Container\ContainerInterface;
|
|
|
|
|
|
|
|
|
|
class StdoutLoggerFactory
|
|
|
|
|
{
|
|
|
|
|
public function __invoke(ContainerInterface $container)
|
|
|
|
|
{
|
|
|
|
|
return Log::get('sys');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2021-07-06 18:54:56 +08:00
|
|
|
|
- 宣告依賴, 使用 `StdoutLoggerInterface` 的地方, 由實際依賴的 `StdoutLoggerFactory` 例項化的類來完成
|
2019-05-16 20:18:43 +08:00
|
|
|
|
|
|
|
|
|
```php
|
2019-10-08 01:00:41 +08:00
|
|
|
|
// config/autoload/dependencies.php
|
2019-05-16 20:18:43 +08:00
|
|
|
|
return [
|
2019-10-08 01:00:41 +08:00
|
|
|
|
\Hyperf\Contract\StdoutLoggerInterface::class => \App\StdoutLoggerFactory::class,
|
2019-05-16 20:18:43 +08:00
|
|
|
|
];
|
|
|
|
|
```
|
|
|
|
|
|
2019-12-12 16:24:04 +08:00
|
|
|
|
### 不同環境下輸出不同格式的日誌
|
2019-05-16 20:18:43 +08:00
|
|
|
|
|
2019-12-12 16:24:04 +08:00
|
|
|
|
上面這麼多的使用, 都還只在 monolog 中的 `Logger` 這裡打轉, 這裡來看看 `Handler` 和 `Formatter`
|
2019-05-16 20:18:43 +08:00
|
|
|
|
|
|
|
|
|
```php
|
|
|
|
|
// config/autoload/logger.php
|
2019-05-23 18:02:03 +08:00
|
|
|
|
$appEnv = env('APP_ENV', 'dev');
|
|
|
|
|
if ($appEnv == 'dev') {
|
2019-05-16 20:18:43 +08:00
|
|
|
|
$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,
|
|
|
|
|
],
|
2021-07-06 18:54:56 +08:00
|
|
|
|
];
|
2019-05-16 20:18:43 +08:00
|
|
|
|
```
|
|
|
|
|
|
2019-12-12 16:24:04 +08:00
|
|
|
|
- 預設配置了名為 `default` 的 `Handler`, 幷包含了此 `Handler` 及其 `Formatter` 的資訊
|
|
|
|
|
- 獲取 `Logger` 時, 如果沒有指定 `Handler`, 底層會自動把 `default` 這一 `Handler` 繫結到 `Logger` 上
|
|
|
|
|
- dev(開發)環境: 日誌使用 `php://stdout` 輸出到 `標準輸出(stdout)`, 並且 `Formatter` 中設定 `allowInlineLineBreaks`, 方便檢視多行日誌
|
|
|
|
|
- 非 dev 環境: 日誌使用 `JsonFormatter`, 會被格式為 `json`, 方便投遞到第三方日誌服務
|
2019-07-17 14:54:52 +08:00
|
|
|
|
|
2019-12-12 16:24:04 +08:00
|
|
|
|
### 日誌檔案按日期輪轉
|
2019-07-17 14:54:52 +08:00
|
|
|
|
|
2022-12-27 13:37:04 +08:00
|
|
|
|
如果您希望日誌檔案可以按照日期輪轉,可以透過 `Mongolog` 已經提供了的 `Monolog\Handler\RotatingFileHandler` 來實現,配置如下:
|
2019-07-17 14:54:52 +08:00
|
|
|
|
|
2019-12-12 16:24:04 +08:00
|
|
|
|
修改 `config/autoload/logger.php` 配置檔案,將 `Handler` 改為 `Monolog\Handler\RotatingFileHandler::class`,並將 `stream` 欄位改為 `filename` 即可。
|
2019-07-17 14:54:52 +08:00
|
|
|
|
|
|
|
|
|
```php
|
|
|
|
|
<?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,
|
|
|
|
|
],
|
|
|
|
|
],
|
|
|
|
|
],
|
|
|
|
|
];
|
|
|
|
|
```
|
2019-07-17 15:03:21 +08:00
|
|
|
|
|
2022-12-27 13:37:04 +08:00
|
|
|
|
如果您希望再進行更細粒度的日誌切割,也可透過繼承 `Monolog\Handler\RotatingFileHandler` 類並重新實現 `rotate()` 方法實現。
|
2019-11-08 11:22:09 +08:00
|
|
|
|
|
2019-12-12 16:24:04 +08:00
|
|
|
|
### 配置多個 `Handler`
|
2019-11-08 11:22:09 +08:00
|
|
|
|
|
2021-07-06 18:54:56 +08:00
|
|
|
|
使用者可以修改 `handlers` 讓對應日誌組支援多個 `handler`。比如以下配置,當用戶投遞一個 `INFO` 級別以上的日誌時,會在 `hyperf.log` 和 `hyperf-debug.log` 寫入日誌。
|
|
|
|
|
當用戶投遞一個 `DEBUG` 級別日誌時,只會在 `hyperf-debug.log` 中寫入日誌。
|
2019-11-08 11:22:09 +08:00
|
|
|
|
|
|
|
|
|
```php
|
|
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
|
|
|
|
use Monolog\Handler;
|
|
|
|
|
use Monolog\Formatter;
|
|
|
|
|
use Monolog\Logger;
|
|
|
|
|
|
|
|
|
|
return [
|
|
|
|
|
'default' => [
|
|
|
|
|
'handlers' => [
|
|
|
|
|
[
|
|
|
|
|
'class' => Handler\StreamHandler::class,
|
|
|
|
|
'constructor' => [
|
|
|
|
|
'stream' => BASE_PATH . '/runtime/logs/hyperf.log',
|
|
|
|
|
'level' => Logger::INFO,
|
|
|
|
|
],
|
|
|
|
|
'formatter' => [
|
|
|
|
|
'class' => Formatter\LineFormatter::class,
|
|
|
|
|
'constructor' => [
|
|
|
|
|
'format' => null,
|
|
|
|
|
'dateFormat' => null,
|
|
|
|
|
'allowInlineLineBreaks' => true,
|
|
|
|
|
],
|
|
|
|
|
],
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
'class' => Handler\StreamHandler::class,
|
|
|
|
|
'constructor' => [
|
|
|
|
|
'stream' => BASE_PATH . '/runtime/logs/hyperf-debug.log',
|
|
|
|
|
'level' => Logger::DEBUG,
|
|
|
|
|
],
|
|
|
|
|
'formatter' => [
|
|
|
|
|
'class' => Formatter\JsonFormatter::class,
|
|
|
|
|
'constructor' => [
|
|
|
|
|
'batchMode' => Formatter\JsonFormatter::BATCH_MODE_JSON,
|
|
|
|
|
'appendNewline' => true,
|
|
|
|
|
],
|
|
|
|
|
],
|
|
|
|
|
],
|
|
|
|
|
],
|
|
|
|
|
],
|
|
|
|
|
];
|
|
|
|
|
```
|
|
|
|
|
|
2019-12-12 16:24:04 +08:00
|
|
|
|
結果如下
|
2019-11-08 11:22:09 +08:00
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
==> runtime/logs/hyperf.log <==
|
|
|
|
|
[2019-11-08 11:11:35] hyperf.INFO: 5dc4dce791690 [] []
|
|
|
|
|
|
|
|
|
|
==> runtime/logs/hyperf-debug.log <==
|
|
|
|
|
{"message":"5dc4dce791690","context":[],"level":200,"level_name":"INFO","channel":"hyperf","datetime":{"date":"2019-11-08 11:11:35.597153","timezone_type":3,"timezone":"Asia/Shanghai"},"extra":[]}
|
|
|
|
|
{"message":"xxxx","context":[],"level":100,"level_name":"DEBUG","channel":"hyperf","datetime":{"date":"2019-11-08 11:11:35.597635","timezone_type":3,"timezone":"Asia/Shanghai"},"extra":[]}
|
|
|
|
|
```
|