Приглашаем посетить
Черный Саша (cherny-sasha.lit-info.ru)

Handling Errors

Previous
Table of Contents
Next

Handling Errors

Now that you've seen what sort of errors PHP will generate, you need to develop a plan for dealing with them when they happen. PHP provides four choices for handling errors that fall within the error_reporting threshold:

  • Display them.

  • Log them.

  • Ignore them.

  • Act on them.

None of these options supersedes the others in importance or functionality; each has an important place in a robust error-handling system. Displaying errors is extremely beneficial in a development environment, and logging them is usually more appropriate in a production environment. Some errors can be safely ignored, and others demand reaction. The exact mix of error-handling techniques you employ depends on your personal needs.

Displaying Errors

When you opt to display errors, an error is sent to the standard output stream, which in the case of a Web page means that it is sent to the browser. You toggle this setting on and off via this php.ini setting:

display_errors = On

display errors is very helpful for development because it enables you to get instant feedback on what went wrong with a script without having to tail a logfile or do anything but simply visit the Web page you are building.

What's good for a developer to see, however, is often bad for an end user to see. Displaying PHP errors to an end user is usually undesirable for three reasons:

  • It looks ugly.

  • It conveys a sense that the site is buggy.

  • It can disclose details of the script internals that a user might be able to use for nefarious purposes.

The third point cannot be emphasized enough. If you are looking to have security holes in your code found and exploited, there is no faster way than to run in production with display_errors on. I once saw a single incident where a bad INI file got pushed out for a couple errors on a particularly high-traffic site. As soon as it was noticed, the corrected file was copied out to the Web servers, and we all figured the damage was mainly to our pride. A year and a half later, we tracked down and caught a cracker who had been maliciously defacing other members' pages. In return for our not trying to prosecute him, he agreed to disclose all the vulnerabilities he had found. In addition to the standard bag of JavaScript exploits (it was a site that allowed for a lot of user-developed content), there were a couple particularly clever application hacks that he had developed from perusing the code that had appeared on the Web for mere hours the year before.

We were lucky in that case: The main exploits he had were on unvalidated user input and nondefaulted variables (this was in the days before register_global). All our database connection information was held in libraries and not on the pages. Many a site has been seriously violated due to a chain of security holes like these:

  • Leaving display_errors on.

  • Putting database connection details (mysql_connect()) in the pages.

  • Allowing nonlocal connections to MySQL.

These three mistakes together put your database at the mercy of anyone who sees an error page on your site. You would (hopefully) be shocked at how often this occurs.

I like to leave display_errors on during development, but I never turn it on in production.

Production Display of Errors

How to notify users of errors is often a political issue. All the large clients I have worked for have had strict rules regarding what to do when a user incurs an error. Business rules have ranged from display of a customized or themed error page to complex logic regarding display of some sort of cached version of the content they were looking for. From a business perspective, this makes complete sense: Your Web presence is your link to your customers, and any bugs in it can color their perceptions of your whole business.

Regardless of the exact content that needs to be returned to a user in case of an unexpected error, the last thing I usually want to show them is a mess of debugging information. Depending on the amount of information in your error messages, that could be a considerable disclosure of information.

One of the most common techniques is to return a 500 error code from the page and set a custom error handler to take the user to a custom error page. A 500 error code in HTTP signifies an internal server error. To return one from PHP, you can send this:

   header("HTTP/1.0 500 Internal Server Error");

Then in your Apache configuration you can set this:

   ErrorDocument 500 /custom-error.php

This will cause any page returning a status code of 500 to be redirected (internallymeaning transparently to the user) to /custom-error.php.

In the section "Installing a Top-Level Exception Handler," later in this chapter, you will see an alternative, exception-based method for handling this.


Logging Errors

PHP internally supports both logging to a file and logging via syslog via two settings in the php.ini file. This setting sets errors to be logged:

log_errors = On

And these two settings set logging to go to a file or to syslog, respectively:

