Jak wyłapać błąd krytyczny PHP (`E_ERROR`)?


557

Mogę użyć, set_error_handler()aby wyłapać większość błędów PHP, ale to nie działa na fatalne (E_ERROR błędów ), takich jak wywołanie funkcji, która nie istnieje. Czy istnieje inny sposób na wyłapanie tych błędów?

Próbuję wywołać mail()wszystkie błędy i korzystam z PHP 5.2.3.


Napisałem pytania i odpowiedzi w stylu wiki z kompletnym rozwiązaniem do wychwytywania wszystkich błędów w PHP; które można obejrzeć / zebrać / skradzić / skrytykować tutaj na Stack Overflow . Rozwiązanie obejmuje pięć metod, które owijają wszystkie błędy, które PHP może wygenerować, które ostatecznie przekażą wspomniane błędy do obiektu o typie „ErrorHandler”.
DigitalJedi805,



Odpowiedzi:


635

Loguj krytyczne błędy używając register_shutdown_function, który wymaga 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));
    }
}

Będziesz musiał zdefiniować funkcje error_maili format_error. Na przykład:

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

Użyj Swift Mailer, aby napisać error_mailfunkcję.

Zobacz też:


113
+1 To jest prawdziwa poprawna odpowiedź. Nie wiem, dlaczego ludzie odkładają słuchawkę na temat „nie można wyzdrowieć po fatalnych błędach” - pytanie nie mówiło nic o wyzdrowieniu.
David Harkness,

21
Dzięki, dobrze. Odzyskiwanie po błędach krytycznych (na przykład limity pamięci) nie jest czymś, co chciałbym zrobić, ale sprawienie, aby błędy te były wykrywalne (bez przesyłania przez klienta zgłoszenia do pomocy technicznej) robi różnicę.
Ilija

2
Korzystanie z podstawowej poczty:mail("myname@myemail.com", "My Site: FATAL ERROR", "Details: " . $errno . ' ' . $errstr . ' ' . $errfile . ' ' . $errline);
Eric Muyser,

4
@ScottNicol Slava V jest poprawny, ponieważ funkcja zamykania jest wywoływana za każdym razem, gdy skrypt kończy działanie. Sposób, w jaki kod został napisany teraz, przy każdym załadowaniu strony zostanie wysłany e-mail.
Nate

2
Uwaga: to nie jest 100% poprawna odpowiedź. Każde miejsce, które używa symbolu @ do ignorowania błędów, nadal ustawia ostatni błąd (abyś mógł obsługiwać błędy). Zatem skrypt kończy się bez problemu, ale funkcja register_shutdown_ nadal uważa, że ​​wystąpił błąd. Jednak dopiero od PHP 7 mieli one funkcję error_clear_last ().
Rahly,

150

