8/31/2017 - #development #extensions #magento
by: Thomas Klein

In Magento 1, there were many log files to separate different logs (for payment methods, etc.) and it was easy to change the target log file just by changing the $file parameter of Mage::log. But Magento 2 has changed to build their logs with Monolog library.

In this article we will discover how the logging system in Magento 2 is different compared to Magento 1, how it works and how could we reclaim it and make custom log files.

What are logs ?

Logs record information. Logger provides a standard mechanism to log system and error logs. These logs record information in a chronological order and are used for analyzing events and checking if everything is ok or not. Logs help developers to find and understand how an error occurred or what led to the error. Usually logs are stored in a local file or a database.
In PHP web applications built using the OOP paradigm, generally there is a logger object to manage logs, in Magento 2 this logger is managed by Monolog.

Standard PSR-3

Magento 2 complies with the PSR-3 standard.
But what's the PSR-3?
PSR-3 is one of the different PSR rules. PSR is an acronym for PHP Standard Recommendations, these recommendations, validate by PHP Framework Interoperability Group, have to improve interoperability between the framework and reduce conflicts.
So PSR-3 is a Logger Interface. Its goal is to write libraries which receive the same object Psr\Log\LoggerInterface, and guarantees a better portability for your solution on other applications.
In Magento 2, PSR is located in <Magento2_Root>/vendor/psr.

Monolog Library and Magento 2

Magento 2 has a built-in log facility based on Monolog Library. Monolog is a popular PHP logging solution and this is an object-oriented library, which is fully extensible, allowing Magento 2 to override and adapt Monolog to their needs. Moreover, Monolog is compliant with standard PSR-3. If you want to check Monolog and see how it is built, see in <Magento2_Root>/vendor/monolog.

How Monolog complies to PSR-3

Monolog implements LoggerInterface of PSR-3 Interface and implements the eight methods according levels of the RFC 5424. In the di.xml, located in <Magento2_Root>/app/etc/di.xml, Magento 2 comes with the following declaration of implementation :

<preference for="Psr\Log\LoggerInterface" type="Magento\Framework\Logger\Monolog"/>

It means Magento 2 makes a preference for LoggerInterface. So that means each time when Psr\Log\LoggerInterface is required, an instance of Magento\Framework\Logger\Monolog will be supplied. This instance implements LoggerInterface. So PSR-3 coupled to Monolog make a powerful logging solution and you will see, easy to use.

Channel and handlers

Monolog manages logs by channel, which set your logs into different « categories ». Then, each channel has a stack of handlers, these handlers can be shared between channels. The channel can be imagined like the type of the log, for example, it is debug or system log. The channel itself does not know how to handle a record and put the message into a log. It delegates it to handlers, will write records, for example, in files, database, etc. So the main goal for a handler is to write your log records/messages, according their levels.
Those levels are implemented by Monolog\Logger, below the levels of Psr\Log\LoggerInterface :

<?php  
namespace Psr\Log;
 
/**
* Describes a logger instance
*
* The message MUST be a string or object implementing __toString().
*
* The message MAY contain placeholders in the form: {foo} where foo
* will be replaced by the context data in key "foo".
*
* The context array can contain arbitrary data, the only assumption that
* can be made by implementors is that if an Exception instance is given
* to produce a stack trace, it MUST be in a key named "exception".
*
* See https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md
* for the full interface specification.
*/
interface LoggerInterface
{
   /**
    * System is unusable.
    *
    * @param string $message
    * @param array $context
    * @return null
    */
   public function emergency($message, array $context = array());
 
   /**
    * Action must be taken immediately.
    *
    * Example: Entire website down, database unavailable, etc. This should
    * trigger the SMS alerts and wake you up.
    *
    * @param string $message
    * @param array $context
    * @return null
    */
   public function alert($message, array $context = array());
 
   /**
    * Critical conditions.
    *
    * Example: Application component unavailable, unexpected exception.
    *
    * @param string $message
    * @param array $context
    * @return null
    */
   public function critical($message, array $context = array());
 
