89

In Magento 1, it was common to segment logs into different files (to separate logs for payment methods, etc.). That's as easy as changing the $file parameter of Mage::log.

Magento 2 has changed to use Monolog.

It appears that Monolog (or Magento2's implementation of it) segments all logs for the entire framework to handlers by severity. There are a few handlers that write to file:

\Magento\Framework\Logger\Handler\Debug, \Magento\Framework\Logger\Handler\Exception, \Magento\Framework\Logger\Handler\System

Logging to respective files in var/log as in Magento 1.

I could add a handler for a particular severity (IE, write notices to var/log/notice.log). Extend \Magento\Framework\Logger\Handler\Base, and register the handler in di.xml.

This article roughly describes that process: http://semaphoresoftware.kinja.com/how-to-create-a-custom-log-in-magento-2-1704130912

But how do I go about writing all logs (not just one severity) for one class (not all of Magento) to my file of choice?

It looks like I'll have to create my own version of Magento\Framework\Logger\Monolog, but then how does everything fit together for that to actually work?

If this is a big no-no in Magento 2, then what is the alternative? I want something to separate the logs for this extension for the purpose of debugging it when necessary on client sites. Having that info written to system.log, exception.log, etc. and jumbled with the logs of every other module is not practical.

Rafael Corrêa Gomes
  • 13,309
  • 14
  • 84
  • 171
Ryan Hoerr
  • 12,271
  • 7
  • 47
  • 54

17 Answers17

148

You do not need to customize or try to extend Magento2's logging. As you said it's using Monolog with only slight customization. It is sufficient to write your own logger extending Monolog with very little effort.

Assuming your module is in YourNamespace/YourModule:

1) Write Logger class in Logger/Logger.php:

<?php
namespace YourNamespace\YourModule\Logger;

class Logger extends \Monolog\Logger { }

2) Write Handler class in 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/myfilename.log';

}

Note: This is the only step that uses the Magento code. \Magento\Framework\Logger\Handler\Base extends Monolog's StreamHandler and e.g. prepends the $fileName attribute with the Magento base path.

3) Register Logger in Dependency Injection 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="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>

Note: This is not strictly required but allows the DI to pass specific arguments to the constructor. If you do not do this step, then you need to adjust the constructor to set the handler.

4) Use the logger in your Magento classes:

This is done by Dependency Injection. Below you will find a dummy class which only writes a log entry:

<?php
namespace YourNamespace\YourModule\Model;

class MyModel { /** * Logging instance * @var \YourNamespace\YourModule\Logger\Logger */ protected $_logger;

/**
 * Constructor
 * @param \YourNamespace\YourModule\Logger\Logger $logger
 */
public function __construct(
    \YourNamespace\YourModule\Logger\Logger $logger
) {
    $this-&gt;_logger = $logger;
}

public function doSomething()
{
    $this-&gt;_logger-&gt;info('I did something');
}

}

MagestyApps
  • 679
  • 3
  • 8
halk
  • 1,691
  • 1
  • 11
  • 5
  • 3
    I was asking something similar to one of the architects the other day, so thanks for this example! I was wondering about adding support based on class name so DI framework could inject the "right" logger to different classes, and having switches in the Admin to turn flags on/off without code changes like this. How useful would this sort of functionality be to people? – Alan Kent Jul 29 '15 at 21:16
  • @halk, how to add debug from .phtml files? example <?php $this->_logger->addDebug('Customer.phtml'); ?> – Manoj Kumar Jul 30 '15 at 06:55
  • 1
    Manoj, if the template you are refering to has a block class with logger then you can write a public method which then passes the message to the logger. Your example wont work since _logger is protected if it exists at all – halk Jul 30 '15 at 11:54
  • Alan, I think the default logger has no such decisive approach. Currently without coding only log levels can be used as a differentiator. – halk Jul 30 '15 at 11:55
  • Correct, the logger currently has not such approach. I was curious to how much people would like that to be added - but maybe this should move to a GitHub issue instead as off topic. – Alan Kent Jul 30 '15 at 19:36
  • 3
    In my opinion, the current approach is a step back from what M1 had. Logging should also be a developer tool, it is not only meant for monitoring a live application. I can see how an optional multi-purpose simplified library could be created for being used in development overriding the current implementation and then replaced for production use – barbazul Dec 10 '15 at 16:34
  • 2
    @AlanKent I agree with barbazul here - the ability to easily log to whichever file you wanted to, specifying the level quickly in M1 was great. This is not as flexible (dynamically) which is a shame. Would be good to have the filename as a parameter to the default logger calls. Thanks for the answer halk! – scrowler Dec 15 '15 at 21:06
  • @AlanKent agree. For me it'd be sufficient to be able to define those in di.xml without the need of a custom class. This would require some sort of DI container class to be used in classes which want to make use of the logger as there would not be a custom class to mention in the constructor – halk Jan 22 '16 at 10:39
  • 3
    For me it's always taking the /var/log/system.log, any idea why? – MagePsycho Mar 25 '16 at 20:46
  • 1
    @AlanKent +1 for the ability to have the filename as a parameter to the default logger calls – Hervé Guétin Apr 10 '16 at 23:13
  • 1
    The problem with this example is that it replaces the log for all the modules. So instead of having a log for 1 module or 1 class it's used all over Magento. Any idea on how to have a module based logger? – Sakis Nov 24 '17 at 11:10
  • 1
    "Missing required argument $name of" can be solved by bin/magento cache:flush – Liam Mitchell Sep 03 '19 at 03:08
  • can we define multiple $loggerType in Handler Class ? – 26vivek Jan 09 '20 at 07:38
