Как поймать фатальную ошибку PHP

Я могу использовать set_error_handler(), чтобы уловить большинство ошибок PHP, но он не работает для фатальных (E_ERROR) ошибок, таких как вызов функции, которая не существует. Есть ли другой способ уловить эти ошибки?

Я пытаюсь вызвать mail() для всех ошибок и запускаю PHP 5.2.3.

Ответ 1

Логарифмические ошибки при использовании 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.

См. также:

Ответ 2

Просто придумал это решение (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

Ответ 3

PHP не предоставляет традиционных средств для обнаружения и восстановления после фатальных ошибок. Это потому, что обработка не должна быть восстановлена после фатальной ошибки. Строка, совпадающая с выходным буфером (как было предложено в оригинальном посте, описанном в PHP.net), явно не рекомендуется. Это просто ненадежно.

Вызов функции mail() из метода обработчика ошибок также оказывается проблематичным. Если бы у вас было много ошибок, ваш почтовый сервер был бы загружен работой, и вы могли бы оказаться с мрачным почтовым ящиком. Чтобы избежать этого, вы можете использовать cron для периодического сканирования журналов ошибок и соответствующей отправки уведомлений. Вам также может понравиться программное обеспечение для мониторинга системы, такое как Nagios.


Чтобы немного рассказать о регистрации функции выключения:

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

Дело в том, что мы обычно не должны пытаться восстанавливаться после фатальных ошибок, особенно не используя регулярное выражение для вашего выходного буфера. Я отвечал на принятый ответ, который связан с предложением на php.net, которое с тех пор было изменено или удалено.

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

Возможно, стоит отметить, что более поздние версии PHP (около 5.1), по-видимому, вызывают функцию завершения работы раньше, чем вызывается обратный вызов буферизации вывода. В версии 5 и более ранних версиях этот порядок был обратным (за обратным вызовом буферизации вывода следовала функция выключения). Кроме того, начиная с версии 5.0.5 (что намного раньше, чем версия 5.2.3 опрашивающего), объекты выгружаются задолго до вызова зарегистрированной функции выключения, поэтому вы не сможете полагаться на свои объекты в памяти для выполнения много всего

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

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

Ответ 4

Ну, похоже, можно поймать Fatal Errors другим способом:)

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;

}

Ответ 5

Я разработал способ поймать все типы ошибок в 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();

?>

Надеюсь, это поможет многим людям! Я искал это решение слишком долго и не нашел! Тогда я разработал один!

Ответ 6

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

function __fatalHandler()
{
    $error = error_get_last();
//check if it a core/fatal error, otherwise it 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');

Ответ 7

Вы не можете генерировать исключение внутри зарегистрированной функции выключения:

<?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()
?>

Ответ 8

Если вы используете 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
}

?>

Ответ 9

Фатальные ошибки или восстанавливаемые фатальные ошибки теперь генерируют экземпляры 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
}

https://3v4l.org/67vbk

Или вы можете использовать Throwable интерфейс для перехвата всех исключений.

Пример:

<?php
    try {
        undefinedFunctionCall();
    } catch (Throwable $e) {
        // Handle error
        echo $e->getMessage(); // Call to undefined function undefinedFunctionCall()
    }

https://3v4l.org/Br0MG

Для получения дополнительной информации: http://php.net/manual/en/language.errors.php7.php

Ответ 10

Хорошее решение найдено в 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;
    }

    // [...]
}

Ответ 11

Мне нужно обработать фатальные ошибки для производства, чтобы вместо этого отобразить статический стилизованный вывод 503 Service Unavailable HTML. Разумеется, это разумный подход к "улавливанию фатальных ошибок". Это то, что я сделал:

У меня есть специальная функция обработки ошибок "error_handler", которая отобразит мою HTML-страницу "503 недоступна для службы" на любом E_ERROR, E_USER_ERROR и т.д. Теперь это будет вызвано функцией выключения, которая ломает мою фатальную ошибку.

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() и @молчания, поскольку мы не хотим, чтобы наши скрипты error_handler генерировали любые ошибки.

По-прежнему соглашаясь с кепаро, ловить фатальные ошибки приводит к поражению цели "FATAL error", поэтому она не предназначена для дальнейшей обработки. Не запускайте никаких функций mail() в этом процессе выключения, так как вы, безусловно, создадите резервную копию почтового сервера или вашего почтового ящика. Скорее зарегистрируйте эти вхождения, чтобы записать и запланировать cron, чтобы найти эти файлы error.log и отправить их администраторам.

Ответ 12

У PHP есть увлекательные фатальные ошибки. Они определяются как E_RECOVERABLE_ERROR. Руководство по PHP описывает E_RECOVERABLE_ERROR как:

Допустимая фатальная ошибка. Он указывает, что произошла, вероятно, опасная ошибка, но не оставила двигатель в неустойчивом состоянии. Если ошибка не поймана определяемым пользователем дескриптором (см. Также set_error_handler()), приложение прерывается, поскольку это был E_ERROR.

Вы можете "поймать" эти "фатальные" ошибки, используя set_error_handler() и проверив E_RECOVERABLE_ERROR. Мне пригодится бросить исключение, когда эта ошибка поймана, тогда вы можете использовать try/catch.

Этот вопрос и ответ дают полезный пример: Как я могу поймать "уловимую фатальную ошибку" ? по подсказке типа PHP?

Ошибки E_ERROR, однако, могут обрабатываться, но не восстанавливаться, поскольку двигатель находится в неустойчивом состоянии.

Ответ 13

Просто хороший трюк, чтобы получить текущий метод error_handler =)

<?php
register_shutdown_function('__fatalHandler');
function __fatalHandler()
{
    $error      = error_get_last();

    //check if it a core/fatal error, otherwise it 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 перестает отображать ошибку, иначе текст ошибки будет отправлен клиенту до вашего обработчика ошибок

Ответ 14

Так как большинство ответов здесь являются ненужными подробностями, здесь моя не уродливая версия верхнего голоса проголосовала:

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");

Ответ 15

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

Ответ 16

Существуют определенные обстоятельства, которые должны быть уловлены даже фатальными ошибками (вам может потребоваться очистить до изящества и не просто умереть..). Я применил привязку pre_system к моим приложениям для кодирования, чтобы я мог получить свои фатальные ошибки по электронной почте, и это помогло мне найти ошибки, о которых не сообщалось (или сообщалось после того, как они были исправлены, как я уже знал о них:)). Sendemail проверяет, была ли уже зарегистрирована ошибка, чтобы не спамить вас с известными ошибками несколько раз.

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();
    }
}

Ответ 17

Я разработал эту функцию, чтобы сделать возможным "песочницу", которая может привести к фатальной ошибке. Поскольку исключения, выброшенные из замыкания 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();
}

Ответ 18

Я написал Q & A в стиле Wiki с полным решением для поиска всех ошибок в PHP; которые можно просмотреть/почерпнуть/украсть/критиковать здесь.

Решение включает в себя 5 методов, которые обертывают все ошибки PHP, которые могут генерировать, которые в конечном итоге передадут упомянутые ошибки вплоть до типизированного объекта ErrorHandler.

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

@Лукас Батистусси получил очки за творчество - я полагаю, что смогу поделиться своим решением и снимать для некоторых подобных точек...