Właśnie wymyśliłem to rozwiązanie (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');

Różne typy błędów są zdefiniowane w predefiniowanych stałych .


25
To rozwiązanie robi dla mnie znacznie więcej niż najwyżej oceniana odpowiedź. Najwyżej oceniana odpowiedź wyśle ​​Ci wiadomość e-mail za każdym razem, gdy skrypt zostanie uruchomiony, nawet jeśli nie wystąpi błąd. Ten ściśle popełniany jest błąd krytyczny.
kmoney12

@periklis, jeśli ostatni błąd został już obsłużony, error_get_last nadal zwróci, prawda?
Pacerier

@Pacerier Nie jestem pewien, co masz na myśli przez „obsłużony”, ponieważ błędy nie są wyjątkami, ale przypuszczam, że odpowiedź brzmi „tak”
periklis

3
@Pacerier Rozumiem, to interesujące pytanie. Spójrz na php.net/error_get_last , jeden z komentarzy wspomina, że ​​„ If an error handler (see set_error_handler ) successfully handles an error then that error will not be reported by this function.
periklis

1
Być może jest to oczywiste, że wywołanie register_shutdown_function()musi być wcześniejsze niż jakikolwiek błąd krytyczny. use_1T_memory(); /* memory exhausted error here! */ register_shutdown_function('shutDownFunction');nie będzie działać zgodnie z oczekiwaniami.
Nobu,

117

PHP nie zapewnia konwencjonalnych sposobów wychwytywania i odzyskiwania po błędach krytycznych. Wynika to z faktu, że przetwarzanie zwykle nie powinno być odzyskiwane po wystąpieniu błędu krytycznego. Ciąg pasujący do bufora wyjściowego (jak sugeruje oryginalny post technika opisana na PHP.net) jest zdecydowanie odradzany. To po prostu niewiarygodne.

Problematyczne jest również wywoływanie funkcji mail () z poziomu metody obsługi błędów. Gdybyś miał dużo błędów, twój serwer pocztowy byłby załadowany pracą i mógłbyś znaleźć się z gnarly skrzynką odbiorczą. Aby tego uniknąć, możesz rozważyć uruchomienie crona, aby okresowo skanować dzienniki błędów i odpowiednio wysyłać powiadomienia. Możesz także zajrzeć do oprogramowania do monitorowania systemu, takiego jak Nagios .


Aby porozmawiać o rejestracji funkcji zamykania:

To prawda, że ​​możesz zarejestrować funkcję zamykania, a to dobra odpowiedź.

Chodzi o to, że zazwyczaj nie powinniśmy próbować przywracać sprawności po błędach krytycznych, zwłaszcza nie używając wyrażenia regularnego w buforze wyjściowym. Odpowiedziałem na zaakceptowaną odpowiedź , która łączyła się z sugestią na php.net, która została zmieniona lub usunięta.

Ta sugestia polegała na zastosowaniu wyrażenia regularnego względem bufora wyjściowego podczas obsługi wyjątków, aw przypadku błędu krytycznego (wykrytego przez dopasowanie do dowolnego skonfigurowanego tekstu błędu, którego można się spodziewać), spróbuj wykonać pewnego rodzaju odzyskiwanie lub dalsze przetwarzanie. To nie byłaby zalecana praktyka (uważam, że dlatego też nie mogę znaleźć oryginalnej sugestii. Albo przeoczam ją, albo społeczność php zestrzeliła ją).

Warto zauważyć, że nowsze wersje PHP (około 5.1) wydają się wcześniej wywoływać funkcję zamykania, zanim zostanie wywołane wywołanie zwrotne buforowania wyjścia. W wersji 5 i wcześniejszych ta kolejność była odwrotna (po wywołaniu zwrotnym buforowania danych wyjściowych nastąpiła funkcja zamykania). Ponadto, ponieważ około 5.0.5 (czyli znacznie wcześniej niż wersja pytającego 5.2.3), obiekty są rozładowywane na długo przed wywołaniem zarejestrowanej funkcji zamykania, więc nie będzie można polegać na obiektach w pamięci dużo czegokolwiek.

Rejestracja funkcji zamykania jest więc w porządku, ale zadania, które powinna wykonać funkcja zamykania, prawdopodobnie ograniczają się do kilku delikatnych procedur zamykania.

Kluczową sprawą tutaj są tylko słowa mądrości dla każdego, kto natknie się na to pytanie i zapozna się z radą w pierwotnie przyjętej odpowiedzi. Nie regexuj bufora wyjściowego.


25
Pfff, pamiętam te 650 000+ e-maili, które dostałem następnego dnia rano. Od tego czasu mój ErrorHandler jest ograniczony do 100 e-maili na serwer.
Bob Fanger,

14
To nieprawda. Możesz uchwycić krytyczne błędy dzięki register_shutdown_function.
hipertracker

56
Istnieją przypadki użycia, które pozwalają wykryć krytyczne błędy. Na przykład pakiety testowe nie powinny po prostu przestać, gdy jeden się nie powiedzie, powinny zgłosić błąd krytyczny i przejść do następnego testu. PHP popełnia po prostu zbyt wiele rzeczy „śmiertelnych” błędów.
Czad,

24
Tak, mówiąc, że „nie należy ich łapać”, jest bardzo krótkowzroczny. W systemie produkcyjnym musisz wiedzieć, kiedy coś się nie powiedzie (skonfiguruj e-maile lub zaloguj się w bazie danych - domyślna obsługa błędów php nie jest zbyt skomplikowana).
BT

8
Chcę szybko skomentować to, co wszyscy mówicie o „Błędy muszą zostać wychwycone, abyśmy mogli je naprawić” ... Dyrektywy Ini ini log_errors i error_log.
Kelly Elton,

37

Wydaje się, że możliwe jest złapanie krytycznych błędów w inny sposób :)

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

3
Dałbym 10 głosów pozytywnych, gdybym mógł. Działa idealnie dla mnie w przypadku tych dziwnych błędów, które czasami występują, gdy strona się bombarduje i nic nie jest rejestrowane. Nie używałbym kodu produkcyjnego na żywo, ale świetnie jest dodać do strony, gdy potrzebna jest szybka odpowiedź na to, co nie działa. Dziękuję Ci!
Night Owl

Jedno z najlepszych rozwiązań, jakie znalazłem w Internecie. Działa jak urok.
Odbij

W jaki sposób? Wyjaśnienie byłoby właściwe, zwłaszcza jeśli jest to jedno z najlepszych rozwiązań w Internecie (może być jeszcze lepsze).
Peter Mortensen

