Logging in Magento Modules

When creating a magento module logging in often required for multiple reasons. Logging can be done in multiple ways, specially if you want o logging to a custom file rather than to the default magento files.

Let’s assume we are creating a sample module to display some content in the frontend. As always, to create a module we will need a registration.php file and an etc/module.xml.

// File: app/code/Webguru/SampleModule/registration.php

<?php
\Magento\Framework\Component\ComponentRegistrar::register(
    \Magento\Framework\Component\ComponentRegistrar::MODULE,
    'Webguru_SampleModule',
    __DIR__
);
// File: app/code/Webguru/SampleModule/etc/module.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
    <module name="Webguru_SampleModule" setup_version="1.0.0" />
</config>

Next, for the purpose of this article, let’s create a Block/Sample.php file inside our module. Because this is a block class to be used on a template it needs to extend Magento\Framework\View\Element\Template. Also, let’s add a constructor because we will need it to setup logging in our module.

// File: app/code/Webguru/SampleModule/Block/Sample.php

<?php

namespace Webguru\SampleModule\Block;

use Magento\Framework\View\Element\Template;

class Sample extends Template
{
    public function __construct(Template\Context $context, array $data = [])
    {
        parent::__construct($context, $data);
    }
}

To setup logging we will need to pass to our class the logging service using dependency injection. Dependency injection is a technique in which an object receives other objects that it depends on. This is the recommend technique by magento whenever a service is needed to be used in a specific module. This is done by adding a new argument to the class constructor and creating an instance variable to store that object for latter use on our methods. The service we want to use is define in class Psr/Log/LoggerInterface, so we will create a new argument of this type.

// File: app/code/Webguru/SampleModule/Block/Sample.php

<?php

namespace Webguru\SampleModule\Block;

use Magento\Framework\View\Element\Template;
use Psr\Log\LoggerInterface;

class Sample extends Template
{
    private LoggerInterface $logger;

    public function __construct(
        Template\Context $context,
        LoggerInterface $logger,
        array $data = [])
    {
        parent::__construct($context, $data);
        $this->logger = $logger;
    }
}

Now, let’s create a new method test and write a debug message to the default magento debug.log file located under var/log.

// File: app/code/Webguru/SampleModule/Block/Sample.php

    ...

    public function test()
    {
        $this->logger->debug("This is a debug message");
    }
}

Custom log file

Now, what if we want to log to a custom file? There are 3 different ways to achieve this goal using dependency injection.

