Использование Log4php в Magento-приложениях

Magento без сомнения является выдающейся системой для построения e-commerce приложений. Принципы, заложенные в ее архитектуру, позволили не только занять первое место рейтинга в своем классе web-приложений и удерживать его многие годы, но, что более важно, сформировать вокруг себя эко-систему разработчиков, создающих для основного функционала расширения, удовлетворяющие самым экзотическим требованиям пользователей. Но вот чего мне сильноочень сильно не хватало в Magento при первом «подходе к снаряду», так это системы логирования уровня Log4php. Поэтому первое расширение, которое было сделано для Magento — это «обертка» для Log4php. Под катом описание того, как мы используем эту «обертку» в наших Magento-проектах.
Чтобы уменьшить зависимость других наших модулей от Praxigento_Log, каждый наш модуль содержит свой собственный logger, который в зависимости от конфигурации Magento-приложения использует либо «родной» Magento-logger, либо Log4php, если установлен модуль Praxigento_Log.

Вот код этой обертки
class Praxigento_Module_Logger
{
    private static $_isLog4phpUsed = null;
    private $_loggerLog4php;
    private $_name;

    function __construct($name)
    {
        /**
         * switch off/on error reporting to prevent messages like
         * "ERR (3): Warning: include(Praxigento\Log\Logger.php): failed to open stream: No such file or directory"
         * in case of Praxigento_Log module is not used.
         */
        $level = error_reporting(0);
        self::$_isLog4phpUsed = class_exists('Praxigento_Log_Logger', true);
        error_reporting($level);
        if (self::$_isLog4phpUsed) {
            $this->_loggerLog4php = Praxigento_Log_Logger::getLogger($name);
        } else {
            $this->_name = is_object($name) ? get_class($name) : (string)$name;
        }
    }

    /**
     * Override getter to use '$log = Praxigento_Log_Logger::getLogger($this)' form in Mage classes.
     */
    public static function getLogger($name)
    {
        $class = __CLASS__;
        return new $class($name);
    }

    public function debug($message, $throwable = null)
    {
        $this->doLog($message, $throwable, 'debug', Zend_Log::INFO);
    }

    /**
     * Internal dispatcher for the called log method.
     */
    private function doLog($message, $throwable, $log4phpMethod, $zendLevel)
    {
        if (self::$_isLog4phpUsed) {
            $this->_loggerLog4php->$log4phpMethod($message, $throwable);
        } else {
            Mage::log($this->_name . ': ' . $message, $zendLevel);
            if ($throwable instanceof Exception) {
                Mage::logException($throwable);
            }
        }
    }

    public function error($message, $throwable = null)
    {
        $this->doLog($message, $throwable, 'error', Zend_Log::ERR);
    }

    public function fatal($message, $throwable = null)
    {
        $this->doLog($message, $throwable, 'fatal', Zend_Log::CRIT);
    }

    public function info($message, $throwable = null)
    {
        $this->doLog($message, $throwable, 'info', Zend_Log::NOTICE);
    }

    public function trace($message, $throwable = null)
    {
        $this->doLog($message, $throwable, 'trace', Zend_Log::DEBUG);
    }

    public function warn($message, $throwable = null)
    {
        $this->doLog($message, $throwable, 'warn', Zend_Log::WARN);
    }
}


Этот код идентичен для всех наших модулей (за исключением названия самого класса) и позволяет использовать логирование вне зависимости от того есть ли в приложении модуль Praxigento_Log или его нет. Вот, собственно, пример вызова:

        $log = Praxigento_Module_Logger::getLogger(__CLASS__);
        $log->trace('trace level message');
        $log->debug('debug level message');
        $log->info('info level message');
        $log->warn('warn level message');
        $log->error('error level message');
        $log->fatal('fatal level message');

А вот и сами логи при наличии модуля Praxigento_Log (Log4php framework):

