当使用自定义关闭处理程序时,防止PHP致命错误的回溯输出


Preventing PHP fatal error traceback output when using custom shutdown handler

对于处理PHP错误,特别是抑制错误的输出,我的理解一定有些欠缺。当发生致命错误时,我希望shutdown处理程序函数能够优雅地处理它并终止脚本执行。这工作如预期。但是,我似乎无法阻止PHP输出关于致命错误的信息

我的php.ini文件包含以下指令:

error_reporting = E_ALL | E_STRICT
display_errors = Off

我将error_reporting设置为报告所有内容,并使用自定义错误处理程序抛出异常。我的期望是display_errors = Off将阻止任何错误消息的显示。

无论如何,当发生致命错误时,会绕过自定义错误处理程序(因为脚本执行会立即停止),并执行shutdown处理程序。

现在,进入我的简化代码:

error_reporting(E_ALL | E_STRICT);
ini_set('display_errors', 'Off');
function shutdown_handler()
{
  $err = error_get_last();
  $fatal = array(E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR);
  if ($err && in_array($err['type'], $fatal)) {
    echo "'n'ntest fatal error output'n'n";
  }
  exit();
}
register_shutdown_function('shutdown_handler');

为了测试它,我生成一个"允许的内存大小耗尽"致命错误,如下所示:

// max out available memory
$data = '';
while(true) {
  $data .= str_repeat('#', PHP_INT_MAX);
}

因为我有display_errors = Off,我希望这只产生以下输出(根据关机处理程序):

test fatal error output

但是我仍然收到:

PHP Fatal error:  Allowed memory size of 134217728 bytes exhausted (tried to allocate 2147483648 bytes) in /home/daniel/mydev/php/test0.php on line 24
PHP Stack trace:
PHP   1. {main}() /home/daniel/mydev/php/test0.php:0
PHP   2. str_repeat() /home/daniel/mydev/php/test0.php:24
test fatal error output

我错过了什么,将阻止这个错误跟踪输出?


结论

似乎,正如@Cthos明智地指出的那样,"E_ERROR和display_errors不能很好地合作。"

E_PARSE也是这种情况(我假设是E_CORE_ERROR/E_COMPILE_ERROR,但我没有破坏PHP安装来测试它)。我认为PHP在这些情况下强制将错误追溯到STDOUT是有道理的,因为如果不这样做,您可能永远不会知道事情是否/为什么会出错。

在这种情况下的解决方案是:

  1. 如@Cthos所建议的,在php.ini: error_reporting = (E_ALL & ~ E_ERROR)或在运行时使用error_reporting(E_ALL & ~ E_ERROR);静默E_ERROR通知
  2. 更新关机处理程序以检查最后一个错误是否是E_ERROR类型,如果是,则执行相应的操作。

至于其他致命的,如E_PARSE, E_CORE_ERROR等,你只需要确保你的代码是正确的,你的PHP工作。如果你试图沉默E_PARSE错误并在关闭函数中处理它们,它将不起作用,因为解析错误会阻止PHP走那么远。

所以,一个更新的/工作的shutdown处理程序看起来像这样:
error_reporting(E_ALL & ~ E_ERROR);
function shutdown_handler()
{
  $err = error_get_last();
  if ($err && $err['type'] == E_ERROR) {
    $msg = 'PHP Fatal Error: '.$err['message'].' in '.$err['file'].
      ' on line '.$err['line'];
    echo $msg, PHP_EOL;
  }
  exit();
}

不需要的错误信息是由于错误日志记录配置(log_errorserror_log),并且完全独立于display_errors配置。当从命令行运行脚本时,默认的php.ini配置将错误详细信息记录到stderr。在Apache下,它们显示在/etc/httpd/logs/error_log中。

查看PHP源代码的main.c中的php_error_cb函数。特别是,第1056行清楚地表明,任何格式为"PHP %s: %s In %s on line %d"的消息都是由于错误记录配置引起的(由于display_errors引起的错误信息不会有"PHP: "前缀)。

几个可能的解决方案,因为评论区对我很生气。

编辑#3与解决方案:

显然E_ERROR和display_errors不能很好地结合在一起

你可以设置error_reporting为E_ALL &~E_ERROR,然后让shutdown处理程序处理fatal(因为它是最后一个应该调用的函数)。

另外,E_ALL在PHP 5.4.0之前不包含E_DEPRECATED,所以如果你也想捕获它-使用~0 & ~E_ERROR


display_errors可能存在bug

这里有一个要点,教你如何让它吐出错误,即使你告诉它不要:https://gist.github.com/1483028

如果你通过ini_set()将display_errors设置为0,它仍然会显示Fatal Errors

虽然display_errors可以在运行时设置(使用ini_set()),但如果脚本有致命错误,它不会有任何影响。这是因为所需的运行时操作没有得到执行。

另外,你也可以把它发送到stderr,所以这是很棒的。

http://www.php.net/manual/en/errorfunc.configuration.php ini.display-errors

编辑2:

刚刚想到这一点,并这样评论,但php.ini文件不止一个。如果你在命令行上做这个,你需要编辑命令行文件(Ubuntu上是/etc/php5/cli/php.ini),而不是web文件(/etc/php5/apache2/php.ini)

我猜你只需要设置php.ini有display_errors 0.