如何捕获 PHP 致命(`E_ERROR`)错误?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/277224/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-24 22:11:33  来源:igfitidea点击:

How do I catch a PHP fatal (`E_ERROR`) error?

phpfatal-error

提问by too much php

I can use set_error_handler()to catch most PHP errors, but it doesn't work for fatal (E_ERROR) errors, such as calling a function that doesn't exist. Is there another way to catch these errors?

我可以使用它set_error_handler()来捕获大多数 PHP 错误,但它不适用于致命 ( E_ERROR) 错误,例如调用不存在的函数。还有其他方法可以捕获这些错误吗?

I am trying to call mail()for all errors and am running PHP 5.2.3.

我正在尝试调用mail()所有错误并运行 PHP 5.2.3。

回答by user259973

Log fatal errors using the register_shutdown_function, which requires PHP 5.2+:

使用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));
    }
}

You will have to define the error_mailand format_errorfunctions. For example:

您必须定义error_mailformat_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;
}

Use Swift Mailerto write the error_mailfunction.

使用Swift Mailer编写error_mail函数。

See also:

也可以看看:

回答by periklis

I just came up with this solution (PHP 5.2.0+):

我刚刚想出了这个解决方案(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');

Different error types are defined at Predefined Constants.

Predefined Constants中定义了不同的错误类型。

回答by keparo

PHP doesn't provide conventional means for catching and recovering from fatal errors. This is because processing should not typically be recovered after a fatal error. String matching an output buffer (as suggested by the original post the technique described on PHP.net) is definitely ill-advised. It's simply unreliable.

PHP 不提供用于捕获和从致命错误中恢复的传统方法。这是因为在发生致命错误后通常不应恢复处理。匹配输出缓冲区的字符串(如 PHP.net 上描述的技术的原始帖子所建议的)绝对是不明智的。这简直是​​不可靠的。

Calling the mail() function from within an error handler method prove to be problematic, too. If you had a lot of errors, your mail server would be loaded with work, and you could find yourself with a gnarly inbox. To avoid this, you might consider running a cron to scan error logs periodically and send notifications accordingly. You might also like to look into system monitoring software, such as Nagios.

从错误处理程序方法中调用 mail() 函数也证明是有问题的。如果你有很多错误,你的邮件服务器就会充满工作,你会发现自己的收件箱很粗糙。为避免这种情况,您可以考虑运行 cron 定期扫描错误日志并相应地发送通知。您可能还想研究系统监控软件,例如Nagios



To speak to the bit about registering a shutdown function:

谈谈关于注册关闭功能的一点:

It's true that you can register a shutdown function, and that's a good answer.

确实可以注册关机功能,这是一个很好的答案。

The point here is that we typically shouldn't try to recover from fatal errors, especially not by using a regular expression against your output buffer. I was responding to the accepted answer, which linked to a suggestion on php.net which has since been changed or removed.

这里的重点是,我们通常不应该尝试从致命错误中恢复,尤其是不要对输出缓冲区使用正则表达式。我正在回应已接受的答案,该答案与 php.net 上的建议相关联,该建议已被更改或删除。

That suggestion was to use a regex against the output buffer during exception handling, and in the case of a fatal error (detected by the matching against whatever configured error text you might be expecting), try to do some sort of recovery or continued processing. That would not be a recommended practice (I believe that's why I can't find the original suggestion, too. I'm either overlooking it, or the php community shot it down).

该建议是在异常处理期间对输出缓冲区使用正则表达式,并且在发生致命错误的情况下(通过与您可能期望的任何配置错误文本进行匹配来检测),尝试进行某种恢复或继续处理。这不是推荐的做法(我相信这就是为什么我也找不到原始建议的原因。我要么忽略了它,要么 php 社区将其否决了)。

It might be worth noting that the more recent versions of PHP (around 5.1) seem to call the shutdown function earlier, before the output buffering callback is envoked. In version 5 and earlier, that order was the reverse (the output buffering callback was followed by the shutdown function). Also, since about 5.0.5 (which is much earlier than the questioner's version 5.2.3), objects are unloaded well before a registered shutdown function is called, so you won't be able to rely on your in-memory objects to do much of anything.

可能值得注意的是,较新版本的 PHP(大约 5.1)似乎在调用输出缓冲回调之前更早地调用了关闭函数。在版本 5 及更早版本中,该顺序是相反的(输出缓冲回调之后是关闭函数)。此外,由于大约 5.0.5(比提问者的版本 5.2.3 早得多),在调用注册的关闭函数之前很好地卸载了对象,因此您将无法依赖内存中的对象来执行很多东西。

So registering a shutdown function is fine, but the sort of tasks that ought to be performed by a shutdown function are probably limited to a handful of gentle shutdown procedures.

所以注册一个关闭函数是好的,但是应该由关闭函数执行的任务可能仅限于少数温和的关闭程序。

The key take-away here is just some words of wisdom for anyone who stumbles upon this question and sees the advice in the originally accepted answer. Don't regex your output buffer.

对于任何偶然发现这个问题并在最初接受的答案中看到建议的人来说,这里的关键要点只是一些智慧之词。不要正则表达式你的输出缓冲区。

回答by sakhunzai

Well, it seems possible to catch fatal errors some other way :)

好吧,似乎可以通过其他方式捕获致命错误:)

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

回答by LugiHaue

Fatal errors or recoverable fatal errors now throw instances of Errorin PHP 7 or higher versions. Like any other exceptions, Errorobjects can be caught using a try/catchblock.

致命错误或可恢复的致命错误现在ErrorPHP 7 或更高版本中抛出实例。像任何其他异常一样,Error可以使用try/catch块来捕获对象。

Example:

例子:

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

https://3v4l.org/67vbk

Or you can use Throwableinterface to catch all exceptions.

或者您可以使用Throwable接口来捕获所有异常。

Example:

例子:

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

https://3v4l.org/Br0MG

https://3v4l.org/Br0MG

For more information: http://php.net/manual/en/language.errors.php7.php

更多信息:http: //php.net/manual/en/language.errors.php7.php

回答by Lucas Batistussi

I developed a way to catch all error types in PHP (almost all)! I have no sure about E_CORE_ERROR (I think will not works only for that error)! But, for other fatal errors (E_ERROR, E_PARSE, E_COMPILE...) works fine using only one error handler function! There goes my solution:

我开发了一种方法来捕获 PHP 中的所有错误类型(几乎所有)!我不确定 E_CORE_ERROR (我认为不仅适用于该错误)!但是,对于其他致命错误(E_ERROR、E_PARSE、E_COMPILE...),仅使用一个错误处理函数就可以正常工作!这是我的解决方案:

Put this following code on your main file (index.php):

将以下代码放在您的主文件 (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();
?>

回答by zainengineer

You can't catch/handle fatal errors, but you can log/report them. For quick debugging I modified one answer to this simple code

您无法捕获/处理致命错误,但可以记录/报告它们。为了快速调试,我修改了这个简单代码的一个答案

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

回答by hipertracker

You cannot throw an exception inside a registered shutdown function like that:

您不能在注册的关闭函数中抛出异常,如下所示:

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

But you can capture and redirect request to another page.

但是您可以捕获请求并将其重定向到另一个页面。

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

回答by Cyril Tata

If you are using PHP >= 5.1.0 Just do something like this with the ErrorException class:

如果您使用的是 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 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
    }
?>

回答by algorhythm

Nice solution found in Zend Framework 2:

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

This class allows you to start the specific ErrorHandlersometimes if you need it. And then you can also stop the Handler.

ErrorHandler如果需要,这个类允许您有时启动特定的。然后你也可以停止处理程序。

Use this class e.g. like this:

像这样使用这个类:

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 to the full class code:
https://github.com/zendframework/zf2/blob/master/library/Zend/Stdlib/ErrorHandler.php

链接到完整的类代码:https:
//github.com/zendframework/zf2/blob/master/library/Zend/Stdlib/ErrorHandler.php



A maybe better solution is that one from Monolog:

一个可能更好的解决方案是Monolog中的一个:

链接到完整的类代码:


https://github.com/Seldaek/monolog/blob/master/src/Monolog/ErrorHandler.phphttps://github.com/Seldaek/monolog/blob/master/src/Monolog/ErrorHandler.php

It can also handle FATAL_ERRORS using the register_shutdown_functionfunction. According to this class a FATAL_ERROR is one of the following array(E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR).

它还可以使用该register_shutdown_function函数处理 FATAL_ERRORS 。根据此类, 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;
    }

    // [...]
}