Logging system in Magento 2

The logging system comes super handy in Magento development. Developers use it as a go-to tool for code debugging. There is a reduced chance of encountering a raw Magento 2 internal server error without handling it via Magento 2 logging, compared to Magento 1.

Log files show us how the store works, reveal pesky issues under the hood, and even snitch on the functions that aren’t pulling their weight. In this article, we will describe the logging system in Magento 2 and how it differs from Magento 1.

Remember the Mage::log() method in Magento 1? It goes like this:

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

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

But reviewing the logging system in Magento 2, you will notice that the logging system has been changed, and it does not contain the aforementioned function anymore. Instead, we have Psr\Log\LoggerInterface which prepares Magento 2 API to work with a 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 take a closer look at 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 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, let’s 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 see how to log some information into a file. Inject Psr\Log\LoggerInterface to the class that should use the logger and choose the require logging level of the information. Next, call the corresponding method of Psr\Log\LoggerInterface 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 (the path is related to the Magento root directory):

  1. var/log/debug.log — all information 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, you will need to save logs information in some other file, different from the default one. In that case, you need to 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>

Remember to 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;
    }
}

Ta-da! You’ve got a custom.log file in the var/log directory, which 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} []

Find the full version of this example on GitHub.

It is normal that the log files usually contain much information related to the website functions. But it takes much space on a hard disk and, also, the logs reviewing process becomes difficult because of their large size. This issue might 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, which makes developer work easier and helps monitor website’s functioning.

See more in a related article on Database queries logging in Magento 2 or reach out for an expert Magento consultation at Atwix!