Главная страница статей --> Советы по фотошопу, графике и хитрости в построении php кода

Ловля ошибок в PHP

Источник: realcoding.net

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

Кроме того, именно эта служебная информация обычно помогает злым хакерам ломать сайт. В качестве классического примера можно привести вариант с выводом запроса при ошибке: you have an error in query near WHERE id= ... Большое спасибо. Подставляем после WHERE id=... строку 0 OR 1>0 и запрос выполняется по всей таблице. Если запрос на удаление, то...сами понимаете, весело =). Поэтому я всегда переменные в запросах заключаю в кавычки. На всякий случай...

Но я увлекся. Сегодня не об этом. Сегодня поговорим о том, как избежать вывода ошибок клиенту, сохранив при этом все сообщения вебмастеру на память.

Начнем, пожалуй, с краткого обзора видов ошибок в РНР.

Таблица 1. Описания ошибок в PHP4 (оригинальный список)

Числовое значениеКонстантаОписаниеЛовится/нет
1E_ERRORФатальные ошибки. Например, ошибка при обращении к памяти. Выполнение скрипта при этом прерывается.нет
2E_WARNINGПредупреждения (не фатальные ошибки). Выполнение скрипта не прерывается.да
4E_PARSEОшибки во время анализа синтаксиса. Генерируются парсером.нет
8E_NOTICEЗамечания (менее серьезные ошибки, чем предупреждения). Указывают на ситуацию, которая может стать причиной более серьезной ошибки, но могут случаться и в процессе нормальной работы скрипта.да
16E_CORE_ERRORОшибки во время загрузки РНР. Аналог E_ERROR, генерируется ядром РНР.нет
32E_CORE_WARNINGПредупреждения во время загрузки РНР Аналог E_WARNING, генерируется ядром РНР.нет
64E_COMPILE_ERRORФатальные ошибки во время компиляции кода. Аналог E_ERROR, генерируется зендовским движком.нет
128E_COMPILE_WARNINGПредупреждения во время компиляции кода. Аналог E_WARNING, генерируется зендовским движком.нет
256E_USER_ERRORПользовательская ошибка.да
512E_USER_WARNINGПользовательское предупреждение.да
1024E_USER_NOTICEПользовательское замечаниеда

Нас интересуют те ошибки, которые мы можем перехватить. К ним относятся: E_WARNING, E_NOTICE и E_USER_*. Остальные виды ошибок перехвату не поддаются либо из-за того, что происходят они еще до окончания загрузки самого ядра РНР, либо из-за того, что происходят на этапе синтаксического анализа и компилирования РНР-кода, поэтому их вывод придется просто отключить:

ini_set(display_errors,0);

Но я предполагаю, что наши скрипты достаточно отлажены, чтобы в них не было элементарных синтаксических ошибок, поэтому потерять мы ничего не должны.

По умолчанию уровень ошибок в РНР имеет значение E_ALL & ~E_NOTICE (или 2039 в числовой форме), что означает, что мы пропускаем мимо ушей замечания, но сообщаем о всех остальных ошибках.

Кстати, сами разработчики рекомендуют включать на стадии разработки и E_NOTICE - помогает обнаружить потенциально опасные места.

Поэтому изменим уровень вывода ошибок на E_ALL:

error_reporting(E_ALL);

Теперь переопределим хэндлер ошибок и подставим вместо него нашу функцию user_log(), которая и будет заниматься теперь обработкой ошибок:

set_error_handler(user_log);

Рассмотрим эту функцию подробней. Ей передаются 5 параметров:

  • код ошибки
  • текст ошибки
  • имя файла, в котором произошла ошибка
  • строка в файле
  • массив переменных

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

Итак, код с комментариями:

<?php

