In the days of Magento 1, logging was a straightforward, albeit rigid, process. A single static call to Mage::log() was all it took to record data. However, as Magento evolved into Adobe Commerce, the platform adopted modern PHP standards, specifically the PSR-3 logging interface. This shift moved the platform away from global static methods toward a more flexible, dependency-injection-based architecture.

If you are transitioning from Magento 1 or are simply looking for the most efficient way to debug your modules, understanding the new logging ecosystem is essential. In this guide, we will explore the standard PSR-3 approach, how to log to custom files, and the "clean" way to configure loggers using Virtual Types.

The Standard Approach: PSR-3 Dependency Injection

The recommended way to log data in Magento 2 is by injecting the \Psr\Log\LoggerInterface into your class via the constructor. This ensures your code remains testable and adheres to the platform's architectural standards.

By default, using this logger will send information to var/log/system.log or var/log/debug.log, depending on the log level used.

namespace Vendor\Module\Model;

use Psr\Log\LoggerInterface;

class DataProcessor
{
    /**
     * @var LoggerInterface
     */
    protected $logger;

    /**
     * @param LoggerInterface $logger
     */
    public function __construct(LoggerInterface $logger)
    {
        $this->logger = $logger;
    }

    public function process()
    {
        try {
            // Your logic here
            $this->logger->info('Process started successfully.');
        } catch (\Exception $e) {
            $this->logger->error($e->getMessage());
        }
    }
}

After adding a new dependency to your constructor, remember to run the compilation command to update the generated code: php bin/magento setup:di:compile

Logging to Custom Files

One common requirement is logging to a specific file (e.g., var/log/my-custom-debug.log) instead of the generic system logs. In Magento 2, this is slightly more complex than in Magento 1 because the logger is no longer a simple static method.

The Modern Way (Laminas & Zend)

In Magento 2.3.5 and later, the Zend Framework was replaced by Laminas. To quickly write to a custom file without complex configuration, you can use the Laminas log writer. This is particularly useful for temporary debugging sessions.

$writer = new \Laminas\Log\Writer\Stream(BP . '/var/log/custom-debug.log');
$logger = new \Laminas\Log\Logger();
$logger->addWriter($writer);

$logger->info('Logging a simple string');
$logger->info('Logging an array: ' . print_r($myArray, true));

For versions prior to 2.3.5, you would use the \Zend\Log\Writer\Stream class in a similar fashion. If you are working on Magento 2.4.3 or higher, ensure you are using the \Zend_Log or \Laminas namespaces correctly based on your specific composer dependencies.

The "One-Liner" for Rapid Debugging

If you are working inside a .phtml template or a script where injecting dependencies is not feasible, you can use the ObjectManager. While this is discouraged for production code, it is highly effective for quick troubleshooting:

\Magento\Framework\App\ObjectManager::getInstance()
    ->get(\Psr\Log\LoggerInterface::class)
    ->debug('Quick debug message');

Professional Implementation: Virtual Types

For a production-grade module, the most elegant way to handle custom log files is through di.xml using Virtual Types. This allows you to create a custom logger instance and inject it into your class without writing extra PHP logic for file handling.

First, define the virtual type in your module's etc/di.xml:

<virtualType name="Vendor\Module\Model\Logger\CustomHandler" type="Magento\Framework\Logger\Handler\Base">
    <arguments>
        <argument name="fileName" xsi:type="string">/var/log/vendor_custom.log</argument>
    </arguments>
</virtualType>

<virtualType name="Vendor\Module\Model\Logger\CustomLogger" type="Magento\Framework\Logger\Monolog">
    <arguments>
        <argument name="name" xsi:type="string">customLogger</argument>
        <argument name="handlers" xsi:type="array">
            <item name="system" xsi:type="object">Vendor\Module\Model\Logger\CustomHandler</item>
        </argument>
    </arguments>
</virtualType>

<type name="Vendor\Module\Model\MyClass">
    <arguments>
        <argument name="logger" xsi:type="object">Vendor\Module\Model\Logger\CustomLogger</argument>
    </arguments>
</type>

Now, when Vendor\Module\Model\MyClass asks for LoggerInterface in its constructor, Magento will automatically provide your custom logger that writes specifically to vendor_custom.log.

Important Considerations

Logging Arrays and Objects

The PSR-3 logger expects a string for the message. If you need to log an array or an object, always wrap it in print_r() or var_export():

$this->logger->debug(print_r($myArray, true));

Production Mode

In Magento's production mode, debug level logs are often suppressed by default. If your logs aren't appearing in a production environment, you may need to enable debug logging via the database:

INSERT INTO core_config_data (scope, scope_id, path, value) 
VALUES ('default', '0', 'dev/debug/debug_logging', '1');

Wrapping Up

Transitioning from the static Mage::log to the PSR-3 LoggerInterface represents a significant step forward in the modularity of Magento. While it requires a few more lines of code initially, the benefits of using Dependency Injection and Virtual Types include cleaner code, better maintainability, and easier testing.

  • Use DI for standard system/debug logging.
  • Use Laminas/Zend for quick, custom-file debugging.
  • Use Virtual Types for professional, module-specific log files.

By following these patterns, you ensure your Magento 2 extensions remain robust and follow the latest industry best practices.