42

the custom log file created using below code

for Magento 2.4.2 before version use this

$writer = new \Zend\Log\Writer\Stream(BP . '/var/log/custom.log');
$logger = new \Zend\Log\Logger();
$logger->addWriter($writer);
$logger->info('Custom message'); 
$logger->info(print_r($object->getData(), true));

for Magento 2.4.2 or after version use this

$writer = new \Laminas\Log\Writer\Stream(BP . '/var/log/custom.log');
$logger = new  \Laminas\Log\Logger();
$logger->addWriter($writer);
$logger->info('text message');
$logger->info(print_r($object->getData(), true));

for Magento 2.4.3 version use this

$writer = new \Zend_Log_Writer_Stream(BP . '/var/log/custom.log');
$logger = new \Zend_Log();
$logger->addWriter($writer);
$logger->info('text message');
$logger->info(print_r($object->getData(), true));
Msquare
  • 9,063
  • 7
  • 25
  • 63
Sagar Dobariya
  • 2,895
  • 2
  • 24
  • 47
41

We can log data in file like this.

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

$logger->info("Info". $product->getSku() . "----- Id  ". $product->getId() );
$logger->info("preorder qty ". $product->getPreorderQty());
Umar Yousaf
  • 343
  • 3
  • 8
Pramod Kharade
  • 2,832
  • 1
  • 23
  • 39
20

In addition to Halk's and Pradeep Kumar's answers: If indeed your only concern is to log to a different file, there is a slightly easier way, especially if you want to incorporate that to multiple modules or if you want different log files within your module. With this method, you don't have to create custom handlers.

Assuming your module is in MyNamespace/MyModule and the class which you want to log to a custom file, is called MyClass. If the constructor of the class already injects \Psr\Log\LoggerInterface skip to step 2). Otherwise you need to inject it in the constructor:

  1. Inject LoggerInterface in your class MyClass.php:

    <?php
    

    namespace MyNamespace\MyModule;

    use Psr\Log\LoggerInterface;

    class MyClass { /** * @var \Psr\Log\LoggerInterface */ protected $logger;

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

    }

    If you extend a class which already includes a logger (like \Magento\Framework\App\Helper\AbstractHelper) you might as well overwrite that member (usually $_logger) instead of using a separate one. Simply add $this->_logger = $logger after the parent constructor directive.

    <?php
    

    namespace MyNamespace\MyModule;

    use Magento\Framework\App\Helper\Context; use Psr\Log\LoggerInterface;

    class MyClass extends \Magento\Framework\App\Helper\AbstractHelper { public function __construct( Context $context, LoggerInterface $logger ) { parent::__construct( $context );

        $this-&gt;_logger = $logger;
    }
    

    }

  2. Configure logger via dependency injection 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="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.log</argument>
            </arguments>
        </virtualType>
        <virtualType name="MyNamespace\MyModule\Logger\Logger" type="Magento\Framework\Logger\Monolog">
            <arguments>
                <argument name="name" xsi:type="string">MyModule Logger</argument>
                <argument name="handlers" xsi:type="array">
                    <item name="system" xsi:type="object">MyNamespace\MyModule\Logger\Handler</item>
                </argument>
            </arguments>
        </virtualType>
    
    &lt;type name=&quot;MyNamespace\MyModule\MyClass&quot;&gt;
        &lt;arguments&gt;
            &lt;argument name=&quot;logger&quot; xsi:type=&quot;object&quot;&gt;MyNamespace\MyModule\Logger\Logger&lt;/argument&gt;
        &lt;/arguments&gt;
    &lt;/type&gt;
    

    </config>

    This will log everything to /var/log/mymodule.log.

    If you need to log to a different file for a different class, you can simply create another virtual logger with another virtual handler and inject it into that class.

