57

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.

99

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 which uses 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="../../../../../lib/internal/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->_logger = $logger;
    }

    public function doSomething()
    {
        $this->_logger->info('I did something');
    }
}
  • 2
    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
  • 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
  • 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! – Robbie Averill Dec 15 '15 at 21:06
  • 2
    For me it's always taking the /var/log/system.log, any idea why? – MagePsycho Mar 25 '16 at 20:46
20

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());
  • 2
    this is quick and easy – PMB Dec 26 '17 at 10:15
9

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');
6

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->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 seperate 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->_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>

    <type name="MyNamespace\MyModule\MyClass">
        <arguments>
            <argument name="logger" xsi:type="object">MyNamespace\MyModule\Logger\Logger</argument>
        </arguments>
    </type>
</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.

5

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'));
}
  • pushHandler is not exposed method on the interface and the implementation does not seem to work... – George Jan 28 at 13:00
  • Your Magento version? – mshakeel Jan 28 at 13:05
  • Magento CE 2.2.0 – George Jan 28 at 13:10
  • I will try it on CE 2.2.0 and get back to you. I have used it on 2.1 – mshakeel Jan 29 at 7:22
2

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]
1

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

0

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 custom.log file, check this code, you definitely 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);

If you required more information comment here, I will respond. Thank you.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.