Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

The grand, unified theory of PHP error handling

aka, Seeking generic Error Handler (ΟΚ to use commercially)

I doubt that I am the best PHP programmer around, so, although I have my own generic error handler for set_error_handler(), I wondered what others do and if there is a "best" (sorry if that sounds subjective - I just want to draw out general approaches (but even the 'best practices' tag has been removed from SO)).

To be objective about it, here's what I think is needed. Please correct me if I am wrong & point me to some good code if you agree.

  • I want to capture as much information as possible - without knowing what the error was.

  • so, for instance, it makes sense to dump the call stack.

  • and $_GET, $_POST and $_SESSION.

  • and I want the call stack & Globals to be pretty-printed

  • I want some 'plain-text' layout, not CSS & fancy JS to expand/collapse the information. My users may have to cut/paste into email or even print out & fax.

  • I would like to be able to add a header of my own devising, preferably as a parameter, but I can hack the code if need be. The header might include the program version, timestamp, etc (and, in my case, I have an audit track, so I can include the user's last few actions, which led to the crash).

  • some users may allow my code to auto-email the report, some may wish to preview it forst & them email it and some may not want me to send email.

like image 529
Mawg says reinstate Monica Avatar asked Jun 06 '11 02:06

Mawg says reinstate Monica


3 Answers

I suggest to go the "Exceptions" way.

Throw exceptions when there's a user error, and you can convert php errors into exceptions, like this:

function exception_error_handler($errno, $errstr, $errfile, $errline ) {
    throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
}
set_error_handler("exception_error_handler");

Albeit that this kind of behaviour works best in an OOP kind of environment. If you don't have a single point of entry (like a frontcontroller), you may also catch loose exceptions with this:

function myException($exception)
{
    echo "<b>Exception:</b> " , $exception->getMessage();
}

set_exception_handler('myException');

Simple debugging with exceptions would go something a bit like this:

function parseException($e) {
    $result = 'Exception: "';
    $result .= $e->getMessage();
    $trace = $e->getTrace();
    foreach (range(0, 10) as $i) {
        $result .= '" @ ';
        if (!isset($trace[$i])) {
            break;
        }
        if (isset($trace[$i]['class'])) {
            $result .= $trace[$i]['class'];
            $result .= '->';
        }
        $result .= $trace[$i]['function'];
        $result .= '(); ';
        $result .= $e->getFile() . ':' . $e->getLine() . "\n\n";
    }

    return $result;
}

From there evaluating globals etc. is a walk in the park. You might look for inspiration to the Symfony Framework Debug Toolbar, which offers many of these requests.

like image 86
Arend Avatar answered Dec 25 '22 20:12

Arend


I can't believe this hasn't been suggested yet.

At my company we just use Exceptions with a custom error handler. The error handler will compile a debug message with:

  • GET, POST, COOKIES, SESSION, GLOBALS
  • A trace
  • An error message (either the message in the Exception or the warning, yes you should also catch warnings, even STRICTness errors).

Then the message is then sent to a monitoring server, if that fails, it will try to send us an email, it that fails, it will try to log to a database (and if that fails it will log to a file). If the error is a 'fatal' error in the sense that your output can not be guaranteed, you can choose to throw a 500 header and print a default 'oops' message.

I would advise you to always automatically report all errors. The only errors you don't want to know about are errors that are caused by erroneous input by the user. In that case the errors should be presented to the user somehow. I've found that for each exception you can identify whether this is a bug in your system, or an error by a user. For instance: linking to a page, and then removing the page (this will cause a 404). You don't want to know about the 404, but your customer does.

The reason you always want to know about all errors is simple. If your system has a bug you won't know about it unless you either run into yourself, or your customer reports it (which they almost never do). We used to have a system that was good at hiding all errors and it was super buggy. We started exposing all errors and two years down the road it's a very stable application.

Additionally. There is a trick you can use to catch pesky Fatal Errors. You can use register_shutdown_handler to register a function that will always run after your PHP script has finished. Then you can use error_get_last to check for a fatal error. You can then repeat above steps to make the error known to you. This works, I use it all the time.

To round it off. Whatever you choose for error reporting has nothing to do with what the user of your application will see. You can choose to give him an error report, and you could even ask him for feedback at that point. But most of the time there is just a bug in your system so the user can't really do much with it.

like image 43
Halcyon Avatar answered Dec 25 '22 19:12

Halcyon


Exposing all your error handling to the end-user is not a good idea.

1) it exposes the internal structure of your code - OK so it should be secure even when a potential attacker has the full source code - but there's no point in making their life any easier.

2) do you really believe that the end-user will conscientiously copy all the information and send it back to you?

3) you are overwhelming the user with lots of information they don't care about.

The way I handle these is to capture as much info as practical serverside (and write it to flat file when an error occurs) then provide a meaningful error message to the user including a simple reference to where I can find the error in the logs. For large scale systems I'd also recommend capturing a fingerprint of the error (e.g. the last 6 digits of the md5 hash of the stack trace) to allow a helpdesk to manage and classify multiple reported incidents of the same underlying fault.

Remember that with PHP all the data is cleared down when the script completes. If you were writing a Java application or a program in C, then you really don't want to be constantly accumulating data - so the only options are to write debug/info entries to a log, then once an error occurs write a stack trace to the log. But with PHP for web pages, I usually keep a log of these in a PHP variable, then write it out to a file when an error occurs along with a stack trace (or when the script completes and I've set a flag in the code, e.g. via the session, for analysing how it behaves in non-error scenarios).

How much details you record is up to you - I usually log all fn entry and exit points, and any SQL queries.

e.g.

function logit($msg)
{
   global $runlog;
   static $basetime;
   if (!$basetime) $basetime=time();
   $runlog.="+" . time()-$basetime . "s : " . $msg . "\n";
}
like image 26
symcbean Avatar answered Dec 25 '22 20:12

symcbean