Как мне поймать PHP фатальную ошибку
Я могу использовать set_error_handler()
поймать большинство ошибок PHP, но это не работает для фатальной (E_ERROR
) ошибки, такие как вызов функции, которая не существует. Есть ли другой способ отловить эти ошибки?
Я пытаюсь позвонить mail()
за все ошибки и работаю PHP 5.2.3.
19 ответов
Регистрируйте фатальные ошибки, используя register_shutdown_function
, который требует PHP 5.2+:
register_shutdown_function( "fatal_handler" );
function fatal_handler() {
$errfile = "unknown file";
$errstr = "shutdown";
$errno = E_CORE_ERROR;
$errline = 0;
$error = error_get_last();
if( $error !== NULL) {
$errno = $error["type"];
$errfile = $error["file"];
$errline = $error["line"];
$errstr = $error["message"];
error_mail(format_error( $errno, $errstr, $errfile, $errline));
}
}
Вы должны будете определить error_mail
а также format_error
функции. Например:
function format_error( $errno, $errstr, $errfile, $errline ) {
$trace = print_r( debug_backtrace( false ), true );
$content = "
<table>
<thead><th>Item</th><th>Description</th></thead>
<tbody>
<tr>
<th>Error</th>
<td><pre>$errstr</pre></td>
</tr>
<tr>
<th>Errno</th>
<td><pre>$errno</pre></td>
</tr>
<tr>
<th>File</th>
<td>$errfile</td>
</tr>
<tr>
<th>Line</th>
<td>$errline</td>
</tr>
<tr>
<th>Trace</th>
<td><pre>$trace</pre></td>
</tr>
</tbody>
</table>";
return $content;
}
Используйте Swift Mailer, чтобы написать error_mail
функция.
Смотрите также:
Только что придумали это решение (PHP 5.2.0+):
function shutDownFunction() {
$error = error_get_last();
// fatal error, E_ERROR === 1
if ($error['type'] === E_ERROR) {
//do your stuff
}
}
register_shutdown_function('shutDownFunction');
Различные типы ошибок, определенные на http://www.php.net/manual/en/errorfunc.constants.php
PHP не предоставляет традиционных средств для обнаружения и восстановления после фатальных ошибок. Это потому, что обработка не должна быть восстановлена после фатальной ошибки. Строка, совпадающая с выходным буфером (как было предложено в оригинальном посте, описанном в PHP.net), явно не рекомендуется. Это просто ненадежно.
Вызов функции mail() из метода обработчика ошибок также оказывается проблематичным. Если бы у вас было много ошибок, ваш почтовый сервер был бы загружен работой, и вы могли бы оказаться с мрачным почтовым ящиком. Чтобы избежать этого, вы можете использовать cron для периодического сканирования журналов ошибок и соответствующей отправки уведомлений. Вам также может понравиться программное обеспечение для мониторинга системы, такое как Nagios.
Чтобы немного рассказать о регистрации функции выключения:
Это правда, что вы можете зарегистрировать функцию выключения, и это хороший ответ.
Дело в том, что мы обычно не должны пытаться восстанавливаться после фатальных ошибок, особенно не используя регулярное выражение для вашего выходного буфера. Я отвечал на принятый ответ, который связан с предложением на php.net, которое с тех пор было изменено или удалено.
Это предложение заключалось в том, чтобы использовать регулярное выражение в выходном буфере во время обработки исключений, а в случае фатальной ошибки (обнаруженной при сопоставлении с любым сконфигурированным текстом ошибки, который вы могли ожидать) попытаться выполнить какое-либо восстановление или продолжить обработку. Это не было бы рекомендуемой практикой (я полагаю, поэтому я не могу найти оригинальное предложение тоже. Я либо пропускаю его, либо сообщество php сняло его).
Возможно, стоит отметить, что более поздние версии PHP (около 5.1), по-видимому, вызывают функцию завершения работы раньше, чем вызывается обратный вызов буферизации вывода. В версии 5 и более ранних версиях этот порядок был обратным (за обратным вызовом буферизации вывода следовала функция выключения). Кроме того, начиная с версии 5.0.5 (что намного раньше, чем версия 5.2.3 опрашивающего), объекты выгружаются задолго до вызова зарегистрированной функции выключения, поэтому вы не сможете полагаться на свои объекты в памяти для выполнения много всего
Таким образом, регистрация функции выключения - это хорошо, но задачи, которые должны выполняться с помощью функции выключения, вероятно, ограничены горсткой простых процедур выключения.
Ключ к выводу здесь - это всего лишь несколько слов мудрости для любого, кто наткнется на этот вопрос и увидит совет в первоначально принятом ответе. Не проверяйте свой выходной буфер.
Фатальные ошибки или исправимые фатальные ошибки теперь генерируют экземпляры Error
в PHP 7 или более поздней версии. Как и любые другие исключения, Error
объекты могут быть пойманы с помощью try/catch
блок.
Пример:
<?php
$variable = 'not an object';
try {
$variable->method(); // Throws an Error object in PHP 7 or higger.
} catch (Error $e) {
// Handle error
echo $e->getMessage(); // Call to a member function method() on string
}
Или вы можете использовать Throwable
интерфейс для перехвата всех исключений.
Пример:
<?php
try {
undefinedFunctionCall();
} catch (Throwable $e) {
// Handle error
echo $e->getMessage(); // Call to undefined function undefinedFunctionCall()
}
Для получения дополнительной информации: http://php.net/manual/en/language.errors.php7.php
Что ж, кажется возможным поймать фатальные ошибки другим способом:)
ob_start('fatal_error_handler');
function fatal_error_handler($buffer){
$error=error_get_last();
if($error['type'] == 1){
// type, message, file, line
$newBuffer='<html><header><title>Fatal Error </title></header>
<style>
.error_content{
background: ghostwhite;
vertical-align: middle;
margin:0 auto;
padding:10px;
width:50%;
}
.error_content label{color: red;font-family: Georgia;font-size: 16pt;font-style: italic;}
.error_content ul li{ background: none repeat scroll 0 0 FloralWhite;
border: 1px solid AliceBlue;
display: block;
font-family: monospace;
padding: 2%;
text-align: left;
}
</style>
<body style="text-align: center;">
<div class="error_content">
<label >Fatal Error </label>
<ul>
<li><b>Line</b> '.$error['line'].'</li>
<li><b>Message</b> '.$error['message'].'</li>
<li><b>File</b> '.$error['file'].'</li>
</ul>
<a href="javascript:history.back()"> Back </a>
</div>
</body></html>';
return $newBuffer;
}
return $buffer;
}
Вы не можете поймать / обработать фатальные ошибки, но вы можете регистрировать / сообщать о них. Для быстрой отладки я изменил один ответ на этот простой код
function __fatalHandler()
{
$error = error_get_last();
//check if it's a core/fatal error, otherwise it's a normal shutdown
if ($error !== NULL && in_array($error['type'], array(E_ERROR, E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, E_COMPILE_WARNING,E_RECOVERABLE_ERROR))) {
echo "<pre>fatal error:\n";
print_r($error);
echo "</pre>";
die;
}
}
register_shutdown_function('__fatalHandler');
Я разработал способ отловить все типы ошибок в PHP (почти все)! Я не уверен насчет E_CORE_ERROR (думаю, не будет работать только для этой ошибки)! Но для других фатальных ошибок (E_ERROR, E_PARSE, E_COMPILE...) отлично работает, используя только одну функцию обработчика ошибок! Там идет мое решение:
Поместите следующий код в ваш основной файл (index.php):
<?php
define('E_FATAL', E_ERROR | E_USER_ERROR | E_PARSE | E_CORE_ERROR |
E_COMPILE_ERROR | E_RECOVERABLE_ERROR);
define('ENV', 'dev');
//Custom error handling vars
define('DISPLAY_ERRORS', TRUE);
define('ERROR_REPORTING', E_ALL | E_STRICT);
define('LOG_ERRORS', TRUE);
register_shutdown_function('shut');
set_error_handler('handler');
//Function to catch no user error handler function errors...
function shut(){
$error = error_get_last();
if($error && ($error['type'] & E_FATAL)){
handler($error['type'], $error['message'], $error['file'], $error['line']);
}
}
function handler( $errno, $errstr, $errfile, $errline ) {
switch ($errno){
case E_ERROR: // 1 //
$typestr = 'E_ERROR'; break;
case E_WARNING: // 2 //
$typestr = 'E_WARNING'; break;
case E_PARSE: // 4 //
$typestr = 'E_PARSE'; break;
case E_NOTICE: // 8 //
$typestr = 'E_NOTICE'; break;
case E_CORE_ERROR: // 16 //
$typestr = 'E_CORE_ERROR'; break;
case E_CORE_WARNING: // 32 //
$typestr = 'E_CORE_WARNING'; break;
case E_COMPILE_ERROR: // 64 //
$typestr = 'E_COMPILE_ERROR'; break;
case E_CORE_WARNING: // 128 //
$typestr = 'E_COMPILE_WARNING'; break;
case E_USER_ERROR: // 256 //
$typestr = 'E_USER_ERROR'; break;
case E_USER_WARNING: // 512 //
$typestr = 'E_USER_WARNING'; break;
case E_USER_NOTICE: // 1024 //
$typestr = 'E_USER_NOTICE'; break;
case E_STRICT: // 2048 //
$typestr = 'E_STRICT'; break;
case E_RECOVERABLE_ERROR: // 4096 //
$typestr = 'E_RECOVERABLE_ERROR'; break;
case E_DEPRECATED: // 8192 //
$typestr = 'E_DEPRECATED'; break;
case E_USER_DEPRECATED: // 16384 //
$typestr = 'E_USER_DEPRECATED'; break;
}
$message = '<b>'.$typestr.': </b>'.$errstr.' in <b>'.$errfile.'</b> on line <b>'.$errline.'</b><br/>';
if(($errno & E_FATAL) && ENV === 'production'){
header('Location: 500.html');
header('Status: 500 Internal Server Error');
}
if(!($errno & ERROR_REPORTING))
return;
if(DISPLAY_ERRORS)
printf('%s', $message);
//Logging error on php file error log...
if(LOG_ERRORS)
error_log(strip_tags($message), 0);
}
ob_start();
@include 'content.php';
ob_end_flush();
?>
Я надеюсь, что это помогает многим людям! Я слишком долго искал это решение и не нашел! Тогда я разработал один!
Вы не можете выбросить исключение внутри зарегистрированной функции отключения следующим образом:
<?php
function shutdown() {
if (($error = error_get_last())) {
ob_clean();
throw new Exception("fatal error");
}
}
try {
$x = null;
$x->method()
} catch(Exception $e) {
# this won't work
}
?>
Но вы можете перехватить и перенаправить запрос на другую страницу.
<?php
function shutdown() {
if (($error = error_get_last())) {
ob_clean();
# raport the event, send email etc.
header("Location: http://localhost/error-capture");
# from /error-capture, you can use another redirect, to e.g. home page
}
}
register_shutdown_function('shutdown');
$x = null;
$x->method()
?>
Если вы используете php >= 5.1.0 Просто сделайте что-то подобное с классом ErrorException:
<?php
//define an error handler
function exception_error_handler($errno, $errstr, $errfile, $errline ) {
throw new ErrorException($errstr, $errno, 0, $errfile, $errline);
}
//set ur error handle
set_error_handler("exception_error_handler");
/* Trigger exception */
try
{
//try to do something like finding the end of the internet
}
catch(ErrorException $e)
{
//anything you want to do with $e
}
?>
Просто хороший трюк, чтобы получить текущий метод error_handler =)
<?php
register_shutdown_function('__fatalHandler');
function __fatalHandler()
{
$error = error_get_last();
//check if it's a core/fatal error, otherwise it's a normal shutdown
if($error !== NULL && $error['type'] === E_ERROR) {
//Bit hackish, but the set_exception_handler will return the old handler
function fakeHandler() { }
$handler = set_exception_handler('fakeHandler');
restore_exception_handler();
if($handler !== null) {
call_user_func($handler, new ErrorException($error['message'], $error['type'], 0, $error['file'], $error['line']));
}
exit;
}
}
?>
Также я не хочу отметить, что если вы позвоните
<?php
ini_set('display_errors', false);
?>
Php перестает отображать ошибку, в противном случае текст ошибки будет отправлен клиенту до вашего обработчика ошибок
Хорошее решение найдено в Zend Framework 2:
/**
* ErrorHandler that can be used to catch internal PHP errors
* and convert to an ErrorException instance.
*/
abstract class ErrorHandler
{
/**
* Active stack
*
* @var array
*/
protected static $stack = array();
/**
* Check if this error handler is active
*
* @return bool
*/
public static function started()
{
return (bool) static::getNestedLevel();
}
/**
* Get the current nested level
*
* @return int
*/
public static function getNestedLevel()
{
return count(static::$stack);
}
/**
* Starting the error handler
*
* @param int $errorLevel
*/
public static function start($errorLevel = \E_WARNING)
{
if (!static::$stack) {
set_error_handler(array(get_called_class(), 'addError'), $errorLevel);
}
static::$stack[] = null;
}
/**
* Stopping the error handler
*
* @param bool $throw Throw the ErrorException if any
* @return null|ErrorException
* @throws ErrorException If an error has been catched and $throw is true
*/
public static function stop($throw = false)
{
$errorException = null;
if (static::$stack) {
$errorException = array_pop(static::$stack);
if (!static::$stack) {
restore_error_handler();
}
if ($errorException && $throw) {
throw $errorException;
}
}
return $errorException;
}
/**
* Stop all active handler
*
* @return void
*/
public static function clean()
{
if (static::$stack) {
restore_error_handler();
}
static::$stack = array();
}
/**
* Add an error to the stack
*
* @param int $errno
* @param string $errstr
* @param string $errfile
* @param int $errline
* @return void
*/
public static function addError($errno, $errstr = '', $errfile = '', $errline = 0)
{
$stack = & static::$stack[count(static::$stack) - 1];
$stack = new ErrorException($errstr, 0, $errno, $errfile, $errline, $stack);
}
}
Этот класс позволяет вам начать конкретный ErrorHandler
иногда, если вам это нужно. И тогда вы также можете остановить обработчик.
Используйте этот класс, например, так:
ErrorHandler::start(E_WARNING);
$return = call_function_raises_E_WARNING();
if ($innerException = ErrorHandler::stop()) {
throw new Exception('Special Exception Text', 0, $innerException);
}
// or
ErrorHandler::stop(true); // directly throws an Exception;
Ссылка на полный код класса:
https://github.com/zendframework/zf2/blob/master/library/Zend/Stdlib/ErrorHandler.php
Возможно, лучшее решение - это решение от Monolog:
Ссылка на полный код класса:https://github.com/Seldaek/monolog/blob/master/src/Monolog/ErrorHandler.php
Он также может обрабатывать FATAL_ERRORS, используя register_shutdown_function
функция. Согласно этому классу FATAL_ERROR является одним из следующих array(E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR)
,
class ErrorHandler
{
// [...]
public function registerExceptionHandler($level = null, $callPrevious = true)
{
$prev = set_exception_handler(array($this, 'handleException'));
$this->uncaughtExceptionLevel = $level;
if ($callPrevious && $prev) {
$this->previousExceptionHandler = $prev;
}
}
public function registerErrorHandler(array $levelMap = array(), $callPrevious = true, $errorTypes = -1)
{
$prev = set_error_handler(array($this, 'handleError'), $errorTypes);
$this->errorLevelMap = array_replace($this->defaultErrorLevelMap(), $levelMap);
if ($callPrevious) {
$this->previousErrorHandler = $prev ?: true;
}
}
public function registerFatalHandler($level = null, $reservedMemorySize = 20)
{
register_shutdown_function(array($this, 'handleFatalError'));
$this->reservedMemory = str_repeat(' ', 1024 * $reservedMemorySize);
$this->fatalLevel = $level;
}
// [...]
}
Мне нужно обработать фатальные ошибки для производства, чтобы вместо этого показать статический стиль 503 Сервис недоступен вывод HTML. Это, безусловно, разумный подход к "выявлению фатальных ошибок". Вот что я сделал:
У меня есть пользовательская функция обработки ошибок "error_handler", которая будет отображать мою HTML-страницу "503 service unavailable" на любом E_ERROR, E_USER_ERROR и т. Д. Теперь она будет вызываться для функции shutdown, улавливающей мою фатальную ошибку.
function fatal_error_handler() {
if (@is_array($e = @error_get_last())) {
$code = isset($e['type']) ? $e['type'] : 0;
$msg = isset($e['message']) ? $e['message'] : '';
$file = isset($e['file']) ? $e['file'] : '';
$line = isset($e['line']) ? $e['line'] : '';
if ($code>0) error_handler($code,$msg,$file,$line);
}
}
set_error_handler("error_handler");
register_shutdown_function('fatal_error_handler');
в моей пользовательской функции error_handler, если ошибка E_ERROR или E_USER_ERROR и т. д., я также вызываю @ob_end_clean(); очистить буфер, удалив таким образом PHP сообщение "фатальная ошибка".
Обратите внимание на строгую проверку isset() и функции @ silencing, поскольку мы не хотим, чтобы наши скрипты error_handler генерировали какие-либо ошибки.
Все еще соглашаясь с keparo, обнаружение фатальных ошибок лишает цели "ФАТАЛЬНОЙ ошибки", поэтому она не предназначена для дальнейшей обработки. Не запускайте никакие функции mail() в этом процессе выключения, так как вы обязательно создадите резервную копию почтового сервера или вашего почтового ящика. Вместо этого запишите эти случаи в файл и запланируйте cron, чтобы найти эти файлы error.log и отправьте их по почте администраторам.
PHP имеет ловимые фатальные ошибки. Они определены как E_RECOVERABLE_ERROR. Руководство по PHP описывает E_RECOVERABLE_ERROR как:
Ловимая фатальная ошибка. Это указывает на то, что, возможно, произошла опасная ошибка, но двигатель не работал нестабильно. Если ошибка не обнаружена определяемым пользователем дескриптором (см. Также set_error_handler()), приложение прерывается, так как оно было E_ERROR.
Вы можете "поймать" эти "фатальные" ошибки, используя set_error_handler() и проверяя E_RECOVERABLE_ERROR. Я считаю полезным генерировать исключение при обнаружении этой ошибки, тогда вы можете использовать try/catch.
Этот вопрос и ответ дают полезный пример: как я могу поймать "поддающуюся фатальной ошибке" подсказку типа PHP?
Ошибки E_ERROR, однако, могут быть обработаны, но не могут быть восстановлены, поскольку движок находится в нестабильном состоянии.
На самом деле, нет. Так называются фатальные ошибки, потому что они фатальны. Вы не можете оправиться от них.
Поскольку большинство ответов здесь неоправданно многословны, вот моя не уродливая версия ответа с наибольшим количеством голосов:
function errorHandler($errno, $errstr, $errfile = '', $errline = 0, $errcontext = array()) {
//Do stuff: mail, log, etc
}
function fatalHandler() {
$error = error_get_last();
if($error) errorHandler($error["type"], $error["message"], $error["file"], $error["line"]);
}
set_error_handler("errorHandler")
register_shutdown_function("fatalHandler");
Существуют определенные обстоятельства, в которых должны быть обнаружены даже фатальные ошибки (вам, возможно, придется сделать некоторую очистку, прежде чем грациозно выйти и просто не умереть...). Я реализовал хук pre_system в моих приложениях codeigniter, чтобы я мог получать фатальные ошибки по электронной почте, и это помогло мне найти ошибки, о которых не сообщалось (или о которых сообщалось после того, как они были исправлены, поскольку я уже знал о них:)). Почтовый ящик проверяет, не сообщалось ли об ошибке, чтобы она не доставляла вам спам с известными ошибками несколько раз.
class PHPFatalError {
public function setHandler() {
register_shutdown_function('handleShutdown');
}
}
function handleShutdown() {
if (($error = error_get_last())) {
ob_start();
echo "<pre>";
var_dump($error);
echo "</pre>";
$message = ob_get_clean();
sendEmail($message);
ob_start();
echo '{"status":"error","message":"Internal application error!"}';
ob_flush();
exit();
}
}
Я разработал эту функцию, чтобы сделать возможным "песочницу" кода, который может привести к фатальной ошибке. Поскольку исключения выбрасываются из закрытия register_shutdown_function
не выводятся из стека вызовов до фатальной ошибки, я вынужден выйти после этой функции, чтобы обеспечить единообразный способ ее использования.
function superTryCatchFinallyAndExit( Closure $try, Closure $catch = NULL, Closure $finally )
{
$finished = FALSE;
register_shutdown_function( function() use ( &$finished, $catch, $finally ) {
if( ! $finished ) {
$finished = TRUE;
print "EXPLODE!".PHP_EOL;
if( $catch ) {
superTryCatchFinallyAndExit( function() use ( $catch ) {
$catch( new Exception( "Fatal Error!!!" ) );
}, NULL, $finally );
} else {
$finally();
}
}
} );
try {
$try();
} catch( Exception $e ) {
if( $catch ) {
try {
$catch( $e );
} catch( Exception $e ) {}
}
}
$finished = TRUE;
$finally();
exit();
}
Я написал Q&A в стиле Wiki с полным решением для обнаружения всех ошибок в PHP; которые можно посмотреть / почерпнуть / украсть / критиковать здесь.
Решение включает в себя 5 методов, которые обертывают все ошибки, которые может генерировать PHP, которые в конечном итоге передают указанные ошибки в типизированный объект ErrorHandler.
Надеюсь, что некоторые люди извлекут из этого пользу. Даже если вы не украдете это напрямую, я уверен, что решение является, по крайней мере, хорошим примером того, как обрабатывать ошибки в PHP - во всех отношениях.
@Lucas Batistussi получил очки за креативность - я думаю, что я мог бы поделиться своим решением и стрелять по некоторым подобным пунктам...
Что касается PHP 7.4.13, мой опыт показывает, что все возможные ошибки и исключения в программе могут быть обнаружены только двумя функциями обратного вызова:
set_error_handler("ErrorCB");
set_exception_handler("ExceptCB");
ErrorCB просто сообщает свои аргументы любым желаемым способом и вызывает Exit ().
ExceptCB вызывает методы "get" для своего аргумента исключения и выполняет некоторую логику, чтобы определить, где находятся файл, строка и функция (спросите меня, хотите ли вы подробностей), и сообщает информацию любым желаемым способом и возвращает.
Единственная необходимость в try/catch - это если вам нужно подавить ошибки для определенного кода, когда @ не работает. Использование try/catch для "основной функции" без установки обработчиков не дает результата, поскольку он не улавливает все ошибки.
Если кто-то найдет код, который генерирует ошибку, которую этот подход не улавливает, сообщите мне, и я отредактирую этот ответ. Одна ошибка, которую этот подход не может перехватить, - это одиночный символ {в конце программы PHP; это генерирует ошибку синтаксического анализа, для которой может потребоваться register_shutdown_function().
Обратите внимание, что все, что меня волнует, это сообщение об ошибках и выход из программы; Мне не нужно восстанавливаться после ошибок - это действительно был бы гораздо более сложный вопрос.