PHP : Custom error handler - handling parse & fatal errors
Asked Answered
K

6

66

How can i handle parse & fatal errors using a custom error handler?

Kettledrum answered 14/12, 2009 at 10:57 Comment(3)
Why not catch parse errors upstream with a php -l?Ranita
How do you do that from a browser that runs a PHP program?Vaughnvaught
You need jQuery.Joli
P
32

Simple Answer: You can't. See the manual:

The following error types cannot be handled with a user defined function: E_ERROR, E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, E_COMPILE_WARNING, and most of E_STRICT raised in the file where set_error_handler() is called.

For every other error, you can use set_error_handler()

EDIT:

Since it seems, that there are some discussions on this topic, with regards to using register_shutdown_function, we should take a look at the definition of handling: To me, handling an error means catching the error and reacting in a way that is "nice" for the user and the underlying data (databases, files, web services, etc.).

Using register_shutdown_function you cannot handle an error from within the code where it was called, meaning the code would still stop working at the point where the error occurs. You can, however, present the user with an error message instead of a white page, but you cannot, for example, roll back anything that your code did prior to failing.

Prefix answered 14/12, 2009 at 10:59 Comment(7)
Actually you can handle those errors with an user defined function. All you have to do is define register_shutdown_function. See my answer below for a working example I implemented in my website.Scamp
Yes, you can. However, there are some downsides to it: see my edit abovePrefix
Thank you for the update. Actually you can do more than present the user with an error message. You can log the details of the error, including variables that were set at the time the error occurred. Many people that use shared hosting do not have access to Apache logs for example. By using this function they would be able to log critical errors and address them. In addition, you can try to recover transactions. For example, if the error occurred when the user was trying to place an order, you can dump all the details of the order in a log or email and try to recover it offline.Scamp
In practice, pretty much the only difference between a fatal error and an unexpected non-fatal error is that you cannot get the stack trace for the former.Daybook
Or, you can "handle" them via an app like NewRelic that lets you capture them and then proactively monitor and fix them. If you just ignore them, they will never get handled.Palocz
As noted in the other answers, you can catch all type of errors of includes (not: require). So all you need is a small top level .php file which does all the catching, which then includes the real one. This is relatively easy to implement with NginX and php-fpm (clue: cgi.fix_pathinfo=0).Gillard
My experiments all fail. Where do the try/catch blocks go? I'm always seeing a fatal error message from the line "abc();", where there is no function abc. I want to catch this exception so the error message is not shown and I can handle the error silently. I'm using Apache 2.4.Vaughnvaught
S
81

Actually you can handle parse and fatal errors. It is true that the error handler function you defined with set_error_handler() will not be called. The way to do it is by defining a shutdown function with register_shutdown_function(). Here is what I have working in my website:

File prepend.php (this file will be prepended to all php scripts automatically). See below for tips on prepending files to PHP.

set_error_handler("errorHandler");
register_shutdown_function("shutdownHandler");

function errorHandler($error_level, $error_message, $error_file, $error_line, $error_context)
{
$error = "lvl: " . $error_level . " | msg:" . $error_message . " | file:" . $error_file . " | ln:" . $error_line;
switch ($error_level) {
    case E_ERROR:
    case E_CORE_ERROR:
    case E_COMPILE_ERROR:
    case E_PARSE:
        mylog($error, "fatal");
        break;
    case E_USER_ERROR:
    case E_RECOVERABLE_ERROR:
        mylog($error, "error");
        break;
    case E_WARNING:
    case E_CORE_WARNING:
    case E_COMPILE_WARNING:
    case E_USER_WARNING:
        mylog($error, "warn");
        break;
    case E_NOTICE:
    case E_USER_NOTICE:
        mylog($error, "info");
        break;
    case E_STRICT:
        mylog($error, "debug");
        break;
    default:
        mylog($error, "warn");
}
}

function shutdownHandler() //will be called when php script ends.
{
$lasterror = error_get_last();
switch ($lasterror['type'])
{
    case E_ERROR:
    case E_CORE_ERROR:
    case E_COMPILE_ERROR:
    case E_USER_ERROR:
    case E_RECOVERABLE_ERROR:
    case E_CORE_WARNING:
    case E_COMPILE_WARNING:
    case E_PARSE:
        $error = "[SHUTDOWN] lvl:" . $lasterror['type'] . " | msg:" . $lasterror['message'] . " | file:" . $lasterror['file'] . " | ln:" . $lasterror['line'];
        mylog($error, "fatal");
}
}

function mylog($error, $errlvl)
{
...do whatever you want...
}

PHP will call function errorHandler() if he catches an error in any of the scripts. If the error forces the script to shutdown immediately, the error is handled by function shutdownHandler().

This is working on the site I have under development. I haven't yet tested it in production. But it is currently catching all errors I find while developing it.

I believe there is a risk of catching the same error twice, once by each function. This could happen if an error that I am handling in the function shutdownHandler() was also caught by function errorHandler().

TODO's:

