Last update: 09 Nov 2022
You want to know what's happening "under the hood" of your application? ZAPHYR provides a robust PSR-3 logging service.
There are basically two ways to start logging. Once by directly calling a Logger
instance and by using a
LogManager
instance. We will take a closer look at both options and their advantages and disadvantages below.
A Logger
instance does not require much configuration effort:
$logger = new Zaphyr\Logger\Logger('app', [
new Zaphyr\Logger\Handlers\FileHandler('/path/to/log/file.log')
]);
However, if you want to use multiple log channels in an application, the use can quickly become confusing. You can read more about the available log handlers here.
A little more configuration work is required to use a LogManager
instance. On the other hand, using a
log manager after a successful configuration is easier and simplifies the use of multiple
log handlers. So let's build a LogManager
instance first:
$logManager = new LogManager([
'dev',
[
'dev' => [
'handlers' => [
'file' => [
'storagePath' => '/path/to/log/file.log',
],
],
],
'prod' => [
'handlers' => [
'rotate' => [
'interval' => 'day',
'storagePath' => '/path/to/log/directory',
],
'mail' => [
'dsn' => 'smtp://user:pass@smtp.example.com:25',
'from' => 'noreply@example.com',
'to' => 'admin@example.com',
'subject' => 'Log message received',
],
],
],
]
]);
The configuration for a log manager instance may seem a bit confusing at first. So let's take a look at each part of the configuration.
In line 2 of our LogManager
example we first define a default log channel, in this case dev
. Whenever we make a log
entry with the log manager and do not pass a log channel to the logger()
method, the default log channel is used. You
can read more about using the logger()
method here.
$devLogger = $logManager->logger(); // Uses the default "dev" log channel
In lines 4 to 10 we now configure our dev
log channel, which was already defined in line 2 of our example as the
default log channel. Inside the dev
configuration we define our log handlers to be used for this log channel.
We will learn more about the various log handlers later.
In lines 11 to 24 we have defined another log channel, in this case named prod
. If we call the logger()
method with
the parameter prod
, the logger entries of the defined log channel are used:
$prodLogger = $logManager->logger('prod'); // Uses the defined "prod" log channel
Limitations:
Currently, the
LogManager
only allows usage of the available log handlers. It is currently not possible to use custom log handlers without extending theLogManager
. However, such a functionality is planned and will be implemented in the near future!
When it comes to logging in your application, this logging service offers you different log levels as described below.
If you have created a logging stack with a Logger
instance, you can easily create log messages:
$logger->alert("Something went wrong!");
You can also pass an array as context data to log methods. This is meant to hold extra information that does not fit well in a string:
$logger->info('User {username} created', ['username' => 'John Doe']);
Once you have created a logging stack with a LogManager
instance, you can call the logger()
method and start
logging. The LogManager
instance also offers you the possibility to select different Logger
instances for the
respective logging.
If you want to call the default logger, just use the logger()
method without any additional parameters. After calling
the logger()
method you can use any available log level method:
$logManager->logger()->alert("Something went wrong!");
If you want to use a different Logger
instance than default, pass the name of the desired logger to the logger()
method:
$logManager->logger('app')->info('User {username} created', ['username' => 'John Doe']);
You can read how to configure different loggers with the LogManager
here.
The logging service, offers all the log levels defined in the
RFC 5424 specification. In descending order of severity, these log
levels are: emergency
, alert
, critical
, error
, warning
, notice
, info
, and debug
.
Level | Description |
---|---|
$logger->emergency() |
System is unusable |
$logger->alert() |
Action must be taken immediately |
$logger->critical() |
Critical conditions |
$logger->error() |
Error conditions |
$logger->warning() |
Warning conditions |
$logger->notice() |
Normal but significant condition |
$logger->info() |
Informational messages |
$logger->debug() |
Debug-level messages |
The logging service comes with a handful of log handlers. They decide how log messages reach you or the responsible project owner. If the default handlers are not enough, you can simply create a custom handler.
The FileHandler
is the simplest of all handlers. This handler simply writes log messages to the log file
passed in the constructor. A new log entry is written at the end of the file:
$fileHandler = new Zaphyr\Logger\Handlers\FileHandler('/path/to/log/file.log');
If the log file specified in the constructor does not exist, the FileHandler
instance creates it on its own.
Warning:
The
FileHandler
stores log messages in a single file. So this handler should either be used in small projects or it should be ensured that the log file is deleted or overwritten at regular intervals. Otherwise, this log file can reach a considerable size and may overload the available storage space of your server!
The MailHandler
does exactly what the name says – sending log messages as emails. This handler uses the
symfony\mail
package to send messages. You can read more about the symfony mailer package
here.
So you first create the email configuration and then inject it to your MailHandler
instance:
$transport = Symfony\Component\Mailer\Transport::fromDsn('smtp://localhost');
$mailer = new Symfony\Component\Mailer\Mailer($transport);
$email = (new Symfony\Component\Mime\Email())
->from('noreply@example.com')
->to('admin@example.com')
->subject('Log message received');
$mailHandler = new Zaphyr\Logger\Handlers\MailHandler($mailer, $email);
Now you will get all log messages sent to the email address specified in your email configuration.
The RotateHandler
is also a log file handler. However, an interval can be set for this handler,
at which intervals a new log file should be created. By default, this handler creates a new log file for each day.
Simply inject the path to the folder in which the log files should be saved and enter the desired interval:
$rotateHandler = new Zaphyr\Logger\Handlers\RotateHandler('/path/to/log/directory', 'day');
If the directory for the log files does not exist, the RotateHandler
creates this directory independently.
The following intervals can be used to create log files:
Interval | Description |
---|---|
hour |
Creates a new log file every hour |
day |
Creates a new log file every day |
week |
Creates a new log file every week |
month |
Creates a new log file every month |
year |
Creates a new log file every year |
It is also possible to create a custom log handler. To do this, you first create your own handler class which
implements the HandlerInterface
:
class MyCustomHandler implements Zaphyr\Logger\Contracts\HandlerInterface
{
/**
* {@inheritdoc}
*/
public function add(string $name, string $level, string $message, array $context = []): void
{
// your custom handler logic
}
}
Now you can pass your previously created handler instance to a Logger
and use the logger as already described:
$handlers = [
new MyCustomHandler(),
];
$logger = new Logger('app', $handlers);
As you may have seen in the previous examples, multiple log handlers can be triggered simultaneously as soon as a log message is registered.
For example, if you not only want to store a log message in a file, but also send it as an email to a responsible administrator, you can do this for the use of a Logger instance, as follows:
$transport = Symfony\Component\Mailer\Transport::fromDsn('smtp://localhost');
$mailer = new Symfony\Component\Mailer\Mailer($transport);
$email = (new Symfony\Component\Mime\Email())
->from('noreply@example.com')
->to('admin@example.com')
->subject('Log message received');
$handlers = [
new Zaphyr\Logger\Handlers\FileHandler('/path/to/log/file.log'),
new Zaphyr\Logger\Handlers\MailHandler($mailer, $email),
];
$logger = new Zaphyr\Logger\Logger('app', $handlers);
The use of multiple log handlers is also possible with the use of a LogManager instance:
$defaultChannel ='app';
$channels = [
'app' => [
'handlers' => [
'rotate' => [
'interval' => 'day',
'storagePath' => '/path/to/log/directory',
],
'mail' => [
'dsn' => 'smtp://user:pass@smtp.example.com:25',
'from' => 'noreply@example.com',
'to' => 'admin@example.com',
'subject' => 'Log message received',
],
],
],
];
$logManager = new Zapyhr\Logger\LogManager($defaultChannel, $channels);
Formatters, as the name suggests, determine the format in which log messages are written to the log files.
By default, the logging service offers only one formatter, the DefaultFormatter
.
The default formatter creates a log message in the following format:
[{date}] {name}.{level}: {message} [{context}] [{exception}]
// [2022-09-13 09:41:00] app.alert: Something went wrong! […] […]
It could be possible that you want to save a log message in a different format. In this case you can simply create a custom formatter instance:
class MyCustomFormatter implements Zaphyr\Logger\Contracts\FormatterInterface
{
/**
* {@inheritdoc}
*/
public function interpolate(string $name, string $level, string $message, array $context = []): string
{
// Your custom formatter logic
}
}
A good starting point for creating your custom formatter is to take a look at the DefaultFormatter.
After you have created your custom formatter, you pass it to your handler instance:
$formatter = new MyCustomFormatter();
$handler = new Zaphyr\Logger\Handlers\FileHandler('/path/to/file.log', $formatter);
Now your log messages will be written in your custom format.