tripleee
  • 127
  • 1
  • 7
T. Dreiling
  • 431
  • 4
  • 5
19

The simplest possible way:

$writer = new \Zend\Log\Writer\Stream(BP . '/var/log/test.log');
$logger = new \Zend\Log\Logger();
$logger->addWriter($writer);
$logger->info('Your text message');
$logger->info(print_r($object->getData(), true));
Msquare
  • 9,063
  • 7
  • 25
  • 63
Yamen Ashraf
  • 437
  • 3
  • 7
8

If you need it within your single class only:

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

    $this->logger->pushHandler(new \Monolog\Handler\StreamHandler($this->dir->getRoot().'/var/log/custom.log'));
}
mshakeel
  • 528
  • 9
  • 12
7

From Magento 2.3.5-p1, the code should be:

$writer = new \Laminas\Log\Writer\Stream(BP . '/var/log/test.log');
$logger = new \Laminas\Log\Logger();
$logger->addWriter($writer);
$logger->info('Your text message');

Zend is now Laminas

PauGNU
  • 712
  • 7
  • 18
7

If you are using logger for only testing purpose, you can use direct code either use proper magento standard

With Magento 2.4.4 & plus versions use below code:

$writer = new \Zend_Log_Writer_Stream(BP . '/var/log/testing.log');
$logger = new \Zend_Log();
$logger->addWriter($writer);
$logger->info('Test the log');

For Magento 2.4.2 & lower versions:

$writer = new \Zend\Log\Writer\Stream(BP . '/var/log/custom.log');
$logger = new \Zend\Log\Logger();
$logger->addWriter($writer);
$logger->info('Custom message'); 
$logger->info(print_r($object->getData(), true));

NOTE: Recommended to use Magento standard this is only for testing purpose

Arun Sharma
  • 1,315
  • 1
  • 9
  • 33
5

Try this:

$writer = new \Zend\Log\Writer\Stream(BP . '/var/log/yyr.log');
$logger = new \Zend\Log\Logger();
$logger->addWriter($writer);
$logger->info('Your text message');
$logger->info(print_r($object->getData(), true));
Msquare
  • 9,063
  • 7
  • 25
  • 63
2

I tried this below logger object code in a third-party module where I want to get log info there I placed and get them into the custom.log file, check this code, you get the logs into your custom log file.

$writer = new \Zend\Log\Writer\Stream(BP . '/var/log/custom.log');
$logger = new \Zend\Log\Logger();
$logger->addWriter($writer);
$logger->info('Your log details: ' .$variable);
$logger->info(print_r($object->getData(), true));

If the above solution not worked try the below one. directly we inject psr logs into our custom code.

protected $logger;
public function __construct(\Psr\Log\LoggerInterface $logger)
{
    $this->logger = $logger;
}

$this->logger->info($message); $this->logger->debug($message);

If you still finding any issues please let me know. Also, I was updated your code.

Msquare
  • 9,063
  • 7
  • 25
  • 63
Jdprasad V
  • 143
  • 2
  • 11
  • Can i get help thanks : https://paste.ofcode.org/yr83DQAUMaZmGuAHTXDdh2 line from 30- 33 log not created at var/log/custom.log @Jdprasad V – zus Mar 14 '20 at 09:16
  • 1
    I have updated your code please check and let me know. – Jdprasad V Mar 16 '20 at 10:26
2
$writer = new \Zend\Log\Writer\Stream(BP . '/var/log/custom.log');
$logger = new \Zend\Log\Logger();
$logger->addWriter($writer);
$logger->info('Custom text message');  /** To Print Only String **/ 
$logger->info(print_r($object->getData(), true));  /** To Print Object Data **/
Rafiqul Islam
  • 115
  • 10
Milan Maniya
  • 330
  • 3
  • 11
1

Try "praxigento/mage2_ext_logging" module. This module adds "Monolog Cascade" support to Magento 2. "Monolog Cascade" allows you to configure you logging output with single configuration file. You can print out your logs to different files, databases, send email alerts and etc without modifications of your own code.

This is a sample of the configuration file ('var/log/logging.yaml' by default):

disable_existing_loggers: true
formatters:
    dashed:
        class: Monolog\Formatter\LineFormatter
        format: "%datetime%-%channel%.%level_name% - %message%\n"
handlers:
    debug:
        class: Monolog\Handler\StreamHandler
        level: DEBUG
        formatter: dashed
        stream: /.../var/log/cascade_debug.log
    system:
        class: Monolog\Handler\StreamHandler
        level: INFO
        formatter: dashed
        stream: /.../var/log/cascade_system.log
    exception:
        class: Monolog\Handler\StreamHandler
        level: EMERGENCY
        formatter: dashed
        stream: /.../log/cascade_exception.log