1 - I need to work on a better log() function to handle errors gracefully. Because I am still in development, I am basically logging the error to a database and echoing it to the screen.

2 - Implement error handling for all MySQL calls.

3 - Implement error handling for my javascript code.

IMPORTANT NOTES:

1 - I am using the following line in my php.ini to automatically prepend the above script to all php scripts:

auto_prepend_file = "/homepages/45/d301354504/htdocs/hmsee/cgi-bin/errorhandling.php"

it works well.

2 - I am logging and resolving all errors, including E_STRICT errors. I believe in developing a clean code. During development, my php.ini file has the following lines:

track_errors = 1
display_errors = 1
error_reporting = 2147483647
html_errors = 0

When I go live, I will change display_errors to 0 to reduce the risk of my users seeing ugly PHP error messages.

I hope this helps someone.

Scamp answered 6/9, 2011 at 0:29 Comment(4)
Does not seem to handle "Parse Errors"... try the following: echo "Cat"; echo "Dog" echo "Lion";Wetnurse
You have to prepend the file to handle parse errors!Obrian
E_DEPRECATED and E_USER_DEPRECATED should be also included in custom error handler function.Calculate
If I manually prepend a file like this, it does not catch the error "abc();", where abc is not defined. The "not handled" error is displayed. Please provide a complete test example.Vaughnvaught
P
32

Simple Answer: You can't. See the manual:

The following error types cannot be handled with a user defined function: E_ERROR, E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, E_COMPILE_WARNING, and most of E_STRICT raised in the file where set_error_handler() is called.

For every other error, you can use set_error_handler()

EDIT:

Since it seems, that there are some discussions on this topic, with regards to using register_shutdown_function, we should take a look at the definition of handling: To me, handling an error means catching the error and reacting in a way that is "nice" for the user and the underlying data (databases, files, web services, etc.).

Using register_shutdown_function you cannot handle an error from within the code where it was called, meaning the code would still stop working at the point where the error occurs. You can, however, present the user with an error message instead of a white page, but you cannot, for example, roll back anything that your code did prior to failing.

Prefix answered 14/12, 2009 at 10:59 Comment(7)
Actually you can handle those errors with an user defined function. All you have to do is define register_shutdown_function. See my answer below for a working example I implemented in my website.Scamp
Yes, you can. However, there are some downsides to it: see my edit abovePrefix
Thank you for the update. Actually you can do more than present the user with an error message. You can log the details of the error, including variables that were set at the time the error occurred. Many people that use shared hosting do not have access to Apache logs for example. By using this function they would be able to log critical errors and address them. In addition, you can try to recover transactions. For example, if the error occurred when the user was trying to place an order, you can dump all the details of the order in a log or email and try to recover it offline.Scamp
In practice, pretty much the only difference between a fatal error and an unexpected non-fatal error is that you cannot get the stack trace for the former.Daybook
Or, you can "handle" them via an app like NewRelic that lets you capture them and then proactively monitor and fix them. If you just ignore them, they will never get handled.Palocz
As noted in the other answers, you can catch all type of errors of includes (not: require). So all you need is a small top level .php file which does all the catching, which then includes the real one. This is relatively easy to implement with NginX and php-fpm (clue: cgi.fix_pathinfo=0).Gillard
My experiments all fail. Where do the try/catch blocks go? I'm always seeing a fatal error message from the line "abc();", where there is no function abc. I want to catch this exception so the error message is not shown and I can handle the error silently. I'm using Apache 2.4.Vaughnvaught
P
31

You can track these errors using code like this:

(Parse errors can only be caught if they occur in other script files via include() or require(), or by putting this code into an auto_prepend_file as other answers have mentioned.)

function shutdown() {
    $isError = false;

    if ($error = error_get_last()){
    switch($error['type']){
        case E_ERROR:
        case E_CORE_ERROR:
        case E_COMPILE_ERROR:
        case E_USER_ERROR:
            $isError = true;
            break;
        }
    }

    if ($isError){
        var_dump ($error);//do whatever you need with it
    }
}

register_shutdown_function('shutdown');
Primo answered 14/12, 2009 at 11:8 Comment(6)
Yes, there are a way to catch E_PARSE! Put error handler function in index.php and include/require other file where should be the site logic!Revareval
The only way I was able to prevent parse errors from displaying was to include the script that contained the parse error.Preschool
@LucasBatistussi, you still can't catch parse errors for index.phpHalting
Testing on php 5.3 - win 7. The shutdown function is NOT invoked on parse errors.Smectic
@LucasBatistussi can you explain? I put the error handler function in index.php & then include it on my script using include(index.php). But it still can not catch parse errors(syntax error).I don't think its possible without embedding it in php.ini.Pompea
When I try a test case "abc();", where function abc is not defined, INSIDE an include file, the PHP error message still appears. Can someone please provide complete demo code that works?Vaughnvaught
M
12

From the PHP.net comments on the page http://www.php.net/manual/en/function.set-error-handler.php