   /**
    * Runtime errors that do not require immediate action but should typically
    * be logged and monitored.
    *
    * @param string $message
    * @param array $context
    * @return null
    */
   public function error($message, array $context = array());
 
   /**
    * Exceptional occurrences that are not errors.
    *
    * Example: Use of deprecated APIs, poor use of an API, undesirable things
    * that are not necessarily wrong.
    *
    * @param string $message
    * @param array $context
    * @return null
    */
   public function warning($message, array $context = array());
 
   /**
    * Normal but significant events.
    *
    * @param string $message
    * @param array $context
    * @return null
    */
   public function notice($message, array $context = array());
 
   /**
    * Interesting events.
    *
    * Example: User logs in, SQL logs.
    *
    * @param string $message
    * @param array $context
    * @return null
    */
   public function info($message, array $context = array());
 
   /**
    * Detailed debug information.
    *
    * @param string $message
    * @param array $context
    * @return null
    */
   public function debug($message, array $context = array());
 
   /**
    * Logs with an arbitrary level.
    *
    * @param mixed $level
    * @param string $message
    * @param array $context
    * @return null
    */
   public function log($level, $message, array $context = array());
}

Handlers catch a certain level and writes them to the specific log (file, database, mail, etc.). The basic handler is the StreamHandler, which writes logs in a stream. The log file path is declared into the handler (by default in app/logs/). For example Magento 2  declared two handlers : debug and system. In which handlers, the log file path declared are var/log/debug.log and var/log/system.log.But, how Magento 2 declares these two handlers and channel ?

Let's see, in <Magento2_Root>/app/etc/di.xml :

<type name="Magento\Framework\Logger\Monolog">
  <arguments>
    <argument name="name" xsi:type="string">main</argument>
    <argument name="handlers" xsi:type="array">
      <item name="system" xsi:type="object">Magento\Framework\Logger\Handler\System</item>
      <item name="debug" xsi:type="object">Magento\Framework\Logger\Handler\Debug</item>
    </argument>
  </arguments>
</type>

The first argument is the name of the channel, here channel « main » is declared. The second argument is the stack of handlers for this channel, here there is two handlers declared : handler « system » and handler « debug ». These two objects have an attribute $fileName with the path of the log file where it will write, and an another attribute $loggerType which allows handler to catch all records of this type.

How to use Magento 2 logging system

In this part, we will call the different level methods that we had seen in Monolog Library part, with the different levels.

class SomeModel
{
  private $logger
  public function __construct (\Psr\Logger\LoggerInterface $logger)
  {
    $this->logger = $logger;
  }
  public function doSomething()
  {
    try{
      //do something
    } catch (/Exception $e) {
      $this->logger->critical($e);
    }
  }
}

To use the logger in your class, add a \Psr\Log\LoggerInterface object via constructor injection to your $logger attribute. So you can use your logger in your methods and write exception or info in logs. In the case of a Magento model or block, if you extend the Magento class, a logger is already provided via the $_logger attribute.

How to create a custom channel and handler

You may need to record some information in the logs during your development. But you don’t want to melt these data with others. Therefore you have to write your own channel and handler.
Firstly lets see how to make our own handler. For these examples, we will assume that your module is in YourNamespace/YourModule
Create your channel by extending the \Magento\Framework\Logger\Monolog class or the classic \Monolog\Logger:

A best practice, if you didn’t need to add a custom implementation in the class, is to use a virtual type:

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
  <virtualType name="customLogger" type="Magento\Framework\Logger\Monolog">
      <arguments>
          <argument name="name" xsi:type="string">custom_logger</argument>
          <argument name="handlers"  xsi:type="array">
              <item name="debug" xsi:type="object">YourNamespace\YourModule\Logger\Handler</item>
          </argument>
      </arguments>
  </virtualType>
  <type name="YourNamespace\YourModule\Logger\Handler">
      <arguments>
          <argument name="logger" xsi:type="object">custom_logger</argument>
      </arguments>
  </type>
</config>

