In PHP things can go wrong easily. That’s totally OK. What’s not OK is if you don’t know about it. This post is about error handling, missing traces, PHP limitations and strategies against these problems. Also it is about Drupal and finally about how to push quality assurance within Drupal to the max.
Project Past
Past is our extended entity based Drupal logging framework in PHP. We were not happy with watchdog and its limitation. That’s why we started to record things on our own: With more structure, being able to query the log records, with deep Drupal integration (Views, Rules, …). That’s where our journey started.
Past can be a watchdog replacement. The strategies below have been implemented and Past covers all cases we have identified. It truly helps you(r site) to survive (or know why you(r site) died ;-) ).
The standard error handler
PHP offers to implement an error handler via set_error_handler() (http://php.net/manual/en/function.set-error-handler.php) function. In your own error handler you can then retrieve the last occurred error by calling error_get_last() as an associative array structure and act upon error occurrence.
Drupal has taken this approach. Part of the watchdog logging module utilizes this custom defined error handler, so all errors which can be captured show up in the watchdog log. It will log the error type, message and the last called function or method. However there is no backtrace.
Past implements hook_watchdog() which in addition to watchdog info composes and logs the backtrace.
The standard exception handler
Handling uncaught exceptions in PHP is more straightforward than handling PHP errors. In a custom exception handler (http://php.net/manual/en/function.set-exception-handler.php) the exception object is available which contains not only a message and a last caller place, but also a full backtrace. This makes it simple for both watchdog and Past to log detailed records for exceptions.
The limitations and the solution
PHP has major limitations when it comes to handling of certain error types with high severity. In particular the following error types cannot be handled via PHP error handler: E_ERROR, E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, E_COMPILE_WARNING.
Note that none of these errors will show up in watchdog, which makes it an unreliable source of your application status overview.
The PHP shutdown function
With Past we fill a gap by implementing the PHP shutdown function. (http://php.net/manual/en/function.register-shutdown-function.php) This function is able to capture the error type (except E_PARSE). As the error is captured in the shutdown function the backtrace is not available.
For a shutdown handler it is impossible to capture a backtrace. This is a problem and happens for instance when trying to access an object as an array, or calling an inexistent method on an object or something that's not an object at all. Backtraces would be very useful here but we can't do anything about it.
New Relic is a commercial SaaS service that needs a compiled PHP module and can capture backtraces in this case.
The error.log
E_PARSE is more tricky as it prevents any code execution; thus it does not reach the shutdown function. There is no other option but to look into server error logs. Past comes in handy in such a case as well. It can incrementally parse the server error log for you and log the errors as separate records. You will have all reported issues at one place.
Reproducing errors & exceptions
You can find a list of PHP error types at http://www.php.net/manual/en/errorfunc.constants.php. This chapter investigates examples that trigger the different known error types.
E_ERROR
This error can occur for instance when calling a function that doesn’t exist, or when using unsupported operand types. It will stop any further code execution and any code executed before will not be rolled back (obviously it cannot).
Example:
E_WARNING
Unable to open file or an constant used does not exists which name has been used then as a string. This will not prevent further code execution but may have effect on your application functionality. Your application must not contain any errors of this type.
Example:
E_PARSE
Try to not use semicolon after a function call. It will fail before any code execution happens. This makes the error impossible to capture by your application, on the other hand it can be very easily fixed and as it prevents any code execution you can expect that no inconsistencies happened.
Example:
E_NOTICE
Mostly broken coding standards which may lead to potential errors. For example referring to an undeclared variable. It is recommended to fix all such errors as they might result in real error state.
Example:
E_STRICT
Same philosophy as E_NOTICE however stricter. For example when you do not follow method signature of parent class. In general your application can live with these. Even some Drupal contrib modules that have thousands of installations result in errors of this kind. Fixing this kind of errors result in clearer code, and more robust design. That is why it makes sense to care of them.
Example:
E_RECOVERABLE_ERROR
PHP considers this error to be not fatal. So code execution will continue. This is not true when it comes to Drupal. It considers this state to be fatal and will stop any further code execution.
Example:
E_DEPRECATED
This error occurs when a native function, method or a construct is called which is considered to be deprecated (and so removed in the future). In case you write a new code never use such functions as it makes your code incompatible with newer version of PHP or a library.
Example:
E_CORE_ERROR
It means that PHP core itself is not able to execute the code. In general if this is the case there is some problem with PHP installation. For sure, we can not deal with this situation with PHP code. We expect PHP itself to be well setup.
E_CORE_WARNING
Same as in the E_CORE_ERROR, however PHP is able to run. This can happen for example when missing gd library and attempting to process an image.
E_COMPILE_ERROR
Error type indicating that not all required application source files are available. This error will prevent any code execution.
Example:
E_COMPILE_WARNING
Error type indicating that not all included source files are available. The code will be executed, however it might result in other fatal errors such as function not found. It is strongly recommended to fix such errors as it implies that file includes are a mess.
Example:
Uncaught exceptions
Error state indicated by application itself. In case an application logic does not handle exception thrown by lower layer there needs to be a mechanism to log and process such uncaught exceptions. A good example is a PDOException thrown during a failing DB write process that is not handled by the calling code.
Example:
To be able to handle uncaught exceptions at one place you can register an exception handler (see “The standard exception handler” section)
Conclusion
Past will capture all errors from your code if Drupal manages to bootstrap till hook_init successfully. This makes this tool the best existing logging framework for Drupal.
Use Past! You will love it. And fix the errors you discover.
At MD Systems, we have a clean-system policy and it makes a huge difference. We don’t accept any error in a production system and immediately take care of it. If you enable it, you will be surprised how many (undiscovered) errors happen on most production systems. This leads to unsatisfied user experience and finally loss of users and conversions.
The only thing we’re asking ourselves was - why did no one else care about all that before?! As always: Contact us if you need help debugging your most nasty bugs. We know how to do it right.