If you look at the global dependency configuration file in magento that is located under app/etc/di.xml (check line #10 below) you can see that class Psr\Log\LoggerInterface is actually being implemented with class Magento\Framework\Logger\Monolog and in line #21 the arguments for this class are being configured: argument name will get value main and the argument handlers will receive an array with the class handlers for system, debug and syslog logs.

<?xml version="1.0"?>
<!--
/**
 * Copyright © Magento, Inc. All rights reserved.
 * See COPYING.txt for license details.
 */
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <preference for="DateTimeInterface" type="DateTime" />
    <preference for="Psr\Log\LoggerInterface" type="Magento\Framework\Logger\LoggerProxy" />
    <preference for="Magento\Framework\EntityManager\EntityMetadataInterface" type="Magento\Framework\EntityManager\EntityMetadata" />
    <preference for="Magento\Framework\EntityManager\HydratorInterface" type="Magento\Framework\EntityManager\Hydrator" />
    <preference for="Magento\Framework\View\Template\Html\MinifierInterface" type="Magento\Framework\View\Template\Html\Minifier" />
    <preference for="Magento\Framework\Model\Entity\ScopeInterface" type="Magento\Framework\Model\Entity\Scope" />
    <preference for="Magento\Framework\ObjectManager\FactoryInterface" type="Magento\Framework\ObjectManager\Factory\Dynamic\Developer" />
    <preference for="Magento\Framework\Search\Request\Aggregation\StatusInterface" type="Magento\Framework\Search\Request\Aggregation\Status" />
    <preference for="Magento\Framework\Search\Adapter\Aggregation\AggregationResolverInterface" type="Magento\Framework\Search\Adapter\Aggregation\AggregationResolver"/>
    <preference for="Magento\Framework\App\RequestInterface" type="Magento\Framework\App\Request\Http" />
    <preference for="Magento\Framework\App\PlainTextRequestInterface" type="Magento\Framework\App\Request\Http" />
    ...
    <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>
                <item name="syslog" xsi:type="object">Magento\Framework\Logger\Handler\Syslog</item>
            </argument>
        </arguments>
    </type>
    ...
</config>

Class Magento\Framework\Logger\Handler\Debug definition is very simple and short. It inherits all functionality from class Magento\Framework\Logger\Handler\Base and it only overrides 2 instance variables: filename and loggerType. As you can see filename is the variable where the output filename is defined, which means that if we override this value we can make the logs be written to a different file.

<?php
/**
 * Copyright © Magento, Inc. All rights reserved.
 * See COPYING.txt for license details.
 */

namespace Magento\Framework\Logger\Handler;

use Monolog\Logger;

class Debug extends Base
{
    /**
     * @var string
     */
    protected $fileName = '/var/log/debug.log';

    /**
     * @var int
     */
    protected $loggerType = Logger::DEBUG;
}

Create a custom Log Handler

To override this value we can create a custom debug handler and tell magento to use it instead of the default debug handle defined in Magento\Framework\Logger\Handler\Debug. Let’s call this new class DebugHandler and place it inside the Model folder in our module. In this class let’s initialize filename with our custom debug filename (custom_debug.log, for example)

// File: app/code/Webguru/SampleModule/Model/DebugHandler.php

namespace Webguru\SampleModule\Model;

use Magento\Framework\Logger\Handler\Base;
use Monolog\Logger;

class DebugHandler extends Base
{
    /**
     * @var string
     */
    protected $fileName = '/var/log/custom_debug.log';

    /**
     * @var int
     */
    protected $loggerType = Logger::DEBUG;
}

Now, let’s tell magento to use this customer handler. This can be done in the module di.xml file by setting a preference, just like we saw above.

# File: app/code/Webguru/SampleModule/etc/di.xml
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <preference for="Magento\Framework\Logger\Handler\Debug" type="Webguru\SampleModule\Model\DebugHandler" />
</config>

Alternatively we can create a more specific di configuration. So, instead of preference we can use a type config in our di.xml file to just replace the debug handler.

# File: app/code/Webguru/SampleModule/etc/di.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <type name="Magento\Framework\Logger\Monolog">
        <arguments>
            <argument name="handlers"  xsi:type="array">
                <item name="debug" xsi:type="object">Webguru\SampleModule\Model\DebugHandler</item>
            </argument>
        </arguments>
    </type>
</config>

The downside of any of these approaches is that we are replacing\reconfiguring the instance of class Magento\Framework\Logger\Monolog for all magento framework and not just our module. This means that all debug logs will now be written into our custom log file.

To overcome this problem we should use a virtualType config in our di.xml instead. virtualType allow us to create like a virtual class of a certain class (type). This newly created virtual class can then be used by magento whenever within our module a request to inject a LoggerInterface class is made. For this we need to set a type config to explicitly say that the argument logger (of type LoggerInterface) should be injected with our virtualType class and not the default class defined in general app/etc/di.xml.

# File: app/code/Webguru/SampleModule/etc/di.xml

<?xml version="1.0"?>
<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">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">Webguru\SampleModule\Model\DebugHandler</item>
                <item name="syslog" xsi:type="object">Magento\Framework\Logger\Handler\Syslog</item>
            </argument>
        </arguments>
    </virtualType>
    <type name="Webguru\SampleModule\Block\Sample">
        <arguments>
            <argument name="logger" xsi:type="object">CustomLogger</argument>
        </arguments>
    </type>
</config>

Now, only calls to the debug method of the argument logger in our Webguru\SampleModule\Block\Sample class will log into our custom log file.

Leave a Reply

Your email address will not be published. Required fields are marked *