error_log = /path/to/filename

error_log = syslog

Logging provides an auditable trace of any errors that transpire on your site. When diagnosing a problem, I often place debugging lines around the area in question.

In addition to the errors logged from system errors or via trigger_error(), you can manually generate an error log message with this:

error_log ("This is a user defined error");

Alternatively, you can send an email message or manually specify the file. See the PHP manual for details. error_log logs the passed message, regardless of the error_reporting level that is set; error_log and error_reporting are two completely different entries to the error logging facilities.

If you have only a single server, you should log directly to a file. syslog logging is quite slow, and if any amount of logging is generated on every script execution (which is probably a bad idea in any case), the logging overhead can be quite noticeable.

If you are running multiple servers, though, syslog's centralized logging abilities provide a convenient way to consolidate logs in real-time from multiple machines in a single location for analysis and archival. You should avoid excessive logging if you plan on using syslog.

Ignoring Errors

PHP allows you to selectively suppress error reporting when you think it might occur with the @ syntax. Thus, if you want to open a file that may not exist and suppress any errors that arise, you can use this:

$fp = @fopen($file, $mode);

Because (as we will discuss in just a minute) PHP's error facilities do not provide any flow control capabilities, you might want to simply suppress errors that you know will occur but don't care about.

Consider a function that gets the contents of a file that might not exist:

$content = file_get_content($sometimes_valid);

If the file does not exist, you get an E_WARNING error. If you know that this is an expected possible outcome, you should suppress this warning; because it was expected, it's not really an error. You do this by using the @ operator, which suppresses warnings on individual calls:

$content = @file_get_content($sometimes_valid);

In addition, if you set the php.ini setting track_errors = On, the last error message encountered will be stored in $php_errormsg. This is true regardless of whether you have used the @ syntax for error suppression.

Acting On Errors

PHP allows for the setting of custom error handlers via the set_error_handler() function. To set a custom error handler, you define a function like this:

<?php
require "DB/Mysql.inc";
function user_error_handler($severity, $msg, $filename, $linenum) {
   $dbh = new DB_Mysql_Prod;
   $query = "INSERT INTO errorlog
                 (severity, message, filename, linenum, time)
                 VALUES(?,?,?,?, NOW())";
   $sth = $dbh->prepare($query);
   switch($severity) {
   case E_USER_NOTICE:
      $sth->execute('NOTICE', $msg, $filename, $linenum);
      break;
   case E_USER_WARNING:
      $sth->execute('WARNING', $msg, $filename, $linenum);
      break;
   case E_USER_ERROR:
      $sth->execute('FATAL', $msg, $filename, $linenum);
      print "FATAL error $msg at $filename:$linenum<br>";
      break;
   default:
      print "Unknown error at $filename:$linenum<br>";
      break;
   }
}
?>

You set a function with this:

set_error_handler("user_error_handler");

Now when an error is detected, instead of being displayed or printed to the error log, it will be inserted into a database table of errors and, if it is a fatal error, a message will be printed to the screen. Keep in mind that error handlers provide no flow control. In the case of a nonfatal error, when processing is complete, the script is resumed at the point where the error occurred; in the case of a fatal error, the script exits after the handler is done.

Mailing Oneself

It might seem like a good idea to set up a custom error handler that uses the mail() function to send an email to a developer or a systems administrator whenever an error occurs. In general, this is a very bad idea.

Errors have a way of clumping up together. It would be great if you could guarantee that the error would only be triggered at most once per hour (or any specified time period), but what happens more often is that when an unexpected error occurs due to a coding bug, many requests are affected by it. This means that your nifty mailing error_handler() function might send 20,000 mails to your account before you are able to get in and turn it off. Not a good thing.

If you need this sort of reactive functionality in your error-handling system, I recommend writing a script that parses your error logs and applies intelligent limiting to the number of mails it sends.



Previous
Table of Contents
Next