Czy potrzebna jest np. Cała zawartość CSS? Czy nie można tego sprowadzić do niezbędności? Odpowiedz, edytując odpowiedź, a nie tutaj w komentarzach (odpowiednio).
Peter Mortensen

@PeterMortensen Nie twierdzę, że jest najlepszy. Jest to również moje osobiste rozwiązanie problemu, są też inne lepsze opcje o wiele bardziej profesjonalne. Jak sugeruje ktoś, nie nadaje się do produkcji. Css jest tam bcz Właśnie
wkleiłem

36

Błędy krytyczne lub możliwe do odzyskania błędy krytyczne generują teraz instancje Errorw PHP 7 lub wyższych wersjach . Jak wszystkie inne wyjątki, Errorobiekty można złapać za pomocą try/catchbloku.

Przykład:

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

Lub możesz użyć Throwableinterfejsu, aby złapać wszystkie wyjątki.

Przykład:

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

https://3v4l.org/Br0MG

Aby uzyskać więcej informacji: http://php.net/manual/en/language.errors.php7.php


2
Wszelkie pomysły na temat tego, jak to wykorzystać, aby złapać błąd, na przykład Fatal error: Trait 'FailedTrait' not found inpodczas używania ReflectionClass?
TCB13,

1
@ TCB13 spróbuj owinąć wewnętrzną zawartość try w pliku, a include "filename.php"zamiast tego w trybloku, a następnie Throwableprzynajmniej złap blok ParseError.
Niloct,

24

Opracowałem sposób na wyłapanie wszystkich typów błędów w PHP (prawie wszystkie)! Nie mam pewności co do E_CORE_ERROR (myślę, że nie będzie działać tylko w przypadku tego błędu)! Ale w przypadku innych błędów krytycznych (E_ERROR, E_PARSE, E_COMPILE ...) działa dobrze przy użyciu tylko jednej funkcji obsługi błędów! Oto moje rozwiązanie:

Umieść następujący kod w głównym pliku (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();
?>

2
Co robi wiersz @include „content.php”?
Marco,

22

Nie możesz wychwycić / obsłużyć krytycznych błędów, ale możesz je zalogować / zgłosić. W celu szybkiego debugowania zmodyfikowałem jedną odpowiedź na ten prosty kod

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

ale dokąd poszedłby ten kod?
TKoL

@TKoL pierwsza linia. Zasadniczo wprowadź plik swojego skryptu / programu, więc wykonuje się najpierw, jeśli nie jest to możliwe, umieść go we wspólnym pliku
zainengineer

17

Nie można wrzucić wyjątku do zarejestrowanej funkcji zamykania w następujący sposób:

<?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
    }
?>

Ale możesz przechwytywać i przekierowywać żądania na inną stronę.

<?php
    function shutdown() {
        if (($error = error_get_last())) {
           ob_clean();
           # Report the event, send email, etc.
           header("Location: http://localhost/error-capture");
           # From /error-capture. You can use another
           # redirect, to e.g. the home page
        }
    }
    register_shutdown_function('shutdown');

    $x = null;
    $x->method()
?>

11

Jeśli używasz PHP> = 5.1.0 Po prostu zrób coś takiego z klasą ErrorException:

<?php
    // Define an error handler
    function exception_error_handler($errno, $errstr, $errfile, $errline ) {
        throw new ErrorException($errstr, $errno, 0, $errfile, $errline);
    }

    // Set your error handler
    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

Ładne rozwiązanie znalezione w 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);
    }
}

Ta klasa pozwala ErrorHandlerczasem rozpocząć konkretną, jeśli jest to potrzebne. Następnie możesz zatrzymać Handlera.

Użyj tej klasy, np. W ten sposób:

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;

Link do pełnego kodu klasy:
https://github.com/zendframework/zf2/blob/master/library/Zend/Stdlib/ErrorHandler.php


Być może lepszym rozwiązaniem jest to od Monolog :

Link do pełnego kodu klasy:
https://github.com/Seldaek/monolog/blob/master/src/Monolog/ErrorHandler.php

Może również obsługiwać FATAL_ERRORS za pomocą register_shutdown_functionfunkcji. Według tej klasy FATAL_ERROR jest jedną z poniższych 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;
    }

    // [...]
}

9

Muszę obsłużyć krytyczne błędy związane z produkcją, aby zamiast tego wyświetlić statyczny wynik HTML 503 Usługa niedostępna . Jest to z pewnością rozsądne podejście do „łapania błędów krytycznych”. Oto co zrobiłem:

