如何捕获PHP致命错误

我可以使用set_error_handler()来捕获大多数PHP错误,但是它不适用于致命( E_ERROR )错误,例如调用一个不存在的函数。 是否有另一种方法来捕捉这些错误?

我想调用mail()的所有错误,并运行PHP 5.2.3。

使用register_shutdown_functionlogging致命错误,它需要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_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; } 

使用Swift Mailer编写error_mail函数。

也可以看看:

刚刚想出了这个解决scheme(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中定义了不同的错误types

PHP没有提供捕获和从致命错误中恢复的常规手段。 这是因为处理过程通常不会在致命错误后被恢复。 与输出缓冲区匹配的string(正如在PHP.net上描述的技术所build议的那样)绝对不适合。 这简直是​​不可靠的。

从error handling程序方法中调用mail()函数也被certificate是有问题的。 如果你有很多的错误,你的邮件服务器将被加载工作,你可以发现自己与一个粗糙的收件箱。 为避免这种情况,您可以考虑运行cron来定期扫描错误日志,并相应地发送通知。 您也可能想要查看系统监视软件,如Nagios 。


要说到关于注册关机function的一点:

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

这里的要点是,我们通常不应该尝试从致命错误中恢复,尤其是不要使用正则expression式对输出缓冲区进行恢复。 我回应了被接受的答案 ,这个答案与php.net上的一个build议有关,后来这个build议被修改或删除了。

该build议是在exception处理期间对输出缓冲区使用正则expression式,并且在发生致命错误(通过匹配您所期望的任何configuration的错误文本来检测)的情况下,尝试进行某种恢复或继续处理。 这不会是一个推荐的做法(我相信这就是为什么我也找不到原来的build议,我要么忽略它,要么php社区把它击落)。

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

所以注册一个closures函数是可以的,但是应该由closures函数执行的任务可能仅限于一些轻微的closures过程。

这里关键的一点是,任何人在这个问题上磕磕绊绊,看到最初接受的答案中的build议,都只是一些智慧的话语。 不要正则expression式输出缓冲区。

那么它似乎有可能赶上致命的错误一些其他的方式:)

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

我开发了一种方法来捕获PHP中的所有错误types(几乎全部)! 我不知道E_CORE_ERROR(我认为不会只适用于那个错误)! 但是,对于其他致命错误(E_ERROR,E_PARSE,E_COMPILE …),只使用一个error handling函数就可以正常工作! 有我的解决scheme:

把下面的代码放在你的主文件(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(); ?> 

我希望这有助于很多人! 我正在寻找这个解决scheme太久,没有find! 然后我开发了一个!

你不能像这样在注册closures函数里抛出exception:

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

但是你可以捕获和redirect请求到另一个页面。

 <?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 eg home page } } register_shutdown_function('shutdown'); $x = null; $x->method() ?> 

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

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

在Zend Framework 2中find了很好的解决scheme:

 /** * 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

一个可能更好的解决scheme是来自Monolog的一个:

链接到完整的课程代码:
https://github.com/Seldaek/monolog/blob/master/src/Monolog/ErrorHandler.php

它也可以使用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; } // [...] } 

我需要处理生产的致命错误,而不是显示一个静态样式的503服务不可用的HTML输出。 这肯定是“捕捉致命错误”的合理方法。 这就是我所做的:

我有一个自定义的error handling函数“error_handler”,这将显示任何E_ERROR,E_USER_ERROR等我的“503服务不可用”的HTML页面。现在将在关机function上调用捕获我的致命错误。

 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脚本产生任何错误。

在仍然同意keparo,捕捉致命错误确实打败了“致命错误”的目的,所以它不是真的打算为你做进一步的处理。 不要在这个关机过程中运行任何mail()函数,因为您一定会备份邮件服务器或收件箱。 将这些事件logging下来并logging下来,find这些error.log文件并将它们发送给pipe理员。

PHP有可捕捉的致命错误。 它们被定义为E_RECOVERABLE_ERROR。 PHP手册将E_RECOVERABLE_ERROR描述为:

可捕捉的致命错误。 这表明发生了一个可能的危险错误,但是并没有使发动机处于不稳定的状态。 如果错误没有被用户定义的句柄捕获(另请参见set_error_handler() ),则应用程序将中止,因为它是E_ERROR。

你可以通过使用set_error_handler()和检查E_RECOVERABLE_ERROR来“捕捉”这些“致命的”错误。 当发现这个错误时,我发现抛出exception是有用的,那么你可以使用try / catch。

这个问题和答案提供了一个有用的例子: 我怎样才能捕捉到PHPtypes提示“可捕捉的致命错误”?

然而,E_ERROR错误可以被处理,但是不能从引擎处于不稳定状态中恢复。

只是一个很好的技巧来获取当前的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停止显示错误,否则错误文本将在您的error handling程序之前发送到客户端

由于这里的大部分答案都是非常冗长的,下面是我最不赞成的答案:

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

在某些情况下,即使是致命的错误也应该被捕获(你可能需要做一些清理工作,然后才能正常退出而不会死亡)。 我已经在我的codeigniter应用程序上实现了pre_system钩子,这样我就可以通过电子邮件得到我的致命错误,这帮助我find了没有报告的错误(或者在被修复之后报告,因为我已经知道它们:))。 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(); } } 

不是真的。 致命的错误被称为,因为它们是致命的。 你不能从中恢复过来。

我开发了这个function,使“砂箱”代码可能导致致命的错误。 由于从closuresregister_shutdown_function抛出的exception不会从致命错误调用堆栈中发出,我不得不在这个函数之后退出,以提供一个统一的使用方式。

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

我用一个完整的解决scheme写了一个Wiki风格的问答,用于捕获PHP中的所有错误; 可以在这里查看/搜集/窃取/评论。

该解决scheme包括5个方法,可以包含PHP可以生成的所有错误,最终将所有错误传递给“ErrorHandler”types的对象。

希望有些人可以使用它。 即使你不直接窃取,我相信这个解决scheme至less是一个很好的例子,说明如何在PHP中处理错误。

@卢卡斯Batistussi得分创意 – 我想我可以分享我的解决scheme,以及拍摄一些类似的点…