I have realized that a few people here mentioned that you cannot capture parse errors (type 4, E_PARSE). This is not true. Here is how I do. I hope this helps someone.

1) Create a "auto_prepend.php" file in the web root and add this:

<?php 
register_shutdown_function('error_alert'); 

function error_alert() 
{ 
        if(is_null($e = error_get_last()) === false) 
        { 
                mail('[email protected]', 'Error from auto_prepend', print_r($e, true)); 
        } 
} 
?> 

2) Then add this "php_value auto_prepend_file /www/auto_prepend.php" to your .htaccess file in the web root.

  • make sure you change the email address and the path to the file.
Minus answered 10/6, 2011 at 2:58 Comment(2)
This solution covers E_PARSE, E_COMPILE_ERROR, etc. So Dan Soap's answer and the php.net manual are wrong. Thank you!Holohedral
If you are using FastCgi, you cannot set the PHP value in .htaccess. Instead, you use your local php.ini file and set directly auto_prepend_file = /www/auto_prepend.phpLynellelynett
P
6

From my experience you can catch all type of errors, hide the default error message and display an error message of your own (if you like). Below are listed the things you need.

1) An initial/top level script, let us call it index.php where you store you custom error handler functions. Custom error function handlers must stay at the top so they catch errors below them, by "below" I mean in inclued files.

2) The assumption that this top script is error free must be true! this is very important, you cannot catch fatal errors in index.php when your custom error handler function is found in index.php.

3) Php directives (must also be found in index.php) set_error_handler("myNonFatalErrorHandler"); #in order to catch non fatal errors register_shutdown_function('myShutdown'); #in order to catch fatal errors ini_set('display_errors', false); #in order to hide errors shown to user by php ini_set('log_errors',FALSE); #assuming we log the errors our selves ini_set('error_reporting', E_ALL); #We like to report all errors

while in production (if I am not wrong) we can leave ini_set('error_reporting', E_ALL); as is in order to be able to log error, in the same time ini_set('display_errors', false); will make sure that no errors are displayed to the user.

As for the actual content of the two functions I am talking, myNonFatalErrorHandler and myShutdown, I don't put detailed content here in order to keep things simple. In addition the other visitors have given alot of examples. I just show a very plain idea.

function myNonFatalErrorHandler($v, $m, $f, $l, $c){
 $some_logging_var_arr1[]="format $v, $m, $f, ".$err_lvl[$l].", $c the way you like";
 //You can display the content of $some_logging_var_arr1 at the end of execution too.
}

function myShutdown()
{
  if( ($e=error_get_last())!==null ){
      $some_logging_var_arr2= "Format the way you like:". $err_level[$e['type']].$e['message'].$e['file'].$e['line'];
  }
//display $some_logging_var_arr2 now or later, e.g. from a custom session close function
}

as for $err_lvl it can be:

$err_lvl = array(E_ERROR=>'E_ERROR', E_CORE_ERROR=>'E_CORE_ERROR', E_COMPILE_ERROR=>'E_COMPILE_ERROR', E_USER_ERROR=>'E_USER_ERROR', E_PARSE=>'E_PARSE', E_RECOVERABLE_ERROR=>'E_RECOVERABLE_ERROR', E_WARNING=>'E_WARNING', E_CORE_WARNING=>'E_CORE_WARNING', E_COMPILE_WARNING=>'E_COMPILE_WARNING',
E_USER_WARNING=>'E_USER_WARNING', E_NOTICE=>'E_NOTICE', E_USER_NOTICE=>'E_USER_NOTICE',E_STRICT=>'E_STRICT');
Preschool answered 28/12, 2013 at 0:49 Comment(3)
Thanks for adding that the functions can't be in the same file as the error.Algo
Better use a Closure as error handler, because named functions can be overridden nowadaysGillard
This is the only answer that works for me. The key was ini_set('display_errors', false); to hide the PHP reporting. It should also be noted that the set_error_handler function is not needed, as the register_shutdown_function callback receives even E_NOTICE errors, such as the use of defined constants that have not been defined. With this understanding, exception handling (try/catch) is not needed for many programs, those which stop on the first detected error.Vaughnvaught
W
4

The script with parse error is always interrupted and it can not be handled. So if the script is called directly or by include/require, there is nothing you can do. But if it is called by AJAX, flash or any other way, there is a workaround how to detect parse errors.

I needed this to handle swfupload script. Swfupload is a flash that handles file uploads and everytime file is uploaded, it calls PHP handling script to handle filedata - but there is no browser output, so the PHP handling script needs these settings for debugging purposes:

  • warnings and notices ob_start(); at the beginning and store content into session by ob_get_contents(); at the end of the handling script: This can be displayed into browser by another script
  • fatal errors register_shutdown_function() to set the session with the same trick as above
  • parse errors if the ob_get_contents() is located at the end of the handling script and parse error occured before, the session is not filled (it is null). The debugging script can handle it this way: if(!isset($_SESSION["swfupload"])) echo "parse error";

Note 1 null means is not set to isset()

Wallop answered 2/9, 2010 at 7:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.