Использование 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
    


    Конфигурация Log4php


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

    Пример конфигурации
    <?xml version="1.0" encoding="UTF-8"?>
    <configuration xmlns="http://logging.apache.org/log4php/">
        <!-- ***********************************************************************************************
            THIS IS SAMPLE CONFIGURATION THAT WILL BE REWRITTEN ON REINSTALL.
            DON'T USE THIS FILE IN YOUR CONFIGURATION. MAKE A COPY TO "app/etc/log4php.cfg.xml" AND CHANGE
            "SYSTEM / CONFIGURATION / DEVELOPER / LOG SETTINGS / LOG4PHP CONFIG FILE NAME" VALUE
        *********************************************************************************************** -->
    
        <!--
                Available log levels:
                    ALL -> TRACE -> DEBUG -> INFO -> WARN -> ERROR -> FATAL -> OFF
                Available appenders:
                    http://logging.apache.org/log4php/docs/appenders.html
                    appenders/LoggerAppenderMemory
                Available layouts:
                    http://logging.apache.org/log4php/docs/layouts.html
        -->
    
        <!-- default appender -->
        <appender name="fileDefault" class="LoggerAppenderFile">
            <layout class="LoggerLayoutPattern">
                <param name="conversionPattern" value="%d{Y/m/d H:i:s,u} %p %c{2}: %m%n"/>
            </layout>
            <param name="file" value="/absolute/path/to/file/log4php.log"/>
            <param name="append" value="true"/>
        </appender>
    
        <!-- In-memory log for Adminhtml (backend sync output) -->
        <appender name="memoryDefault" class="LoggerAppenderMemory" threshold="DEBUG">
            <layout class="LoggerLayoutPattern">
                <param name="conversionPattern" value="%date{H:i:s,u} %p %c{2}: %msg%n"/>
            </layout>
        </appender>
    
        <!-- ERROR mail appender -->
        <appender name="emailError" class="LoggerAppenderMail">
            <layout class="LoggerLayoutPattern">
                <param name="conversionPattern" value="%d{Y/m/d H:i:s,u} %p %c{2}: %m%n"/>
            </layout>
            <param name="to" value="comma-separeted@emails.com"/>
            <param name="from" value="project-name@praxigento.com"/>
            <filter class="LoggerFilterLevelRange">
                <param name="levelMin" value="ERROR"/>
                <param name="levelMax" value="FATAL"/>
            </filter>
        </appender>
    
        <!-- root logger -->
        <root>
            <appender_ref ref="fileDefault"/>
            <appender_ref ref="memoryDefault"/>
            <appender_ref ref="emailError"/>
            <level value="ALL"/>
        </root>
    
    </configuration>
    



    Бонусы Log4php



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


    При создании логгера используется имя класса в котором этот логгер используется:
            $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'
    


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


    В зависимости от выбранного в конфигурационном файле уровня можно детализировать вывод в лог в зависимости от нагрузки или в случае возникновения сбоев в приложении. Причем можно лить в отдельный лог все сообщения от какого-либо класса или группы классов:
        <root>
            <appender_ref ref="defaultLog"/>
            <level value="INFO"/>
        </root>
        <logger name="Praxigento_Bonus_Test_Logger_UnitTest">
            <appender_ref ref="failureLog"/>
            <level value="TRACE"/>
        </logger>
    


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


    В случае возникновения ошибок (уровни fatal, error, warn) можно отправлять соответствующие лог-записи по email'у:
        <appender name="emailInfo" class="LoggerAppenderMail">
            <layout class="LoggerLayoutPattern">
                <param name="conversionPattern" value="%d{Y/m/d H:i:s,u} %p %c{2}: %m%n"/>
            </layout>
            <param name="to" value="support@praxigento.com,developers@praxigento.com"/>
            <param name="from" value="some-project@prxgt.com"/>
            <filter class="LoggerFilterLevelRange">
                <param name="levelMin" value="WARN"/>
                <param name="levelMax" value="FATAL"/>
            </filter>
        </appender>
    


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


    В модуле к оригинальным Log4php апендерам добавлен дополнительный класс LoggerAppenderMemory, который позволяет получать доступ к логам из Magento-приложения:
        <appender name="memoryDefault" class="LoggerAppenderMemory" threshold="DEBUG">
            <layout class="LoggerLayoutPattern">
                <param name="conversionPattern" value="%date{H:i:s,u} %p %c{2}: %msg%n"/>
            </layout>
        </appender>
    


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


    <pre class="prxgt_console">
        <?php  foreach ($debugLog as $entry) {
            echo trim($entry) . "\n";
        }?>
    </pre>
    


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


    Установка


    На данный момент мы для развертывания наших 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-приложения.
    Поделиться публикацией

    Похожие публикации

    Комментарии 2

      0
      «Обертка» для «обертки»

      Занимательно. Под nginx фокус не удастся?
        0
        Это все дело на уровне PHP-кода отрабатывает, там нет зависимости от web-сервера. У нас приложения и под nginx разворачиваются, и под apache'м — логирование работает в обоих случаях.

        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

        Самое читаемое