Error Handling

In the world of the Internet, as a developer, we need to protect ourselves from prying eyes. One of the easiest ways a hacker can crack your code is if you tell them what they did wrong. For that reason, it’s important to suppress errors from the public. But, this doesn’t mean we should eliminate the knowledge when an error occurs and what triggered it.  For that reason, I have written a fairly simple class that will allow for error suppression, error debugging and error reporting.

When in debug mode, the error is displayed on screen in a fairly elegant manner. Barring CSS overrides and surrounding DOM elements, the error is output in a legible manner so that it can be easily read.

When errors are suppressed, the error is caught and handled elegantly and then emailed to the specified email address.

The one drawback: if there is a parse error in the file that includes the Error.Handling.Class.php file, it will not call this class. It will use PHP’s internal error handler and print the error to screen. If anyone knows a way around this, let me know and I will implement it.

Examples:

Code:

<?php
/*
	*	Script Name: Forms
	*	Author: JJ Jiles
	*	Website: http://jjis.me
	*	Version: 1
	*	Copyright (C) March 31 2010
	*
	*	Error Catching and Debugging
	*
*/

class Error_Debug {
	#
	# a couple of basic settings
	#
	# Display Errors?:
	#	True|False
	#	This is overridden should there be a $config->show_errors var
	#
	# Email To:
	#	email for the person who needs to receive the error
	#
	# Style Sheet:
	#	Set the link to the style sheet.
	#	Exclude from the server path ( http://domain.com )
	#
	# Suppressed Error Message
	#	This is the error message that will be displayed if error reporting is turned off
	#
	var $errors = array(
			'display'     => false,
			'email_to'    => 'email@domain.com',
			'email_from'  => 'email@domain.com',
			'style_sheet' => 'css/error-reports.css',
			'suppressed_error_message' => '<div align="center"><b style="font-size: 150%;">lnnl<b style="font-size: 200%;"> (-_-) </b>lnnl</b><br /><br />aw snap! something broke!</div>'
		);

	function Error_Debug() { return true; }

	/* ***********************************************************************
	* START :::
	*
	*	Initial error catching method
	*	This will determine if it's a parse error, or other
	*	types of errors.
	*
	*	PARSE:
	*		stops everything dead in its tracks. If display
	*		errors is allowed, then show the error message
	*		otherwise suppress the error and email it
	*
	*
	*	ALL OTHERS:
	*		Run the backtrace through the previous called
	*		functions and display the information for debugging
	*		If display errors is false, suppress the error
	*		and email the error
	*********************************************************************** */
	function catch_error($n='', $s='', $f='', $l='') {
		global $security, $config;	

		#
		# check to see if the $config object exists. if so, use it's setting
		$this->errors['display'] = (isset($config->show_errors) ) ? $config->show_errors : $this->errors['display'];

		#
		# if this is a parse error, we need to handle it differently
		# because it completely stops all PHP parsing, we catch it immediately
		# and display a simple output
		if( empty($n) && false === is_null($aError = error_get_last()) ) :

			#
			# suppress the error and show something entertaining
			if ( !$this->errors['display'] ) :
				echo $this->errors['suppressed_error_message'];

			#
			# for debugging, display the information about the error
			else :
				$err = error_get_last();
				$this->echo_css();
				echo "<div id=\"error-container\">"
					. "<div id=\"error-wrapper\">"
					. "<table id=\"primary-error\">\n"
					. "<tr><td class=\"left\"><strong>Error Type: </strong></td><td>" . $err['type'] . "</td></tr>\n"
					. "<tr><td class=\"left\"><strong>Message: </strong></td><td>" . $err['message'] . "</td></tr>\n"
					. "<tr><td class=\"left\"><strong>File: </strong></td><td>" . $err['file'] . "</td></tr>\n"
					. "<tr><td class=\"left\"><strong>On line: </strong></td><td>".$err['line']."</td></tr>\n</table>\n";

			endif;

		#
		# exception errors get sent to either backtracing or suppression
		else :

			#
			# backtrace the error and display the information for debugging
			if ( @$this->errors['display'] ) : $this->error_backtrace($n, $s, $f, $l);

			#
			# suppress the error and email it
			else : $this->error_thrown($n, $s, $f, $l); 

			endif;

			return true;

		endif;
		exit();
	}
	/* ***********************************************************************
	* END :::
	*********************************************************************** */