processors:
    web_processor:
        class: Monolog\Processor\WebProcessor
loggers:
    main:
        handlers: [debug, system, exception]
        processors: [web_processor]
Alex Gusev
  • 2,009
  • 3
  • 26
  • 45
0

If there is no logic change and only need to change a custom log file name then no need to create custom logger class also just follow below steps

1. in di.xml

 <type name="Magento\Framework\Logger\Monolog">
        <arguments>
            <argument name="name" xsi:type="string">test</argument>
            <argument name="handlers"  xsi:type="array">
                <item name="test" xsi:type="object">NAME_SPACE\Test\Model\Logger\Handler\Debug</item>
            </argument>
        </arguments>
    </type>

2. Handler

<?php
/**
 * Copyright © 2017 Alshaya, LLC. All rights reserved.
 * See LICENSE.txt for license details.
 *
 */
namespace NAME_SPACE\Test\Model\Logger\Handler;

use Magento\Framework\Logger\Handler\Base;

/**
 * Log handler for reports
 */
class Debug extends Base
{
    /**
     * @var string
     */
    protected $fileName = '/var/log/test.log';
}

where ever you needed to log the data you need to call default PSR log
that is

<?php
/**
 *
 * Copyright © 2013-2017 Magento, Inc. All rights reserved.
 * See COPYING.txt for license details.
 */
namespace NAME_SPACE\Test\Controller\Index;

use Psr\Log\LoggerInterface;
class Index extends \Magento\Framework\App\Action\Action
{


    /**
     * @var LoggerInterface
     */
    private $logger;

    /**
     * Show Contact Us page
     *
     * @return void
     */


    public function __construct(
        \Magento\Framework\App\Action\Context $context,
        LoggerInterface $logger
    ) {
        parent::__construct($context);
        $this->logger = $logger;
    }


    public function execute()
    {
        $this->logger->critical((string) 'Test');
        $this->_view->loadLayout();
        $this->_view->renderLayout();
    }
}

so above example will log all debug data to test.log if you needed to change system also you can add below line in di.xml

Pradeep Kumar
  • 8,731
  • 12
  • 60
  • 86
0

I found one form to use this in one line:

<?php
namespace Vendor\Module\Helper;

class Data extends \Magento\Framework\App\Helper\AbstractHelper { public function __construct( \Magento\Framework\App\Helper\Context $context ){ parent::__construct($context); $this->_logger = $this->_logger->withName("myCustomName")->setHandlers(['system' => \Magento\Framework\App\ObjectManager::getInstance()->create('\Magento\Framework\Logger\Handler\Base', ['fileName' => '/var/log/myCustomLog.log'])->setLevel(\Monolog\Logger\Logger::INFO)]); } }

MeChris99
  • 11
  • 2
0
  $writer = new \Laminas\Log\Writer\Stream(BP . '/var/log/test.log');
  $logger = new  \Laminas\Log\Logger();
  $logger->addWriter($writer);
  $logger->info('text message');

for 2.4.2 above code can be used for custom log.

explorer
  • 26
  • 4
0
$writer = new \Zend_Log_Writer_Stream(BP . '/var/log/custom.log');
$logger = new \Zend_Log();
$logger->addWriter($writer);
$logger->info('text message');
moazzams
  • 87
  • 2
  • 9
0

You can use several formats.

Default logger

1: In Magento default format

protected $_logger;
public function __construct(
    ...
    \Psr\Log\LoggerInterface $logger
    ...
) {
    $this->_logger = $logger;
}

public function logExample() {

//To print string Output in debug.log
$this-&gt;_logger-&gt;addDebug('Your Text Or Variables'); 

// To print array Output in system.log
$this-&gt;_logger-&gt;log('600', print_r($yourArray, true));

}

Custom logger

2: This will work with the older version of Magento 2.4.3:

$writer = new \Zend\Log\Writer\Stream(BP . '/var/log/test.log');
$logger = new \Zend\Log\Logger();
$logger->addWriter($writer);
$logger->info('Your text message');
$logger->info(print_r($array, true)); //to log the array

3: This will work with the newest Magento 2.4.3+. it is tested on Magento 2.4.4, 2.4.6 version.

$writer = new \Zend_Log_Writer_Stream(BP . '/var/log/custom.log');
$logger = new \Zend_Log();
$logger->addWriter($writer);
$logger->info('Your text message');
$logger->info(print_r($array, true)); //to log the array
Rafiqul Islam
  • 471
  • 4
  • 8