Co-authored-by: 李铭昕 <715557344@qq.com>
11 KiB
Logger
The hyperf/logger
component is implemented based on psr/logger, and monolog/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.
Installation
composer require hyperf/logger
Configuration
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:
<?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
<?php
declare(strict_types=1);
namespace App\Service;
use Psr\Container\ContainerInterface;
use Hyperf\Logger\LoggerFactory;
class DemoService
{
/**
* @var \Psr\Log\LoggerInterface
*/
protected $logger;
public function __construct(LoggerFactory $loggerFactory)
{
// 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');
}
public function method()
{
// Do something.
$this->logger->info("Your log message.");
}
}
Basic knowledge about monolog
Let's take a look at some of the basic concepts involved in monolog with the following code:
use Monolog\Formatter\LineFormatter;
use Monolog\Handler\FirePHPHandler;
use Monolog\Handler\StreamHandler;
use Monolog\Logger;
// Create a Channel. The parameter log is the name of the Channel
$log = new Logger('log');
// Create two Handlers, corresponding to variables $stream and $fire
$stream = new StreamHandler('test.log', Logger::WARNING);
$fire = new FirePHPHandler();
// Define the time format as "Y-m-d H:i:s"
$dateFormat = "Y n j, g:i a";
// Define the log format as "[%datetime%] %channel%.%level_name%: %message% %context% %extra%\n"
$output = "%datetime%||%channel||%level_name%||%message%||%context%||%extra%\n";
// Create a Formatter based on the time format and log format
$formatter = new LineFormatter($output, $dateFormat);
// Set Formatter to Handler
$stream->setFormatter($formatter);
// Push the Handler into the Handler queue of the Channel
$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');
- Firstly, instantiate a
Logger
and take a name which corresponds tochannel
- You can bind multiple
Handler
toLogger
.Logger
performs log, and hand it over toHandler
for processing Handler
can specify which log level logs need to be processed, such asLogger::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 correspondingHandler
- What parts of the log included:
"%datetime%||%channel||%level_name%||%message%||%context%||%extra%\n"
- Distinguish the extra information added in the log
context
andextra
: Thecontext
is additionally specified by the user when logging, which is more flexible; And theextra
is fixedly added by theProcessor
bound to theLogger
, which is more suitable for collecting some common information
More usage
Encapsulate the Log
class
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. Let’s demonstrate through code:
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);
}
}
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
stdout log
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?
Absolutely, it is through the powerful Container
.
- First, implement a
StdoutLoggerFactory
class. The usage ofFactory
can be explained in more detail in the Dependency Injection chapter.
<?php
declare(strict_types=1);
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 dependentStdoutLoggerFactory
// config/autoload/dependencies.php
return [
\Hyperf\Contract\StdoutLoggerInterface::class => \App\StdoutLoggerFactory::class,
];
Output different format logs in different environments
So many uses of the above are only for the Logger
in the monolog. Let's take a look at Handler
and 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,
],
]
- A
Handler
nameddefault
is configured by default, and contains the information of thisHandler
and itsFormatter
- When obtaining the
Logger
, if theHandler
is not specified, the bottom layer will automatically bind thedefault(Handler)
to theLogger
- dev (development) environment: Use
php://stdout
to output logs tostdout
, and setallowInlineLineBreaks
inFormatter
, which is convenient for viewing multi-line logs - Non-dev environment: The log uses
JsonFormatter
, which will be formatted asjson
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
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
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":[]}