Using Virtual Types for Saving Custom Logs in Magento 2.0

June 20, 2016 / Posted in Magento, Magento 2 by Artsem Miklashevich
Using Virtual Types for saving custom logs in Magento 2.0

When you are in the process of development, you may need to save your logs a separate file. This could be easily done in Magento 1: use static method Mage::log() with the required file name and text. However, you are no longer able to do the same in Magento 2 — it uses Monolog as the main logging tool which follows the logging standard of PSR-3 . Monolog doesn’t allow to set a new filepath where you want to log a message. Monolog saves log file path during handlers initialization and does not allow to modify filename later. However, it is possible to add one more handler to Monolog, for example, like this:

This solution has one drawback: you will have to add a handler in every class that should use your log. And if you decide to use several handlers the size of your code will double. Magento 2 uses Monolog through the class decorator Magento\framework\Logger\Monolog which is inherited from the class Monolog\Logger. Default handlers are defined in the file app/etc/di.xml:

Here we can see that Magento 2.0 uses 2 default handlers: system and debug. System saves everything to the file var/log/system.log, and debug uses the file var/log/debug.log. Besides, System keeps all logs of the INFO level and higher (levels are constants in the class Monolog/Logger which indicate the importance of a message), while the file debug.log keeps the messages of the level DEBUG and higher.

So, the result is the following:

To avoid the situation when one and the same message can be recorded into several different files, Monolog uses the very first handler that fits the requirements of the levels. That is why the order in which you add handlers and level are very important. If we declared handlers in the reversed order:

the information would be recorded into the debug.log file, because the handler “debug” has the lowest level and therefore will process any message.

That is why it is not such an easy task to save a message into your own log file by using the “magento way”. Except for the way described above, it is possible to solve this task by 2 other different ways. It is possible to create a class which is inherited from the class Magento\Framework\Logger\Handler\Base and then redefine the property and method of this class:

And then add the following code into the di.xml file of your module:

So we have added our own handles with the predefined filepath to the array of handlers. But this solution has certain disadvantages. First off, it is impossible to guarantee that your handler will receive a message (in case anyone else adds one more handler of the same level). Secondly, there is a risk that you will receive not only your own logs, but also the system logs of the same level or higher, which is also not good. So, we need an object that will work only for those objects that we want to use. To solve this task we can use such a remarkable object type as virtual types, which is available in Magento 2.0. Replace the above mentioned code with the following code in the di.xml file of your module:

Virtual types allow you to create objects with the required parameters out of existing classes. Such type of objects simplifies the definition of object parameters. In fact, we create an object of the class defined in the type property but with the parameters which we have specified ourselves. In the above example we have created an object of the class  \Magento\Framework\Logger\Monolog with the parameters indicated in the tag <arguments>. But this example has one weak point. There is nothing that can prevent us from redefining the class \Magento\Framework\Logger\Monolog in some third-party module and adding a different handler there. And if this handler is added before our handler, then there is a risk that you will not get any message at all. That is why I inherit directly from the class Monolog\Logger:

Of corse, nothing prevents us from redefining the class Monolog\Logger too, but lets hope, that no one is going to do this.

Also, lets make our logger to notify a developer in case of any critical errors. In this case we just need to create a virtual object on the basis of the class Monolog\Handler\NativeMailerHandler:

Now lets add these handlers to the Logger:

Here we are — the Logger is ready, now we just have to inject this Logger into the object where we need to use it.

That is all, the Logger has been configured and it is going to work as follows:

So, in this article I have described how you can enable logging for your module with the help of the Monolog library. With the help of virtualType you can flexibly configure objects and define their parameters as well as make the object visible only for specific classes without creating new files and using only the file di.xml of your module.



Post a new comment