/* Наша функция-хэндлер */
function user_log ($errno, $errmsg, $file, $line) {
    // время события
    $timestamp = time();

    //формируем новую строку в логе
    $err_str = $timestamp.;
    $err_str .= $errno.;
    $err_str .= $file.;     
    $err_str .= $line.;
    $err_str .= $errmsg."\n";

    //проверка на максимальный размер
    if (is_file(LOG_FILE_NAME) AND filesize(LOG_FILE_NAME)>=(LOG_FILE_MAXSIZE*1024)) {
        //проверяем настройки, если установлен лог_ротэйт,
        //то "сдвигаем" старые файлы на один вниз и создаем пустой лог
        //если нет - чистим и пишем вместо старого лога
        if (LOG_ROTATE===true) {
            $i=1;
            //считаем старые логи в каталоге
            while (is_file(LOG_FILE_NAME...$i)) { $i++; }
            $i--;
            //у каждого из них по очереди увеличиваем номер на 1
            while ($i>0) {
               rename(LOG_FILE_NAME....$i,LOG_FILE_NAME. . .(1+$i--));
            }
            rename (LOG_FILE_NAME,LOG_FILE_NAME..1);
            touch(LOG_FILE_NAME);
        }
        elseif(is_file(LOG_FILE_NAME)) {
            //если пишем логи сверху, то удалим
            //и создадим заново пустой файл
            unlink(LOG_FILE_NAME);
            touch(LOG_FILE_NAME);
        }
    }

    /*
    проверяем есть ли такой файл
    если нет - можем ли мы его создать
    если есть - можем ли мы писать в него
    */
    if(!is_file(LOG_FILE_NAME)) {
        if (!touch(LOG_FILE_NAME)) {
            return can\t create log file;
        }
    }
    elseif(!is_writable(LOG_FILE_NAME)) {
        return can\t write to log file;
    }
   
    //обратите внимание на функцию, которой мы пишем лог.
    error_log($err_str, 3, LOG_FILE_NAME);
}

?>

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

Собственно, это все. Остальное, я думаю, не составит для вас труда, особенно, если пользоваться функциями file(); & explode();. А если все-таки составит, то вы можете воспользоваться [вот этим кодом].

Предвидя вопрос почему я не использовал CSV, который, казалось бы, логично использовать в этой ситуации?, отвечаю: сообщения об ошибках могут содержать неизвестное количество служебных символов (ака запятых и точек с запятой), что явно затруднило бы разбор CSV. Да и не собираюсь я просматривать лог в Экселе.

Еще разные мысли на эту тему:

  • при устаревании лога gzиповать файл и складывать его в архив;
  • то же, но с посылкой на почту;
  • при возникновении критических ошибок - слать мэйл;
  • для мазохистов можно использовать при этом XML;

Вздохнули спокойно? Я надеюсь, что нет. Ибо переопределение еррор-хэндлера - это никак не панацея, просто одна из удобных фич РНР.

Кто предупрежден, тот защищен - так ведь?

ps Признаю, немного параноидален. Но лучше два раза проверить, чем один раз сделать ошибку.

ps/2 По просьбе Maxim Naumenko добавляю комменты к статье:

Q: Ну и чем это лучше, чем просто в php.ini указать error_log = log_file.log ?


A: Файл пишется в нашем формате. Нам же потом этот файл смотреть надо. Плюс - можно делать что угодно с этими ошибками (файл - это просто для примера). А в случае с error_log = - они ТОЛЬКО пишутся в файл и ничего более. Да и не везде вас пустят к php.ini.



Похожие статьи:
- Правильная оптимизация сайта с фреймами
- Часто задаваемые вопросы (FAQ) об ASP
- Управление сценариями просмотра Web-страниц
- Сложные регулярные выражения
- Развитие объектной ориентированности PHP
- Долой процедурное программирование, даешь объектно-ориентированное!
- Пароль на страницу. Часть 1.
- Страница сгенерирована за ...
- Защищаем Perl
- Работа с шаблонами, использование HTML-Template с CGI-скриптами
- Управление доступом к базам MySQL
- Практическое пособие по написанию слоганов для веб-сайтов
- Кроссбраузерный DHTML


Оглавление | Обсудить на форуме | Главная страница сайта | Карта сайта |

Контакты
Редакция:
[0.002]