In Magento 1, segmenting logs was as simple as passing a filename to the Mage::log() method. However, Magento 2 introduced a more robust, PSR-3 compliant logging system based on Monolog. While this provides significantly more power and flexibility, it also adds a layer of complexity for developers who simply want to isolate their module's debug information into a dedicated file.

By default, Magento 2 funnels most logs into var/log/system.log, var/log/debug.log, or var/log/exception.log. When you are developing a complex extension or debugging a payment gateway, these files become cluttered and difficult to parse. Creating a custom log file in Magento 2 allows you to isolate your data, making troubleshooting faster and more efficient.

In this guide, you will learn the standard Monolog approach, the streamlined Virtual Type method, and quick version-specific snippets for rapid debugging.

Method 1: The Standard Monolog Implementation

This is the recommended approach for production-grade modules. It involves creating a dedicated Logger and Handler class, then wiring them together using Magento's Dependency Injection (DI) system.

Step 1: Create the Logger Class

First, define your custom Logger class. This class should extend the base Monolog Logger. Create the file at app/code/YourNamespace/YourModule/Logger/Logger.php:

<?php
namespace YourNamespace\YourModule\Logger;

class Logger extends \Monolog\Logger
{
}

Step 2: Create the Handler Class

The Handler class determines where the log data goes and what the filename will be. Create app/code/YourNamespace/YourModule/Logger/Handler.php:

<?php
namespace YourNamespace\YourModule\Logger;

use Monolog\Logger;

class Handler extends \Magento\Framework\Logger\Handler\Base
{
    /**
     * Logging level
     * @var int
     */
    protected $loggerType = Logger::INFO;

    /**
     * File name
     * @var string
     */
    protected $fileName = '/var/log/mycustom_filename.log';
}

Step 3: Register the Logger in di.xml

Now you must tell Magento how to instantiate these classes. Update your etc/di.xml file to link the Handler to the Logger:

<?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="YourNamespace\YourModule\Logger\Handler">
        <arguments>
            <argument name="filesystem" xsi:type="object">Magento\Framework\Filesystem\Driver\File</argument>
        </arguments>
    </type>
    <type name="YourNamespace\YourModule\Logger\Logger">
        <arguments>
            <argument name="name" xsi:type="string">myLoggerName</argument>
            <argument name="handlers"  xsi:type="array">
                <item name="system" xsi:type="object">YourNamespace\YourModule\Logger\Handler</item>
            </argument>
        </arguments>
    </type>
</config>

Step 4: Use the Logger in Your Code

You can now inject your custom logger into any class via the constructor:

<?php
namespace YourNamespace\YourModule\Model;

use YourNamespace\YourModule\Logger\Logger;

class MyModel
{
    protected $logger;

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

    public function processData()
    {
        $this->logger->info('Processing custom logic...');
    }
}

Method 2: Using Virtual Types (The Cleaner Approach)

If you want to avoid creating boilerplate PHP classes for every log file, you can use Virtual Types in your di.xml. This allows you to configure a new logger entirely through XML.

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

    <!-- Define a virtual handler with a specific filename -->
    <virtualType name="MyNamespace\MyModule\Logger\Handler" type="Magento\Framework\Logger\Handler\Base">
        <arguments>
            <argument name="filesystem" xsi:type="object">Magento\Framework\Filesystem\Driver\File</argument>
            <argument name="fileName" xsi:type="string">/var/log/mymodule_optimized.log</argument>
        </arguments>
    </virtualType>

    <!-- Define a virtual logger using the virtual handler -->
    <virtualType name="MyNamespace\MyModule\Logger\Logger" type="Magento\Framework\Logger\Monolog">
        <arguments>
            <argument name="name" xsi:type="string">MyModuleLogger</argument>
            <argument name="handlers" xsi:type="array">
                <item name="system" xsi:type="object">MyNamespace\MyModule\Logger\Handler</item>
            </argument>
        </arguments>
    </virtualType>

    <!-- Inject the virtual logger into your class -->
    <type name="MyNamespace\MyModule\Model\MyClass">
        <arguments>
            <argument name="logger" xsi:type="object">MyNamespace\MyModule\Logger\Logger</argument>
        </arguments>
    </type>
</config>

This method is highly scalable. If you need a second log file for a different class, you simply define another set of virtual types in the XML without writing any more PHP logic.

Method 3: Quick Debugging (Version-Specific)

Sometimes you don't need a permanent logger; you just need to dump some data quickly during a development session. Depending on your Magento version, the library used for stream writing has changed.

For Magento 2.4.3 and Newer

In recent versions, Magento has shifted back toward a standardized Zend/Laminas bridge for quick stream writing:

$writer = new \Zend_Log_Writer_Stream(BP . '/var/log/quick_debug.log');
$logger = new \Zend_Log();
$logger->addWriter($writer);
$logger->info('Quick Log Message');
$logger->info(print_r($myObject->getData(), true));

For Magento 2.3.5 through 2.4.2

During this period, Magento transitioned from Zend to Laminas. You should use the Laminas namespace:

$writer = new \Laminas\Log\Writer\Stream(BP . '/var/log/test_laminas.log');
$logger = new \Laminas\Log\Logger();
$logger->addWriter($writer);
$logger->info('Laminas-style logging');

For Magento 2.3.4 and Older

Older versions of Magento 2 rely directly on the original Zend Framework 1 or 2 namespaces:

$writer = new \Zend\Log\Writer\Stream(BP . '/var/log/legacy_debug.log');
$logger = new \Zend\Log\Logger();
$logger->addWriter($writer);
$logger->info('Legacy Zend log message');

Advanced: Runtime Handler Pushing

If you need to switch log files dynamically within a single class based on certain conditions, you can use the pushHandler method provided by Monolog. This is useful when you want to log specific transactions to their own files without defining dozens of DI configurations.

public function __construct(
    \Psr\Log\LoggerInterface $logger, 
    \Magento\Framework\App\Filesystem\DirectoryList $dir
) { 
    $this->logger = $logger;
    $this->dir = $dir;

    // Dynamically add a new file handler at runtime
    $customPath = $this->dir->getRoot() . '/var/log/dynamic_transaction.log';
    $this->logger->pushHandler(new \Monolog\Handler\StreamHandler($customPath));
}

Frequently Asked Questions

Where are the log files stored in Magento 2?

By default, all log files are stored in the var/log/ directory relative to your Magento root path. If you do not see your custom file there, ensure that the var/ directory has the correct write permissions for the web server user.

Why is my custom log file empty?

Check the loggerType defined in your Handler class. If you set it to Logger::ERROR but you are calling $this->logger->info(), the message will be filtered out because the severity level is too low. Set it to Logger::DEBUG or Logger::INFO during development to capture all messages.

Can I log arrays or objects directly?

Monolog expects a string for the message. To log arrays or objects, use print_r($data, true) or json_encode($data) to convert the variable into a readable string format before passing it to the logger.

Wrapping Up

Creating a custom log file in Magento 2 is essential for maintaining a clean development environment. While the quick Zend_Log methods are tempting for a 5-minute fix, implementing a proper Monolog Handler or using Virtual Types is the best practice for long-term maintainability.

By isolating your module's output, you ensure that critical debugging information isn't lost in the noise of the standard system logs, allowing you to identify and resolve issues with your Magento store much faster.