Ловля ошибок в PHP |
||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
На серъезных сайтах странно видеть, когда ошибки выводятся пользователю в браузер в самых неожиданных местах. Почему они выводятся? Ведь текст ошибок является информацией для дебага и предназначена для разработчика, а не для клиента. Пишем программу, делающую лог ошибок и прячем их от пользователя. На серъезных сайтах странно видеть, когда ошибки выводятся пользователю в браузер в самых неожиданных местах. Почему они появляются - это отдельный разговор. Но почему они выводятся ? Ведь текст ошибок является информацией для дебага и предназначена для разработчика, а не для клиента. Кроме того, именно эта служебная информация обычно помогает злым хакерам
ломать сайт. В качестве классического примера можно привести вариант с выводом
запроса при ошибке: Но я увлекся. Сегодня не об этом. Сегодня поговорим о том, как избежать вывода ошибок клиенту, сохранив при этом все сообщения вебмастеру на память. Начнем, пожалуй, с краткого обзора видов ошибок в РНР. Таблица 1. Описания ошибок в PHP4 (оригинальный список)
Нас интересуют те ошибки, которые мы можем перехватить. К ним относятся: 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); } ?> Весь код вы можете посмотреть тут или взять все в архиве. Можно было бы, конечно, использовать более логичное для таких целей хранилище - базу, но ведь ошибки, в большинстве своем, возникают именно при работе с базой, поэтому я бы на нее не полагался. Собственно, это все. Остальное, я думаю, не составит для вас труда, особенно,
если пользоваться функциями Предвидя вопрос "почему я не использовал CSV, который, казалось бы, логично использовать в этой ситуации?", отвечаю: сообщения об ошибках могут содержать неизвестное количество служебных символов (ака запятых и точек с запятой), что явно затруднило бы разбор CSV. Да и не собираюсь я просматривать лог в Экселе. Еще разные мысли на эту тему:
Вздохнули спокойно? Я надеюсь, что нет. Ибо переопределение еррор-хэндлера - это никак не панацея, просто одна из удобных фич РНР. Кто предупрежден, тот защищен - так ведь? ps Признаю, немного параноидален. Но лучше два раза проверить, чем один раз сделать ошибку. ps/2 По просьбе Maxim Naumenko добавляю комменты к статье: |