hyperf/docs/en/logger.md

325 lines
11 KiB
Markdown
Raw Normal View History

# Logger
2019-03-30 12:29:38 +08:00
The `hyperf/logger` component is implemented based on [psr/logger](https://github.com/php-fig/log), and [monolog/monolog](https://github.com/Seldaek/monolog) is used by default as a driver. Some log configurations are provided by default in the `hyperf-skeleton` project, and `Monolog\Handler\StreamHandler` is used by default. Since `Swoole` has already coroutineized functions such as `fopen`, `fwrite`, so long as the `useLocking` parameter is not set to `true`, the coroutine is safe.
2019-03-30 12:29:38 +08:00
2019-12-12 16:24:04 +08:00
## Installation
2019-03-30 12:29:38 +08:00
```
composer require hyperf/logger
```
## Configuration
2019-03-30 12:29:38 +08:00
Some log configurations are provided by default in the `hyperf-skeleton` project. By default, the log configuration file is `config/autoload/logger.php`. An example is as follows:
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,
]
],
],
];
```
## Instruction for use
2019-03-30 12:29:38 +08:00
```php
<?php
declare(strict_types=1);
namespace App\Service;
use Psr\Container\ContainerInterface;
use Hyperf\Logger\LoggerFactory;
class DemoService
{
2019-05-23 18:02:03 +08:00
/**
* @var \Psr\Log\LoggerInterface
2019-05-23 18:02:03 +08:00
*/
2019-03-30 12:29:38 +08:00
protected $logger;
2019-05-23 18:02:03 +08:00
public function __construct(LoggerFactory $loggerFactory)
2019-03-30 12:29:38 +08:00
{
// The first parameter corresponds to the name of the log, and the second parameter corresponds to the key in config/autoload/logger.php
$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
```
## Basic knowledge about monolog
2019-05-16 20:18:43 +08:00
Let's take a look at some of the basic concepts involved in monolog with the following code:
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
// Create a Channel. The parameter log is the name of the Channel
2019-05-16 20:18:43 +08:00
$log = new Logger('log');
// Create two Handlers, corresponding to variables $stream and $fire
2019-05-16 20:18:43 +08:00
$stream = new StreamHandler('test.log', Logger::WARNING);
$fire = new FirePHPHandler();
// Define the time format as "Y-m-d H:i:s"
2019-05-16 20:18:43 +08:00
$dateFormat = "Y n j, g:i a";
// Define the log format as "[%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";
// Create a Formatter based on the time format and log format
2019-05-16 20:18:43 +08:00
$formatter = new LineFormatter($output, $dateFormat);
// Set Formatter to Handler
2019-05-16 20:18:43 +08:00
$stream->setFormatter($formatter);
// Push the Handler into the Handler queue of the Channel
2019-05-16 20:18:43 +08:00
$log->pushHandler($stream);
$log->pushHandler($fire);
// Clone new log channel
2019-05-16 20:18:43 +08:00
$log2 = $log->withName('log2');
// Add records to the log
2019-05-16 20:18:43 +08:00
$log->warning('Foo');
// Add extra data to record
2019-05-16 20:18:43 +08:00
// 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');
```
- Firstly, instantiate a `Logger` and take a name which corresponds to `channel`
- You can bind multiple `Handler` to `Logger`. `Logger` performs log, and hand it over to `Handler` for processing
- `Handler` can specify which **log level** logs need to be processed, such as `Logger::WARNING` or only process logs with log level `>=Logger::WARNING`
- Who will format the log? The `Formatter` will. Just set the Formatter and bind it to the corresponding `Handler`
- What parts of the log included: `"%datetime%||%channel||%level_name%||%message%||%context%||%extra%\n"`
- Distinguish the extra information added in the log `context` and `extra`: The `context` is additionally specified by the user when logging, which is more flexible; And the `extra` is fixedly added by the `Processor` bound to the `Logger`, which is more suitable for collecting some **common information**
2019-05-16 20:18:43 +08:00
## More usage
2019-05-23 18:02:03 +08:00
### Encapsulate the `Log` class
2019-05-16 20:18:43 +08:00
Sometimes, you may wish to keep the habit of logging in most frameworks. Then you can create a `Log` class under `App`, and call the magic static method `__callStatic` to access to `Logger` and each Level of logging. Lets demonstrate through code:
2019-05-16 20:18:43 +08:00
```php
namespace App;
use Hyperf\Logger\Logger;
use Hyperf\Utils\ApplicationContext;
2019-05-16 20:18:43 +08:00
class Log
{
public static function get(string $name = 'app')
2019-05-16 20:18:43 +08:00
{
return ApplicationContext::getContainer()->get(\Hyperf\Logger\LoggerFactory::class)->get($name);
2019-05-16 20:18:43 +08:00
}
}
2019-05-16 20:18:43 +08:00
```
By default, a `Channel` named `app` is used to record logs. You can also use the `Log::get($name)` method to obtain the `Logger` of different `Channels`. The powerful `Container` can help you to solve it all
2019-05-16 20:18:43 +08:00
### stdout log
2019-05-16 20:18:43 +08:00
By default, the log output by the framework components is supported by the implementation class of the interface `Hyperf\Contract\StdoutLoggerInterface`, the `Hyperf\Framework\Logger\StdoutLogger`. This implementation class is just to output the relevant information on the `stdout` through `print_r()`, which is the `terminal` that starts `Hyperf`. In this case, `monolog` is not actually used. What if you want to use `monolog` to be consistent?
2019-05-16 20:18:43 +08:00
Absolutely, it is through the powerful `Container`.
2019-05-16 20:18:43 +08:00
- First, implement a `StdoutLoggerFactory` class. The usage of `Factory` can be explained in more detail in the [Dependency Injection](zh-cn/di.md) chapter.
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');
}
}
```
- Declare the dependency, the work of `StdoutLoggerInterface` is done by the class instantiated by the actual dependent `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
];
```
### Output different format logs in different environments
2019-05-16 20:18:43 +08:00
So many uses of the above are only for the `Logger` in the monolog. Let's take a look at `Handler` and `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,
],
]
```
- A `Handler` named `default` is configured by default, and contains the information of this `Handler` and its `Formatter`
- When obtaining the `Logger`, if the `Handler` is not specified, the bottom layer will automatically bind the `default(Handler)` to the `Logger`
- dev (development) environment: Use `php://stdout` to output logs to `stdout`, and set `allowInlineLineBreaks` in `Formatter`, which is convenient for viewing multi-line logs
- Non-dev environment: The log uses `JsonFormatter`, which will be formatted as `json` and is convenient for delivery to third-party log services
### Rotate log files by date
If you want the log file to be rotated according to the date, you can use the `Monolog\Handler\RotatingFileHandler` provided by `Mongolog`. And the configuration is as follows:
Modify the `config/autoload/logger.php` configuration file, change `Handler` to `Monolog\Handler\RotatingFileHandler::class` and change the `stream` field to `filename`.
```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,
],
],
],
];
```
If you want to perform more fine-grained log cutting, you can also extend the `Monolog\Handler\RotatingFileHandler` class and reimplement the `rotate()` method.
### Configure multiple `Handler`
Users can modify `handlers` so that the corresponding log group can supports multiple `handlers`.
For example, in the following configuration, when a user posts a log higher the level of `INFO`, it will be written in `hyperf.log` and `hyperf-debug.log`.
When a user posts a `DEBUG` log, the log will be written only in `hyperf-debug.log`.
```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,
],
],
],
],
],
];
```
The result is as follows
```
==> 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":[]}
```