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

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

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

Зарегистрировать фатальные ошибки, используя 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 .

Смотрите также:

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

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

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


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

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

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

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

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

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

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

Ну, похоже, можно поймать 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; } 

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

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

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

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

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

 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:

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

Возможно, лучшее решение – это одно из Монолога :

Ссылка на полный код:
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; } // [...] } 

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

У меня есть специальная функция обработки ошибок «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 и отправить их администраторам.

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

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

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

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

Однако ошибки 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 перестает отображать ошибку, иначе текст ошибки будет отправлен клиенту до вашего обработчика ошибок

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

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

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

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

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

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

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

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

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