Lagger — обработка ошибок и отладка в PHP с закрытыми глазами

    image

    Впервые этот инструмент был представлен широкой публике в 2008 году на конференции Highload.ru, после чего был основательно оптимизирован и доработан. О том, что же это за зверь такой и как им удобно пользоваться читаем далее.

    Формат статьи


    Сперва было желание написать подробное руководство-спецификацию по всему функционалу библиотеки. Потом понял, что намного наглядней и понятней будет привести простые примеры её практического использования, а после вкратце перечислить список основных возможностей. И так…

    На пальцах примерах


    config.php — все константы вымышлены, любые совпадения случайны. Важно понимать, что константы эти только для примера приведены. В идеале можно вообще без них всё прописывать (см. далее lagger_init.php).

    <?php

    define('SKIPER_DIR', LOGS_DIR . DIRECTORY_SEPARATOR . 'skip');
    define('SKIPER_EXPIRE', 60 * 60 * 24);
    define('SKIPER_HASH_TEMPLATE', '{file}{line}');

    define('ERRORS_STDOUT', true);
    define('ERRORS_STDOUT_TAGS', null);
    define('ERRORS_STDOUT_TEMPLATE', '<div><font color="red"><b>{type}:</b> {message}<br /><em>{file} [{line}]</em></font></div>');

    define('ERRORS_LOGING', true);
    define('ERRORS_LOGING_TAGS', 'warning,fatal');
    define('ERRORS_LOGING_FILEPATH', LOGS_DIR . DIRECTORY_SEPARATOR . 'errors_log.htm');
    define('ERRORS_LOGING_LIMIT_SIZE', 500000);
    define('ERRORS_LOGING_LIMIT_DAYS', 180);
    define('ERRORS_LOGING_TEMPLATE', '{date} {time} <a href="http://{host}{uri}">http://{host}{uri}</a><br /><b>{type}</b>: {message|htmlentities}<br />{file} [{line}]<hr />');

    define('ERRORS_SMS', false); // TODO: check /library/SmsSender.php before enable it
    define('ERRORS_SMS_TAGS', 'warning,fatal');
    define('ERRORS_SMS_TO', '79627271169,79218550471');
    define('ERRORS_SMS_FROM', 'MyWebSite');
    define('ERRORS_SMS_MESSAGE', 'Web site error, check log at {date} {time}');

    define('ERRORS_EMAIL', true); // TODO: must be TRUE on production server
    define('ERRORS_EMAIL_TAGS', 'warning,fatal');
    define('ERRORS_EMAIL_FROM', 'Lagger <lagger@mywebsite.com>');
    define('ERRORS_EMAIL_TO', 'Jack Johnson <jack_admin@gmail.com>, mike_developer@gmail.com');
    define('ERRORS_EMAIL_SUBJECT', '{type} error in my website');
    define('ERRORS_EMAIL_MESSAGE', "Date: {date} {time}\nURL: http://{host}{uri}\nError({type}): {message}\nSource: {file} [{line}]\n\nPOST:\n{post}\n\nSESSION:\n{session}");

    define('DEBUG_STDOUT', true);
    define('DEBUG_STDOUT_TAGS', 'test,high');
    define('DEBUG_STDOUT_TEMPLATE', '<div><font color="green">{message|htmlentities}</font></div>');

    define('DEBUG_LOGING', true);
    define('DEBUG_LOGING_TAGS', 'sql');
    define('DEBUG_LOGING_FILEPATH', LOGS_DIR . DIRECTORY_SEPARATOR . 'debug_sql_log.csv');
    define('DEBUG_LOGING_LIMIT_SIZE', 500000);
    define('DEBUG_LOGING_LIMIT_DAYS', 7);
    define('DEBUG_LOGING_TEMPLATE', "{date} {time};{process_id|csv};{microtime|csv};{tags|csv};{message|trim|csv}\n");

    * This source code was highlighted with Source Code Highlighter.

    lagger_init.php — непосредственная конфигурация обработчиков Lagger-а. В данном случае из констант конфига, хотя можно и без них.
    <?php

    /**************************************************************
       REGISTER EVENTSPACE VARS
    **************************************************************/

    $laggerES = new Lagger_Eventspace();
    $laggerES->registerReference('host', $_SERVER['HTTP_HOST']);
    $laggerES->registerReference('uri', $_SERVER['REQUEST_URI']);
    $laggerES->registerReference('post', $_POST);
    $laggerES->registerReference('session', $_SESSION); // Session must be already started!
    $laggerES->registerCallback('date', 'date', array('Y-m-d'));
    $laggerES->registerCallback('time', 'date', array('H:i:s'));
    $laggerES->registerCallback('microtime', 'microtime', array(true));
    $laggerES->registerVar('session_id', session_id());
    $laggerES->registerVar('process_id', substr(md5(mt_rand()), 25));

    /**************************************************************
       REGISTER EVENTSPACE MODIFIERS
    **************************************************************/

    function varToStringLine($value) {
      return str_replace(array("\r\n", "\r", "\n"), ' ', is_scalar($value) ? $value : var_export($value, 1));
    }
    $laggerES->registerModifier('line', 'varToStringLine');

    function quoteCSV($string) {
      return varToStringLine(str_replace(';', '\\;', $string));
    }
    $laggerES->registerModifier('csv', 'quoteCSV');

    /**************************************************************
       SKIPER
    **************************************************************/

    $daylySkiper = new Lagger_Skiper($laggerES, SKIPER_HASH_TEMPLATE, SKIPER_EXPIRE, new Lagger_ExpireList(SKIPER_DIR, '.dayly_skiper'));

    /**************************************************************
       LAGGER INTERNAL ERRORS AND EXCEPTIONS HANDLING
    **************************************************************/

    $emailAction = new Lagger_Action_Mail(ERRORS_EMAIL_FROM, ERRORS_EMAIL_TO, ERRORS_EMAIL_SUBJECT, ERRORS_EMAIL_MESSAGE);
    $emailAction->setSkiper($daylySkiper, 'errors_email');

    Lagger_Handler::addInternalErrorAction($emailAction);

    /**************************************************************
       DEBUG HANDLER
    **************************************************************/

    $debug = new Lagger_Handler_Debug($laggerES);

    function toDebug($message, $tags = null) {
      if (isset($GLOBALS['debug'])) {
        $GLOBALS['debug']->handle($message, $tags);
      }
    }

    if (DEBUG_STDOUT) {
      // Allows to rewrite DEBUG_STDOUT_TAGS. Try $_GET['__debug'] = 'high' or $_GET['__debug'] = ''
      $debugTagger = new Lagger_Tagger('__debug');

      $debug->addAction(new Lagger_Action_Print(DEBUG_STDOUT_TEMPLATE), DEBUG_STDOUT_TAGS, $debugTagger);
      $debug->addAction(new Lagger_Action_FirePhp('{message}', '{tags}', FirePHP::INFO), DEBUG_STDOUT_TAGS, $debugTagger);
    }
    if (DEBUG_LOGING) {
      $debug->addAction(new Lagger_Action_FileLog(DEBUG_LOGING_TEMPLATE, DEBUG_LOGING_FILEPATH, DEBUG_LOGING_LIMIT_SIZE, DEBUG_LOGING_LIMIT_DAYS), DEBUG_LOGING_TAGS);
    }

    // Just for fun in windows servers it will speak the text :)
    if(stristr(PHP_OS, 'win') !== false) {
      $debug->addAction(new Lagger_Action_WinSpeak('{message}', 100), 'speak');
    }

    /**************************************************************
       ERRORS AND EXCEPTIONS HANDLERS
    **************************************************************/

    $errors = new Lagger_Handler_Errors($laggerES);
    $exceptions = new Lagger_Handler_Exceptions($laggerES);

    if (ERRORS_STDOUT) {
      $printAction = new Lagger_Action_Print(ERRORS_STDOUT_TEMPLATE, false);
      $errors->addAction($printAction);
      $exceptions->addAction($printAction);
      
      $errorsFirePhpAction = new Lagger_Action_FirePhp('{message} {file} [{line}]', '{type}', FirePHP::ERROR);
      $errors->addAction($errorsFirePhpAction);
      $exceptions->addAction($errorsFirePhpAction);
    }

    if (ERRORS_LOGING) {
      $logAction = new Lagger_Action_FileLog(ERRORS_LOGING_TEMPLATE, ERRORS_LOGING_FILEPATH, ERRORS_LOGING_LIMIT_SIZE, ERRORS_LOGING_LIMIT_DAYS);
      $errors->addAction($logAction, ERRORS_LOGING_TAGS);
      $exceptions->addAction($logAction, ERRORS_LOGING_TAGS);
    }

    if (ERRORS_SMS) {
      $smsAction = new Lagger_Action_Sms(ERRORS_SMS_FROM, ERRORS_SMS_TO, ERRORS_SMS_MESSAGE, true);
      $smsAction->setSkiper($daylySkiper, 'errors_sms');
      $errors->addAction($smsAction, ERRORS_SMS_TAGS);
      $exceptions->addAction($smsAction, ERRORS_SMS_TAGS);
    }

    if (ERRORS_EMAIL) {
      $errors->addAction($emailAction, ERRORS_EMAIL_TAGS);
      $exceptions->addAction($emailAction, ERRORS_EMAIL_TAGS);
    }



    Т.е.


    Есть 3 класса обработчиков событий:
    • Обработчик системных ошибок PHP всех уровней (в том числе E_FATAL & E_COMPILE)
    • Обработчик PHP-исключений (Exceptions)
    • Обработчик сообщений дебага
    Есть 7 классов настраиваемых действий:
    • Запись в лог-файл
    • Отправка Email
    • Отправка SMS
    • Вывод на экран
    • Вывод в консоль и popup-уведомления Google Chrome через расширение PHP Console
    • Вывод в панель FirePHP
    • Генерация исключения (Exception)
    • Озвучивание текста события голосом Sam-а из Microsoft :) Windows only
    Есть много всего прочего:
    • Игнорирование повторной обработки событий (Lagger_Skiper)
    • Мини-шаблонизатор c возможностью расширения списка переменных событий (не пугаться! он очень лёгкий!)
    • Теггирование событий и настройка действий на срабатывание по определённым тегам
    • Динамическое переопределение тегов срабатывания действий через GET (Lagger_Tagger)
    • Обработка внутренних ошибок
    • Всего 20kb лаконичного PHP5 кода (100% ООП)

    100% Open Source


    Скачать последнюю версию
    Примеры вместо документации
    SVN/trunk
    Страница проекта на Google Code

    Всем желающим присоединиться к проекту — очень рад :)
    Всем спасибо за конструктивную критику и отзывы!

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

      +1
      Посмотрел на изменения и увидел WinSpeak.

      Чорт, говорящий дебаггер О.о Зачем такие излишки?
        0
        Ну этот WinSpeak просто забавности ради.
        Он очень маленький — всего 1 класс на 10 строчек, но работает прикольно.

        Это если кто FirePHP не хочет использовать, а под AJAX дебажить и ошибки отслеживать как-то надо, то можно его использовать.
        +5
        правильно понимаю что тут просто лог ошибок разных? ну тогда вы мой герой, так пафосно представить велосипед…

        1. какой толк от ООП если используется туева хуча констант и фсе?
        2. какое отношение лог ошибок имеет к «отладке с закрытыми глазами»?
        3. есть интеграция с чем-либо?
          –4
          Ну смотря что понимать под «просто лог ошибок разных». Я считаю, что тут не просто лог, и не просто ошибок разных.

          1. В константы если не поняли я просто для примера вынес
          2. Отладка понятие растяжимое, в частности речь идёт об обработке ошибок и дебаге
          3. Интеграция с чем? С фреймворками другими интеграции нету. Он изначально абсолютно standalone и быстро прикручиваемый чтобы каждый его использовал как хотел.
          +1
          xdebug уже не доставляет?
            –5
            Сравнили тоже.
            xdebug главным образом ориентирован на профайлинг (выявление узких мест в производительности).

            А как вы будете под ним на AJAX-е отлаживаться?
            А уведомления на Email/SMS он умеет?
            А как вы его вообще сможете использовать если VPS хостинг и он у них не установлен как расширение к PHP?
              0
              во-первых с помощью xdebug и нормальной IDE можно поставить брейкпоинт на нужной строчке кода, пошагово выполнить код, мониторя при этом значения переменных. лично у меня с термином «отладка» ассоциируется именно этот процесс.
              во-вторых отлаживать проект на хостинге попахивает маразмом, отлаживать его надо до выпуска в продакшн.
              в-третьих какая раздница, аяксовый запрос или нет, пхп-скрипт запускается всегда одинаково

              а то, про что вы написали, это лишь средство регистрации ошибок. никакого отношения к отладке оно не имеет, ну разве что косвенное, ведь получив уведомление, узнаешь, где искать ошибку и какой кусок кода надо отлаживать
                –1
                там не только ошибки, а ещё и Lagger_Handler_Debug.

                и тупому понятно, что эта библиотека в первую очередь «обработчик ошибок» и лишь в малой степени может использоваться как дебаггер.

                про профайлинг и трейсинг я нигде не заикался. xdebug это xdebug и нефиг сравнивать грабли с лопатой
                0
                Да, куда же без SMS при дебаге, действительно :)
                P.S. замечательно отлаживаю AJAX с xDebug, что я делаю не так?
                  0
                  Всё правильно делаете :)
                +10
                и главное голос! нет голосовой поддержки, считай калека =)
                0
                по-моему тут только логирование всех ошибок и нотисов и уведомление о них разработчика… какой-либо инструмент для отладки я не увидел
                  –5
                  Под отладкой понимается дебаг, в частности вывод и логирование пользовательских сообщений. См. Lagger_Handler_Debug
                    0
                    вот вы сами и написали что «вывод и логирование»… а где трейсы, профайлинг т.д.?
                      –5
                      Отладка не ограничивается трейсами и профайлингом.

                      Бибилотеки трейса и профайлинга вообще на PHP противопоказано писать т.к. слишком много издержек и большая погрешность получается. Для этих целей нужно расширения всякие типа XDebug использовать.
                  +2
                  О чем вообще статья? Кусок какого то кода без малейших признаков комментариев… Может, стоит всё-таки открыть глаза? :)
                    –6
                    Может мне тогда надо было сперва с объяснения синтаксиса PHP начать?
                    Я то думал на хабре народ достаточно сообразительный, чтобы без комментариев разобраться.

                    Код кстати и так самодокументируемый(по одним названиям можно всё понять), это вам не ассэмблер.
                    0
                    Кошмар!

                    А почему нельзя константы в другом каком-нибудь формате получать, тот же ini файл, если уж совсем фантазии нехватает?
                      0
                      Да какая разница что в качестве конфигурации использовать? XML/ini/константы/базу данных — для особо извращённых, вы ключевое слово «Пример» прочитали?
                        +1
                        То есть если написать «пример» то можно всякий говнокод выкладывать?

                        Какой то плохой пример для подражания.
                          –1
                          ))) а вы судя по профилю на хабре ниразу ничего и не писали.

                          так показали бы пример как надо классно статьи писать и запостили бы что-нибудь… а то всё только троллите
                            0
                            Ну судя по рейтингу и карме, не такой уж я и тролль(не супер конечно, но в плюсе).
                            Конечно же лучшая оборона — нападение, вместо того чтобы говорить по существу начался переход на личности.

                            Просто все плачут что много говнокода вокруг и сами же его выставляют на всеобщее обозрение, этот пост прочитают многие начинающие программисты и решат что «так и надо писать». Не думаю что сложнее будет хранить данные в том же CSV формате, а будет намного нагляднее(меньше времени на просмотр займёт) + меньше места займёт(это не совсем важно в наши дни, но всё же).
                              +1
                              я считаю что мой код далеко не «гавно», см. исходники(вы их наверняка даже в глаза не видели)

                              очень огорчает что на хабре народ в основном делится на пассивных читателей и активных критиков-нытиков. т.е. вот за 2 дня публикации статьи было скачано 50 копий Lagger-а, я даже лично знаю несколько человек, которые теперь внедрили его в свои проекты и остались очень довольны

                              а народ тут пишет про какие-то трейсеры, профайлеры… ей богу, что за бестолковое сравнение? плюсуют по сути идиотские комменты, по инерции минусуют ответы…

                              а ведь была проделана большая работа, но тут все такие самые умные, что помимо неконструктивной критики ничего и сказать не могут! хоть бы кто написал, что архитектура библиотеки неправильная или ещё что-то не оптимально сделано, так нет ведь — даже исходников не видели, даже примеры невнимательно просмотрели, а уже чешут руки написать какой-нить злостный коммент))

                              короче от этой статьи у меня карма только пострадала, но всё равно я буду дальше писать, потому что знаю что большинству мои библиотеки очень помогают в работе… а то, что из 10 возмущённых мнение выскажут все 10, в то время как из 10 довольных только 1-2, то это и ежу понятно
                                –1
                                Я думаю по моему профилю более менее понятно, касательно моих предпочтний, мне исходники были в приницпе неинтересны, потому что мне язык php не нравиться как таковой (причём по многим причинам и вполне обоснованным). Я посмотрел просто как же дебажат в другом ЯП, мне интересны сами принципы, ибо языки разные, а методы в основном похожи.

                                В моём первоначальном комменте я рассчитывал вложить «удивление», нежели какие-то негативные чувства.

                                Могу поспорить что многие не дошли до исходников именно по той же причине, что я обозначил. Пример кода начинаеться с неудобоваримого конфига, собственно вполне интуитивно возникает стереотип некоторого «говнокода» касательно всего оставшегося.

                                А насчёт того, почему не пишу… просто пока времени нету, пару тем классных придумал, даже есть набросок, но никак руки не дойдут…
                      +2
                      Суть статьи осталась неясна. Зато карикатура позабавила.
                        –11
                        Может вам сперва синтаксис PHP изучить?
                          +8
                          синтаксис языка тут не причем. из поста только один вывод нужно вынести: автору нужно потренироваться над донесением сути материала. имхо это так
                        –2
                        Что только не изобретают вместо того, чтобы отладчик и профайлер настроить.
                          0
                          вот насчет ошибок в php. я сам немного в другой области специализируюсь, посему хочу спросить:
                          как мне найти ошибку в синтаксисе файла, который подключен по include?

                          я написал код вроде этого:
                          <?php 
                           error_reporting(E_ALL); 
                           ini_set("display_errors", 1); 
                           include("admin/admin.php"); 
                          ?>


                          Естественно, это покажет мне только ошибки для конкретного файла и только для конкретного вызова. А как мне это дело унифицировать?
                          Заморачиваться на различные дебаггеры и профилировщики не очень хочется. Нет ли какого-нибудь решения, которое просто выведет мне ошибку вверху страницы и прекратит выполнение?
                          Красивая страница «Упс, что-то случилось!» и попадение ошибки в бд меня не очень интересует: я чаще допускаю просто синтаксические ошибки и мне хочется видеть их при обращении к странице.

                          Насколько понимаю, ключ решения в правильной конфигурации php. Не подскажете, что надо настроить?
                            0
                            Вообще конечно не очень хорошо так делать, пользователи ваши ошибки будут видеть, а это и некрасиво и небезопасно.

                            но если очень надо, то

                            вариант 1:

                            прописать в php.ini
                            error_reporting = E_ALL
                            display_errors = On

                            вариант 2:
                            прописывать в .htaccess

                              0
                              вариант 2:
                              прописывать в .htaccess
                              php_value error_reporting = E_ALL
                              php_flag display_errors off
                                0
                                Т.е.

                                вариант 2:
                                прописывать в .htaccess
                                php_value error_reporting = E_ALL
                                php_flag display_errors on
                                  0
                                  отлично. Спасибо, это то, что надо
                                  0
                                  Безопасный способ: tail -F /var/путь-к-логу-ошибок
                                0
                                Я не понял зачем это нужно.
                                  +3
                                  увидев ваш аватар, подумал что у меня монитор грязный :)
                                  0
                                  Интересно, а как эта библиотека отлавливает ошибки уровней E_FATAL & E_COMPILE? Насколько я знаю при фатальных ошибках php записывает данные об ошибке в файл (указанный в php.ini) и прекращает работу, не давая эту ошибку обработать.
                                  P.S. дефайны и глобалс реальное зло!
                                    0
                                    ну… как-то отлавливает(см. исходники), стандартными средствами php без каких либо извратов типа парсинга STDOUT

                                    константы это для примера если не поняли, можете их не использовать. а $GLOBALS по мере необходимости, тоже для примера.
                                    можете и без него свою класс-синглтон обёртку для Lagger_Handler_Debug написать, только чего ради))
                                      0
                                      Хотя бы ради того что-бы не нарушать концепцию лаконичного 100%-го ООП PHP5 кода. Да и смотрелось бы оно гораздо приятнее в виде отдельного класса, который кстати можно было бы включить в проект в качестве примера.
                                        0
                                        У библиотеки есть своя концепция и ваше мнение с ней противоречит.

                                        Она подразумевает, что в один момент времени может существовать более 1 обработчика (что актуально в сложных системах, в которых на разных уровнях могут разные обработчики с несовместимыми конфигурациями).

                                        Класс-синглтон-обётка для дебагера пишется за 30 секунд, а вы на одну только эту переписку со мной в 5 раз больше времени потратили :)
                                          0
                                          Не обязательно делать синглтон. Можно просто класс обертку-иницилазатор, все равно смотрелся бы он лучше чем портянка кода с кучей констант. Просто код из примера совсем не ООП стиль :)
                                            0
                                            Библиотека на 100% ООП, а вот способы её использования уж точно не обязаны в концепции ООП вписываться :)

                                            Примеры использования это совсем другой уровень, там каждый изголяется как ему удобней… хотя понимаю $GLOBALS чуток дико смотрится, но просто не хотел код примера дополнительными классами-обёртками раздувать.
                                              0
                                              Способы использования конечно не обязаны быть в концепции ООП, но было бы красиво :) Плюс такой пример можно было бы с незначительными переделками использовать в своем проекте, а этот как-то не очень хочется.
                                    0
                                    Очень не хватает PHPDoc коментов в коде классов
                                      0
                                      Частично согласен, но думал людям не так критично будет. Теперь вижу, что комменты не помешают. В следующей версии будут :)

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

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