Logging system in Magento 2

Logging system is a very useful tool in Magento development. It is important for developers in the code debugging process, the log files help us determine how the store works, what issues are there, which of the functions do not work correctly. There’s less chances that you will get a raw Magento 2 internal server error without handling it via Magento 2 logging in comparison with Magento 1. In this article we are going to describe the logging system in Magneto 2 and how it differs from Magento 1.

Do you remember the Mage::log() method in Magento 1? Here it is:

public static function log($message, $level = null, $file = '', $forceLog = false);

As you may also know, using this function we can log any information that we need in Magento 1. It is quite simple.

But when we review the logging system in Magento 2, we will notice that the logging system has been changed there, and it does not contain the mentioned above function anymore. Instead of that, we have Psr\Log\LoggerInterface which provides Magento 2 API for working with logging system.
By default, Magento 2 uses the following global preference for the interface:

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

As follows, the Magento\Framework\Logger\Monolog class is used as an argument if you just inject Psr\Log\LoggerInterface as shown at the example below:

<?php

namespace SomeNamespace;

use Psr\Log\LoggerInterface;

class MyClass
{
    /** @var  LoggerInterface */
    protected $logger;
    /**
     * @param LoggerInterface $logger
     * @param array $data
     */
    public function __construct(
        LoggerInterface $logger
    ) {
        $this->logger = $logger; // here will be object of Magento\Framework\Logger\Monolog class
                                // by default
    }
}

Let’s look deeper in the source code of Psr\Log\LoggerInterface:

 
<?php

namespace Psr\Log;

interface LoggerInterface
{
    public function emergency($message, array $context = array());
    
    public function alert($message, array $context = array());
    
    public function critical($message, array $context = array());
    
    public function error($message, array $context = array());
    
    public function warning($message, array $context = array());
    
    public function notice($message, array $context = array());
    
    public function info($message, array $context = array());
    
    public function debug($message, array $context = array());
    
    public function log($level, $message, array $context = array());
}

Psr\Log\LoggerInterface is implemented according to RFC 5424 and it contains the methods to log information on all of eight logging levels that the RFC provides:

  1. Emergency – system is unusable.
  2. Alert – action must be taken immediately. Example: entire website down, database unavailable, etc.
  3. Critical – critical conditions. Example: application component unavailable, unexpected exception.
  4. Error – runtime errors that do not require immediate action but should be typically logged and monitored.
  5. Warning – exceptional occurrences that are not errors.
  6. Notice – normal but significant events.
  7. Info – interesting events.
  8. Debug – detailed debug information.

Now, we check the Magento\Framework\Logger\Monolog class that implements Psr\Log\LoggerInterface:

<?php

namespace Magento\Framework\Logger;

use Monolog\Logger;

class Monolog extends Logger
{
    /**
     * Adds a log record.
     *
     * @param  integer $level   The logging level
     * @param  string  $message The log message
     * @param  array   $context The log context
     * @return Boolean Whether the record has been processed
     */
    public function addRecord($level, $message, array $context = [])
    {
        $context['is_exception'] = $message instanceof \Exception;
        return parent::addRecord($level, $message, $context);
    }
}

If more detailed, this class just extends Monolog APIs and represents it as an intermediate layer.

Let’s check how to log some information to a file. For this, inject Psr\Log\LoggerInterface to the class that should use the logger and choose the require logging level of the information. As a next step, call the corresponding method of Psr\Log\LoggerInterface, just like this:

/** @var LoggerInterface $logger */
$logger->info('Important information', [
'context' => $context,
'something_else_should_be_logged' => $data,
])

By default, logged information is stored in the following three files (paths is related to Magento root directory):

  1. var/log/debug.log – all information that is saved by Psr\Log\LoggerInterface::debug() is stored there.
  2. var/log/exception.log – all exceptions information is stored there.
  3. var/log/system.log – information from other logging levels is saved there.

Sometimes we need to save the logs information to some other file, different from the default one. So, for that we can define a new virtual type to extend the Magento\Framework\Logger\Monolog and rewrite the default “debug” handler by a new one. Those steps are described below:

    <virtualType name="Atwix\CustomLogger\Logger\Custom" type="Magento\Framework\Logger\Monolog">
        <arguments>
            <argument name="handlers"  xsi:type="array">
                <item name="debug" xsi:type="object">Atwix\CustomLogger\Logger\Handler\Custom</item>
            </argument>
        </arguments>
    </virtualType>

Furthermore, we simply define that Magento should create an instance of the virtual type “Atwix\CustomLogger\Logger\Custom” instead of the default Magento\Framework\Logger\Monolog:

    <type name="Atwix\CustomLogger\Observer\Authenticated">
        <arguments>
            <argument name="logger" xsi:type="object">Atwix\CustomLogger\Logger\Custom</argument>
        </arguments>
    </type>

Finally, here is a class that will use the logger with custom handler:

<?php

namespace Atwix\CustomLogger\Observer;

use Psr\Log\LoggerInterface;
use Magento\Framework\Event\ObserverInterface;
use \Magento\Framework\Event\Observer;
use Magento\Customer\Model\Customer;

class Authenticated implements ObserverInterface
{
    /** @var LoggerInterface  */
    protected $logger;

    public function __construct(
        LoggerInterface $logger
    )
    {
        $this->logger = $logger;
    }

    public function execute(Observer $observer)
    {

        /** @var Customer $customer */
        $customer = $observer->getModel();
        if ($customer->getId()) {
            $email = $customer->getEmail();
            $message = sprintf(' %s has been logged in.', $customer->getName());

            $this->logger->debug($message, [
                'email' => $email,
            ]);
        }

        return $this;
    }
}

As result, we have got a custom.log file in the var/log directory. It will contain the following strings:

[2016-03-12 20:02:08] main.DEBUG:  Veronica Costello has been logged in. {"email":"roni_cost@example.com","is_exception":false} []

You can find the full version of this example on GitHub.

It is normal that the log files usually contain much information related to the website’s functioning. But it takes much space on a hard disk and, also, the logs reviewing process becomes difficult because of their large size. Meanwhile, it can be easily fixed by cleaning the var/log directory in the following way:

rm -rf var/log/*

As you can see, Magento 2 still supports the logging system and it makes our work with it easier and helps monitor website’s functioning.