2015/06/25 09:59:55,973 TRACE P\B\T\L\UnitTest: trace level message
2015/06/25 09:59:55,973 DEBUG P\B\T\L\UnitTest: debug level message
2015/06/25 09:59:55,974 INFO P\B\T\L\UnitTest: info level message
2015/06/25 09:59:55,974 WARN P\B\T\L\UnitTest: warn level message
2015/06/25 09:59:55,975 ERROR P\B\T\L\UnitTest: error level message
2015/06/25 09:59:55,975 FATAL P\B\T\L\UnitTest: fatal level message

… и при его отсутствии (Zend_Log framework, файл var/log/system.log):

2015-06-25T10:07:00+00:00 DEBUG (7): Praxigento_Bonus_Test_Logger_UnitTest: trace level message
2015-06-25T10:07:00+00:00 INFO (6): Praxigento_Bonus_Test_Logger_UnitTest: debug level message
2015-06-25T10:07:00+00:00 NOTICE (5): Praxigento_Bonus_Test_Logger_UnitTest: info level message
2015-06-25T10:07:00+00:00 WARN (4): Praxigento_Bonus_Test_Logger_UnitTest: warn level message
2015-06-25T10:07:00+00:00 ERR (3): Praxigento_Bonus_Test_Logger_UnitTest: error level message
2015-06-25T10:07:00+00:00 CRIT (2): Praxigento_Bonus_Test_Logger_UnitTest: fatal level message


В Magento указывается путь к конфигурационному файлу Log4php:
image
в котором и настраиваются все параметры логирования.

Пример конфигурации


    

    

    
    
        
            
        
        
        
    

    
    
        
            
        
    

    
    
        
            
        
        
        
        
            
            
        
    

    
    
        
        
        
        
    




Источник записи


При создании логгера используется имя класса в котором этот логгер используется:

        $log = Praxigento_Module_Logger::getLogger(__CLASS__);


в результате, в логах можно отличить сообщения одного класса от сообщений другого. Так для класса Praxigento_Bonus_Test_Logger_UnitTest сообщение в логе будет выглядеть примерно так (зависит от настроек логирования):

2015/06/25 09:59:55,973 TRACE P\B\T\L\UnitTest: trace level message


что бывает удобно, когда в общем потоке нужно выделить сообщения от конкретного класса:

$ cat  ./var/log/old/log4php.log_20150623 | grep 'P\\B\\T\\L\\UnitTest'

Дифференциация по уровню логирования и источнику


В зависимости от выбранного в конфигурационном файле уровня можно детализировать вывод в лог в зависимости от нагрузки или в случае возникновения сбоев в приложении. Причем можно лить в отдельный лог все сообщения от какого-либо класса или группы классов:

    
        
        
    
    
        
        
    

Email-оповещения


В случае возникновения ошибок (уровни fatal, error, warn) можно отправлять соответствующие лог-записи по email’у:

    
        
            
        
        
        
        
            
            
        
    

In-memory логирование


В модуле к оригинальным Log4php апендерам добавлен дополнительный класс LoggerAppenderMemory, который позволяет получать доступ к логам из Magento-приложения:

    
        
            
        
    

$log = Praxigento_Module_Logger::getLogger(__CLASS__);
$log->debug('...');
$debugLog = LoggerAppenderMemory::getEventsAsArray();

    

В наших приложениях мы используем подобный подход в случае, если приходится из админки запускать задачи, которые обычно выполняются через cron. Список лог-записей просто выводится в web-интерфейс с требуемым уровнем детализации:
414ebf87de0646b59ea972941dc79cc0.png


На данный момент мы для развертывания наших Magento-проектов используем Composer, поэтому описание установки дается для этого метода. Установка модуля также возможна через Magento Connect (там он проходит под предыдущим названием — Nmmlm_Log).

Для подключения модуля через Composer нужно указать в файле composer.json:

{
  "require": {
    "praxigento/mage_ext_log4php": "*"
  },
  "repositories": [
    {
      "type": "vcs",
      "url": "https://github.com/praxigento/mage_ext_log4php"
    }
  ]
}

Можно также закачать исходники модуля, распаковать его и скопировать содержимое из каталога ./src/ в корневой каталог вашего Magento-приложения.

© Habrahabr.ru