	/* ***********************************************************************
	* START :::
	*
	*	Backtraces through the errors and displays a nice
	*	It's pretty simple. Not a lot that needs explaining
	*
	*********************************************************************** */
	function error_backtrace($errno, $errstr, $error_file, $error_line) {
		global $css;

		$errorsThrown = debug_backtrace();

		#
		# we skip the first two errors
		# they are the initial calls from error catching
		# we can ignore them
		#
		if ( isset($errorsThrown[2]) ) :
			$errorThrown  = $errorsThrown[2];

			#
			# echo css
			$this->echo_css();

			#
			# this is the primary error that started it all
			echo "<div id=\"error-container\">"
				. "<div id=\"error-wrapper\">"
				. "<table id=\"primary-error\">\n"
				. "<tr><td class=\"left\"><strong>Error: </strong></td><td>" . $errstr . "</td></tr>\n"
				. "<tr><td class=\"left\"><strong>Line: </strong></td><td>" . $error_line . "</td></tr>\n"
				. "<tr><td class=\"left\"><strong>File: </strong></td><td>" . $error_file . "</td></tr>\n"
				. "<tr><td class=\"left\"><strong>Calling Function: </strong></td><td>" . $errorThrown['function'] . "</td></tr>\n"
				. "<tr><td class=\"left\"><strong>File Name: </strong></td><td>".$errorThrown['file']."</td></tr>\n"
				. "<tr><td class=\"left\"><strong>On line: </strong></td><td>".$errorThrown['line']."</td></tr>\n</table>\n";

			#
			# for additional debugging, loop through all previous function calls
			# this assists in debugging if you call a method multiple times
			# now we can figure out which instance caused the error
			#
			echo "<p>All previously executed functions: </p>\n";

			echo "<table id=\"previous-errors\" cellpadding=5 cellspacing=0 border=0 style=\"border: 1px solid #333;\">"
				. "<tr style=\"background: #d3d3d3;\">"
				. "<th>order</th>"
				. "<th>function name</th>"
				. "<th>file name</th>"
				. "<th>line</th>"
				. "</tr>";

			$count = 0;
			array_reverse($errorsThrown);

			#
			# loop thu the backtrace and output
			foreach ($errorsThrown as $error) :
				$is_db = (bool) strchr($error['file'], 'Database.php');
				if ($count > 1 && !$is_db) :
					echo '<tr>'
					. '<td>' . ($count-1) . '</td>'
					. '<td>' . $error['function'] . '()</td>'
					. '<td>' . $error['file'] . '</td>'
					. '<td>' . $error['line'] . '</td>'
					. '</tr>';
				endif;
				$count++;

			endforeach;

			#
			# close it all up
			echo '</table>'
				. '<p>&nbsp;</p>'
				. '</div></div>';
		exit();
		endif;
	}
	/* ***********************************************************************
	* END :::
	*********************************************************************** */

	/* ***********************************************************************
	* START :::
	*
	*	Backtraces through the errors and displays a nice
	*	It's pretty simple. Not a lot that needs explaining
	*
	*********************************************************************** */
	function error_thrown($errno, $errstr, $error_file, $error_line) {
		global $security;

		$errorThrown = debug_backtrace();
		$errorFunction = '';

		#
		# we skip the first two errors
		# they are the initial calls from error catching
		# we can ignore them
		#
		if (isset($errorThrown[2])) :
			$errorThrown = $errorThrown[2];
			$errorFunction = "\n".'Calling Function: '.$errorThrown['function'];

			$referer = (isset($_SERVER['HTTP_REFERER'])) ? $_SERVER['HTTP_REFERER'] : '';

			$server_r = '';
			$server_out = $_SERVER;
			foreach ($server_out as $key=>$val) {
				$server_r .= $key.' ::: '.$val."\n\r";
			}

			$message = '';
			$message .= "Error Thrown: [{$errno}] {$errstr}{$errorFunction}\n"
					. "File Name: {$error_file}\n"
					. "On line: {$error_line}\n"
					. "Server: " . $_SERVER['SERVER_NAME'] . "\n"
					. "URL: " . $_SERVER['REQUEST_URI'] . "\n"
					. "Referer: {$referer}"
					. "\n\n\n"
					. "Server Output: \n"
					. $server_r
					. "\n";

			if ( !error_log(
				$message, 1,
				$this->errors['email_to'],
				"From: " . $this->errors['email_from']
				)
			) :

				echo $this->errors['suppressed_error_message'];

			endif;
		endif;
	}
	/* ***********************************************************************
	* END :::
	*********************************************************************** */

	/* ***********************************************************************
	* START :::
	*
	*	echo the css style sheet so the error reporting is pretty
	*
	*********************************************************************** */
	function echo_css() {
		$css_url = 'http://' . $_SERVER['HTTP_HOST'] . '/' . $this->errors['style_sheet'];

		echo '<link rel="stylesheet" id="mainstyle" type="text/css" href="' . $css_url . '" />';
	}
	/* ***********************************************************************
	* END :::
	*********************************************************************** */

}

	# Upon All Errors, call the error handling function
	 	set_error_handler(array(new Error_Debug(),'catch_error'));

	# Register function to execute at the end of the script
		register_shutdown_function(array(new Error_Debug(),'catch_error'));

	# Hide error messages
		error_reporting(0);
?>

Some CSS styling for you:

#error-container { width: 100%; color: #333!important; position: relative; background-color: #f3f3f3; border-bottom: 1px dotted #333; }
#error-wrapper { font: 110%/0.8em arial!important; letter-spacing: 0.02em; width: 850px!important; background-color: #f3f3f3!important; padding: 10px!important; }
#error-wrapper a { color: #cc6600; }
#primary-error td { color: #333!important; padding: 4px!important; border: 1px solid #f3f3f3!important; }
#primary-error td.left { width: 120px; text-align: right; padding: 8px 4px 8px 8px!important; margin: 0 8px 1px 0!important; background-color: #d3d3d3!important; }
#error-wrapper p { text-align: left; padding: 18px; clear: both!important; }
#previous-errors th { color: #333!important; padding: 6px; }
#previous-errors td { color: #333!important; padding: 4px; border-top: 1px solid #666; border-right: 1px solid #666; }

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>