Mam niestandardową funkcję obsługi błędów „moduł obsługi błędów”, która wyświetla moją stronę HTML „niedostępna usługa 503” na dowolnym elemencie E_ERROR, E_USER_ERROR itp. Zostanie ona teraz wywołana w funkcji zamykania, wychwytując mój błąd krytyczny,

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

w mojej niestandardowej funkcji modułu obsługi błędów, jeśli błąd to E_ERROR, E_USER_ERROR itp. Również wywołuję @ob_end_clean(); aby opróżnić bufor, usuwając w ten sposób komunikat „fatal error” PHP.

Zwróć uwagę na funkcje ścisłego sprawdzania i @wyciszania funkcji isset (), ponieważ nie chcemy, aby nasze skrypty obsługi błędów generowały błędy.

Wciąż zgadzając się z Keparo, łapanie błędów krytycznych nie pozwala na osiągnięcie celu „FATAL error”, więc tak naprawdę nie jest przeznaczone do dalszego przetwarzania. Nie uruchamiaj żadnych funkcji mail () w tym procesie zamykania, ponieważ z pewnością utworzysz kopię zapasową serwera pocztowego lub skrzynki odbiorczej. Raczej zalogować tych zdarzeń do pliku i zaplanować cron zadanie, aby znaleźć tych error.log plików i wysłać je do administratorów.


7

PHP ma krytyczne błędy krytyczne. Są one zdefiniowane jako E_RECOVERABLE_ERROR. Podręcznik PHP opisuje E_RECOVERABLE_ERROR jako:

Łapalny błąd krytyczny. Wskazuje, że wystąpił prawdopodobnie niebezpieczny błąd, ale nie pozostawił silnika w niestabilnym stanie. Jeśli błąd nie zostanie przechwycony przez uchwyt zdefiniowany przez użytkownika (patrz także set_error_handler () ), aplikacja przerywa, ponieważ była to E_ERROR.

Możesz „złapać” te „krytyczne” błędy, używając set_error_handler () i sprawdzając E_RECOVERABLE_ERROR. Uważam, że przydatne jest zgłoszenie wyjątku, gdy ten błąd zostanie wykryty, wtedy możesz użyć try / catch.

To pytanie i odpowiedź stanowi przydatny przykład: Jak mogę złapać „krytyczny błąd krytyczny” na podpowiedzi typu PHP?

Błędy E_ERROR można jednak obsłużyć, ale nie można ich odzyskać, ponieważ silnik jest w niestabilnym stanie.


6

Oto po prostu fajna sztuczka, aby uzyskać bieżącą metodę modułu obsługi błędów =)

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

            // It is a 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;
        }
    }
?>

Również chcę zauważyć, że jeśli zadzwonisz

<?php
    ini_set('display_errors', false);
?>

PHP przestaje wyświetlać błąd. W przeciwnym razie tekst błędu zostanie wysłany do klienta przed obsługą błędów.


1
Przegłosowano to ze względu na linię ini_set ('display_errors', false);
Sahib Khan

Jeśli z jakiegoś powodu ten bit jest włączony, nadal będą wyświetlać błędy php, nawet jeśli potraktujesz to inaczej
Sahib Khan

5

Ponieważ większość odpowiedzi tutaj jest niepotrzebnie pełna, oto moja nieprzyzwoita wersja najlepiej głosowanej odpowiedzi:

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

4

Nie całkiem. Błędy krytyczne nazywane są tak, ponieważ są śmiertelne. Nie możesz się od nich odzyskać.


12
łapanie i odzyskiwanie to dwie bardzo różne rzeczy.
Simon Forsberg

3

Opracowałem tę funkcję, aby umożliwić „piaskownicę” kodu, który może spowodować błąd krytyczny. Ponieważ wyjątki wyrzucone z zamknięcia register_shutdown_functionnie są emitowane ze stosu wywołań błędów przed śmiercią, jestem zmuszony wyjść z tej funkcji, aby zapewnić jednolity sposób korzystania z niej.

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

3

Są pewne okoliczności, w których należy wychwycić nawet fatalne błędy (być może trzeba będzie trochę posprzątać przed wyjściem z gracją i nie tylko umrzeć ...).

Zaimplementowałem hak pre_system w moim CodeIgniter aplikacjach , dzięki czemu mogę otrzymywać krytyczne błędy za pośrednictwem e-maili, a to pomogło mi znaleźć błędy, które nie zostały zgłoszone (lub zostały zgłoszone po ich naprawieniu, jak już o nich wiedziałem :)).

Sendemail sprawdza, czy błąd został już zgłoszony, aby nie wysyłać spamu znanymi błędami wiele razy.

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

Co to jest „Sendemail” ? Czy masz na myśli Sendmaila (odpowiedz, edytując odpowiedź , a nie tutaj w komentarzach)?
Peter Mortensen
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.