Loggers in Magento and how to create them
This article deals with one of the most versatile tools used when writing code - loggers. Apart from explaining how to create your own log handler in your separated files, we’ll have a look at how they work. You can find the most important information about loggers in Magento below.
Why (and what) to log?
The purpose of logging events occuring when running code is the ability to go back to whatever happened and analyze the executed code, step by step. It allows you to find an error that happened a few days ago but hasn't been reported until now. Logging can also notify you instantly whenever a critical error occurs.
Logs are valuable sources of information. Take system logs or unsuccessful SSH login attempts logs, not stricte Magento cases. They help us keep track of past events or ones we’re not able to control real-time.
You’ve probably seen Magento logs, especially those in the
var/log/exception.log files. In production mode, there are also reports (
var/report) that are generated to hide an error on the frontend and prevent a page or its part to display for a user.
LoggerInterface and Monolog
You could’ve also noticed - whether in articles or directly in code - that to use the logger, you must inject the
\Psr\Log\LoggerInterface interface. PSR stands for PHP Standard Recommendation and it’s been manufactured by PHP Framework Interop Group (PHP-FIG). The logger is derived from PSR-3 that describes the logger construction in PHP applications: PSR-3: Logger Interface - PHP-FIG.
PHP-FIG released an official package containing the definition of interfaces: GitHub - php-fig/log. The LoggerInterface itself contains a declaration of public methods (compliant with PSR-3) that should be included in the interface implementation. Thanks to this, we can also be sure we can log through the correct usage of methods that mirror the logged information (like debug, info, warning, critical, etc.). It doesn’t matter what exact logger is used in the application.
Concluding, in Magento, injecting an interface to the class will result in using a specific implementation,
\Magento\Framework\Logger\Monolog, which in turn is a simple extension of
\Monolog\Logger\Logger. On another note, the Magento extension of Monolog allows fetching messages from
Exception, if it has been passed to the logger.
Every type of log (debug, info, etc.) has its value, distinguished by its handler. A logger choses a proper handler based on the log type and processes the log in the right class. Below you will find a list of available log types along with their properties and short descriptions.
Each type has its equivalent method (the aforementioned
LoggerInterface). Based on the above, you can use the right methods and the logger will define how to process a given log.
Monolog provides us with the right methods that we should use in our code by implementing the
LoggerInterface interface. It also allows using different handlers. It means we can save a log to a file, a database or other places. The class itself doesn’t know where a log is saved to - it uses the handler for that.
Apart from implementing log processing, handlers can check if a given handler should process a specific type of log. E.g. we can save debug logs to a file and emergency logs can be sent by a text message. Also, handlers allow us to use log processors and formatters.
Create your own logger
Once we’re done with the theoretical part, let’s say we simply want to log information from a module to a specific place. To simplify, let’s use a handler that saves logs to a file (If you’d like to see how to create a handler saving logs e.g. to a database or using API, let me know in the comments!).
- We want to save logs to specific files
- Logs of the NOTICE level and below will be saved to the
- Logs above the NOTICE level will be saved to the
- Logs of the NOTICE level and below can be turned on and off at will. We don’t want the module to always log tons of information if they’re not needed.
- Logs above the NOTICE level will always be saved.
I won’t be creating a module from scratch. I will use a module I wrote for the previous article.
Classes for saving logs
First, let’s create two classes responsible for saving logs to different places - e.g. for logs below and above the NOTICE level.
Both these classes extend
\Magento\Framework\Logger\Handler\Base that has a function to save logs to files.
The difference between the classes is pretty simple. In the
Debug handler, we modify the parent method
write and check if our “debug mode” is on. The
Error handler only contains the modification of the
Important - if you look at the logic of picking a handler by the logger, you’ll find a following line:
return $record['level'] >= $this->level;
It means that if a value (constants shown earlier) of a logged event is equal or higher than the
loggerType property of the handler, the log will be processed. In our example, logging an error (
$logger->error('ERROR')) will process it through both Debug and Error handlers. But if you log on INFO level (
$logger->info('info')), it will be only processed by the
Debug handler because INFO has a value of
200 and our
Error handler only logs everything equal or higher than
Places for saving our log
Now that we’ve defined the classes, let’s move to di.xml, where we’ll define places our log should be saved to.
There are two things here we need to discuss:
Firstly, by using
type we injected file names to the handlers. Secondly, we created a
virtualType for the default Magento logger and we passed the array with newly created handlers to the constructor.
And that’s so much for defining loggers in Magento. Obviously, you can inject handlers directly to the Magento logger (it’s useful if you want the alert error notifications to come via a different channel like email or a text message).
Let’s use our Magento loggers
To use the handlers, inject the
virtualType to a class containing the logger:
Then note that we still inject the
LoggerInterface interface in the constructor. The
virtualType is built upon the Magento logger that extends the Monolog logger, which in turn implements
LoggerInterface. Everything is working as intended and we’re still free to use a different logger.
The result can be seen after executing the
As you can see, all logs went to our_module.log and only the ones we wanted went to the error file:
As you can see, there are plenty of advantages of a well-developed method of processing logs, both through recommended PSR standards and through Monolog implementation. Thanks to Dependency Injection we have a control over what handler and what logger will be used in our modules. That’s all for today. Share your thoughts and opinions in the comments!