Ловля ошибок в PHP
Автор: Антон Довгаль
Кроме того, именно эта служебная информация обычно помогает злым хакерам ломать сайт. В качестве классического примера можно привести вариант с выводом запроса при ошибке:
"you have an error in query near WHERE id= "
...
Большое спасибо. Подставляем после "WHERE id=..." строку "0 OR
1>0" и запрос выполняется по всей таблице. Если запрос на
удаление, то...сами понимаете, весело =). Поэтому я всегда
переменные в запросах заключаю в кавычки. На всякий случай... Но я увлекся. Сегодня не об этом. Сегодня поговорим о том, как избежать вывода ошибок клиенту, сохранив при этом все сообщения вебмастеру на память.
Начнем, пожалуй, с краткого обзора видов ошибок в РНР.
Таблица 1. Описания ошибок в PHP4
Числовое значение |
Константа | Описание | Ловится/нет |
---|---|---|---|
1 | E_ERROR | Фатальные ошибки. Например, ошибка при обращении к памяти. Выполнение скрипта при этом прерывается. | нет |
2 | E_WARNING | Предупреждения (не фатальные ошибки). Выполнение скрипта не прерывается. | да |
4 | E_PARSE | Ошибки во время анализа синтаксиса. Генерируются парсером. | нет |
8 | E_NOTICE | Замечания (менее серьезные ошибки, чем предупреждения). Указывают на ситуацию, которая может стать причиной более серьезной ошибки, но могут случаться и в процессе нормальной работы скрипта. | да |
16 | E_CORE_ERROR | Ошибки во время загрузки РНР. Аналог E_ERROR, генерируется ядром РНР. | нет |
32 | E_CORE_WARNING | Предупреждения во время загрузки РНР Аналог E_WARNING, генерируется ядром РНР. | нет |
64 | E_COMPILE_ERROR | Фатальные ошибки во время компиляции кода. Аналог E_ERROR, генерируется зендовским движком. | нет |
128 | E_COMPILE_WARNING | Предупреждения во время компиляции кода. Аналог E_WARNING, генерируется зендовским движком. | нет |
256 | E_USER_ERROR | Пользовательская ошибка. | да |
512 | E_USER_WARNING | Пользовательское предупреждение. | да |
1024 | E_USER_NOTICE | Пользовательское замечание | да |
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'иповать файл и складывать его в архив;
- то же, но с посылкой на почту;
- при возникновении критических ошибок - слать мэйл (см. пример из мануала по функции set_error_handler);
- для мазохистов можно использовать при этом XML.
Кто предупрежден, тот защищен - так ведь?
ps Признаю, немного параноидален. Но лучше два раза проверить, чем один раз сделать ошибку.
ps2 По просьбе Maxim Naumenko добавляю комменты к статье:
Q: Ну и чем это лучше, чем просто в php.ini
указать error_log = "log_file.log"
?
A: Файл пишется в нашем формате. Нам
же потом этот файл смотреть надо. Плюс - можно делать что
угодно с этими ошибками (файл - это просто для примера). А в
случае с error_log = "" - они ТОЛЬКО пишутся в файл и ничего
более. Да и не везде вас пустят к
php.ini.
0 коментарі:
Отправить комментарий