Now we need to create our handler, which extends the default handler of Magento 2.
By default we use the streamHandler. This handler writes records in a log file.
In this class, you should declare two attributes, $loggerType and $filename. $loggerType defines which type of level your handler will catch (it must be RFC 5424 compliant).
You can find all levels type in \Monolog\Logger.

<?php
namespace YourNamespace\YourModule\Logger;
use Monolog\Logger
class Handler extends \Magento\Framework\Logger\Handler\Base 

  /**
  *Logging level
  *@var int
  */
  protected $_loggerType = Logger::INFO; // Your level type
  /**
  *File Name
  *@var string
  */
  protected $fileName = '/var/log/myfilename.log';
}

Now we have to register logger and handler, lets see the code below (etc/di.xml of your module) :

<type name="YourNamespace\YourModule\Logger\Handler\Handler">
  <arguments>
    <argument name="filesystem" xsi:type="object">Magento\Framework\Filesystem\Driver\File</argument>
  </arguments>
</type>
<type name="YourNamespace\YourModule\Logger\Logger\Logger">
  <arguments>
    <argument name="name" xsi:type="string">MyChannel</argument>
  </arguments>
    <argument name="handlers" xsi:type="array">
      <item name="handler" xsi:type="object">YourNamespace\YourModule\Logger\Handler\Handler</item>
    </argument>
  </arguments>
</type>

The logger (channel) does not know how to handle a record, you have to delegate it to your handlers or some others. So you add handlers in di.xml to the logger, as an item in argument « handlers ».

Warning : The order of handler items is important ! Handlers have to be stacked in the right order, meaning following order declaration of RFC 5424 levels, else elements in the wrong order will be ignored.
See the following declaration :

<type name="YourNamespace\YourModule\Logger\Logger">
 <arguments>
  <argument name="name" xsi:type="string">testlog</argument>
  <argument name="handlers" xsi:type="array">
   <!--emergency-->
   <!--alert-->
   <!--critical-->
   <!--error-->
   <!--warning--><item name="warning" xsi:type="object">YourNamespace\YourModule\Logger\Handler\Warning</item>
   <!--notice--><item name="notice" xsi:type="object">YourNamespace\YourModule\Logger\Handler\Notice</item>
   <!--info-->
   <!--debug--><item name="debug" xsi:type="object">Magento\Framework\Logger\Handler\Debug</item>
  </argument>
 </arguments>
</type>

This is a tip for correctly ordered your handler. In this example, emergency, alert, critical, error and warning levels are recorded by the same handler « Warning ». Notice levels have their own handler. Info and debug, share the same handler « Debug ».
Now, we can use our logger in classes, here an example of a class which write a log entry :

<?php
namespace YourNamespace\YourModule\Model;
class MyModel
{
 /**
 *Logging instance
 *@var\YourNamespace\YourModule\Logger\Logger
 */
 protected $_logger;
 /**
  *Constructor
  *@param\YourNamespace\YourModule\Logger\Logger $logger
  */
 public function _construct(
 \YourNamespace\YourModule\Logger\Logger $logger
  ) {
  $this->_logger = $logger;
 }
 public function doSomething()
 {
   $this->_logger->info('I did something');
 }
}

Our $logger object is instantiated by dependency injection.

We have seen how you can add your own log file easily with a new module while respecting PSR-3 and re-use Magento 2 built-in log facility.

Conclusion

Magento 2 is now PSR-3 conformant and uses a powerful logger manager with Monolog. This provides isolation between code needed to write log message and functionnal code.
Magento 2 logger, is fully extensible and we can add our custom handlers and adapt logger to our needs simply, by adding a new module. It is also possible to custom handlers in order to write in database, send email etc... To find out more about this topic, you can check the Monolog documentation.

 Get the sources on Packagist.

Write your comment

Ready
for take-off?

Blackbird is a web agency specialized in the development of eCommerce websites. We are Magento Experts and we offer our services and advice for e-marketing, strategy, branding, deployment and methodology.

Contact
+339 50 66 21 38


Legal terms
Agency 30, Avenue du Rhin
67100 Strasbourg
France
SEE MAP
FR - EN