Custom error handling

Coding Critique is the place to post source code for peer review by other members of DevNetwork. Any kind of code can be posted. Code posted does not have to be limited to PHP. All members are invited to contribute constructive criticism with the goal of improving the code. Posted code should include some background information about it and what areas you specifically would like help with.

Popular code excerpts may be moved to "Code Snippets" by the moderators.

Moderator: General Moderators

Post Reply
potatobob
Forum Newbie
Posts: 9
Joined: Thu Nov 23, 2006 3:34 pm

Custom error handling

Post by potatobob »

This is a crude custom error handling system I scrapped up quickly from some other project, but there is a lot of room for improvement so I'm posting it here to see all the flames I can get 8O
Criticism please!

*note: I modified some stuff to make it easier to look at out of context

Here we have the custom error handler

Code: Select all

/**
 * Global error handler.
 *
 * This should not be called by anything - only for uncaught errors.
 */
public function globalErrorHandler($errorNumber, $errorMsg, $file, $line, $context)
{
	$error = new SpotSec_Error($errorNumber, $errorMsg, $file, $line, $context, SpotSec_Error::TYPE_ERROR, false);

	// Log error
	SpotSec_Error_Logger::log($error);

	// Queue error for those implementations that need it (e.g. web-interface, SOAP)
	SpotSec_Error_Queue::getInstance()->push($error);

	// Show error on standard out if running from command line
	if (preg_match('/cli/', php_sapi_name()))
		echo SpotSec_Error::getErrorType($errorNumber) . ": " . $errorMsg . " - $file ($line)\n";

	// TODO: Add "when to die" policy for uncaught errors
}
Here is the error object

Code: Select all

class SpotSec_Error
{
	protected $_code;
	protected $_message;
	protected $_tag;
	protected $_line;
	protected $_context;
	protected $_caught;
	protected $_trace;
	protected $_type;

	const TYPE_EXCEPTION = 'exception';
	const TYPE_ERROR = 'error';

	/**
	 * Global Error types
	 *
	 * Define global error message mapping.
	 * Handled errors and exceptions use COMMON_xxx constants.
	 * Unhandled errors and exceptions use built-in E_xxx constants.
	 */
	static $globalErrorTypes = array(
		SpotSec_Exception::FATAL => 'fatal',
		SpotSec_Exception::ERROR => 'error',
		SpotSec_Exception::WARNING => 'warning',
		SpotSec_Exception::INFO => 'info',
		SpotSec_Exception::VALIDATION => 'validation',
		SpotSec_Exception::NOTICE => 'notice',
		SpotSec_Exception::DEBUG => 'debug',
		E_STRICT => 'strict',
		E_ERROR => 'error',
		E_WARNING => 'warning',
		E_PARSE => 'parse error',
		E_NOTICE => 'notice',
		E_CORE_ERROR => 'core error',
		E_CORE_WARNING => 'core warning',
		E_COMPILE_ERROR => 'compile error',
		E_COMPILE_WARNING => 'compile warning',
		E_USER_ERROR => 'user error',
		E_USER_WARNING => 'user warning',
		E_USER_NOTICE => 'user notice'
	);

	/**
	 * Error constructor.
	 *
	 * @param integer $code error code
	 * @param string $message error message
	 * @param string $tag a method name or some other nickname
	 * @param integer $line line number
	 * @param array $context error context
	 * @param integer $type type of error - exception or error
	 * @param boolean $caught true if error was caught by application
	 * @param array $trace error back trace
	 * @returns void
	 */
	function __construct($code, $message, $tag, $line, $context = null, $type, $caught = true, $trace = null)
	{
		$this->_code = $code;
		$this->_message = $message;
		$this->_tag = $tag;
		$this->_line = $line;
		$this->_context = $context;
		$this->_type = $type;
		$this->_caught = $caught;
		$this->_trace = $trace;
	}

	/**
	 * Returns error code.
	 *
	 * @returns integer error code
	 */
	function getCode()
	{
		return $this->_code;
	}

	/**
	 * Returns error message.
	 *
	 * @returns string error message
	 */
	function getMessage()
	{
		return $this->_message;
	}

	/**
	 * Returns error tag.
	 *
	 * @returns string error tag
	 */
	function getTag()
	{
		return $this->_tag;
	}

	/**
	 * Returns line number where error occurred.
	 *
	 * @returns integer line number
	 */
	function getLine()
	{
		return $this->_line;
	}

	/**
	 * Returns error context.
	 *
	 * @returns array error context
	 */
	function getContext()
	{
		return $this->_context;
	}

	/**
	 * Returns flag on state of the error.
	 *
	 * @returns boolean true if error was caught by application.
	 */
	function isCaught()
	{
		return $this->_caught;
	}

	/**
	 * Returns error type: error or exception.
	 *
	 * @returns string error type
	 */
	function getType()
	{
		return $this->_type;
	}

	/**
	 * Returns error trace.
	 *
	 * @returns array error trace.
	 */
	function getTrace()
	{
		if ($this->_trace) {
			return $this->_trace;
		} else {
			return array();
		}
	}

	/**
	 * Returns the error level string from the error code
	 *
	 * @todo move this somewhere else
	 * @param integer $code
	 * @return string
	 */
	public static function getErrorType($code)
	{
		$typemap = self::$globalErrorTypes;

		if (in_array($code, $typemap, true)) {
			return $typemap[$code];
		} else {
			return null;
		}
	}
}
This is the error logging class

Code: Select all

class SpotSec_Error_Logger
{
	/**
	 * Constructor
	 *
	 */
	private function __construct()
	{}

	/**
	 * Logs errors
	 *
	 * @todo look at todo comments and fix COMMON_DEBUG_MODE
	 *
	 * @param SpotSec_Error $error
	 */
	public static function log(SpotSec_Error $error)
	{
		static $basetime = 0;

		// Set the log variables
		$errorNumber = $error->getCode();
		$errorMsg = $error->getMessage();
		$file = $error->getTag();
		$line = $error->getLine();
		$context = $error->getContext();
		$caught = $error->isCaught();
		$type = $error->getType();

		// TODO: discuss what policy we want here.  Current policy is...
		// In debug mode, all errors are logged. In production mode, the following errors are logged:
		// - all uncaught errors
		// - COMMON_ERROR
		// - COMMON_FATAL
		/*if ((!COMMON_DEBUG_MODE) && ($errorNumber >= SpotSec_Exception::WARNING)) {
			return;
		}*/

		// Set prefix for log line
		if ($type == SpotSec_Error::TYPE_ERROR) {
			$prefix = 'error';
		} elseif ($type == SpotSec_Error::TYPE_EXCEPTION) {
			$prefix = 'exception';
		} else {
			$prefix = 'unknown';
		}

		if (!$caught) {
			$prefix .= ' uncaught';
		}

		// Specify log line format
		$logline = sprintf("$prefix: %s: %s (%d): %s", SpotSec_Error::getErrorType($errorNumber), preg_replace('/.*\//', '', $file), $line, $errorMsg);

		// Perform extra goodness in debug mode
		if (COMMON_DEBUG_MODE) {
			// Append timestamp to log line
			if ($basetime == 0) {
				$basetime = microtime(true);
				$timestamp = 0;
			} else {
				$currenttime = microtime(true);
				$timestamp = microtime(true) - $basetime;
			}

			$logline = sprintf("%.4f: %s", round($timestamp, 4),  $logline);

			// Log messages to standard out when in command-line mode
			if (ini_get('display_errors') && preg_match('/cli/', php_sapi_name())) {
				echo "$logline\n";
			}

			// Log messages to custom log file (if set) and standard out on
			if (ini_get('error_log')) {
				date_default_timezone_set('CST');
				$timestamp = date("M j G:i:s T Y");
				error_log("{$timestamp}: $logline\n", 3, ini_get('error_log'));

				foreach ($error->getTrace() as $traceinfo) {
					// Backtrace log format
					$logline = sprintf("$prefix: debug backtrace: %s (%d): %s",
									   preg_replace("/.*\//", "", $traceinfo['file']),
									   $traceinfo['line'],
									   $traceinfo['function']);
					error_log("{$timestamp}: $logline\n", 3, ini_get('error_log'));
				}
			}
		} else {
			$logName = 'SpotSec';
			// Log errors to syslog
			try {
				Zend_Log::registerLogger(new SpotSec_Log_Adapter_Syslog($logName, LOG_NDELAY | LOG_PID, LOG_LOCAL6, false), $logName);
				Zend_Log::log($logline, Zend_Log::LEVEL_INFO, $logName);
			} catch (Exception $e) {
				//TODO: do backup plan
			}

			// Log backtrace
			foreach ($error->getTrace() as $traceinfo) {
				// Backtrace log format
				$logline = sprintf("$prefix: debug backtrace: %s (%d): %s",
								   preg_replace('/.*\//', '', $traceinfo['file']),
								   $traceinfo['line'],
								   $traceinfo['function']);

				try {
					Zend_Log::log($logline, Zend_Log::LEVEL_INFO, $logName);
				} catch (Exception $e) {
					//TODO: do backup plan
				}
			}
		}
	}

	/**
	 * Logs an exception to the log.
	 *
	 * @static
	 * @global array global error message mapping
	 * @return void
	 */
	public static function logException(Exception $exception, $iscaught)
	{
		self::log(
			new SpotSec_Error(
				$exception->getCode(),
				$exception->getMessage(),
				$exception->getFile(),
				$exception->getLine(),
				'',
				SpotSec_Error::TYPE_EXCEPTION,
				$iscaught,
				$exception->getTrace()
			)
		);
	}
}
Finally errors are stored in the queue

Code: Select all

class SpotSec_Error_Queue
{
	/**
	 * @var SpotSec_ErrorQueue instance
	 */
	static private $_instance = null;

	/**
	 * @var array error queue
	 */
	static private $_errors = array();

	/**
	 * SpotSec_ErrorQueue constructor.
	 *
	 * @access private
	 */
	private function __construct()
	{}

	/**
	 * Do not allow attempts to clone our singleton.
	 *
	 * @access private
	 */
	private function __clone()
	{}

	/**
	 * Returns an ErrorQueue instance.
	 *
	 * @static
	 * @return ErrorQueue current instance
	 */
	static public function getInstance()
	{
		if (self::$_instance == null) {
			self::$_instance = new self;
		}

		return self::$_instance;
	}

	/**
	 * Add error to error queue.
	 *
	 * @param SpotSec_Error $error error object
	 * @return void
	 */
	public function push(SpotSec_Error $error)
	{
		self::$_errors[] = $error;
	}

	/**
	 * Return list of queued Error objects.
	 *
	 * @param boolean $purge if true, the queue will be purged
	 *
	 * @return array list of Errors
	 */
	public function getAll($purge = true)
	{
	    $errors = self::$_errors;

	    if ($purge){
	        self::$_errors = array();
	    }

		return $errors;
	}

	/**
	 * Return list of queued error messages.
	 *
	 * @param boolean $purge if true, the queue will be purged
	 *
	 * @return array list of Errors
	 */
	public function getAllMessages($purge = true)
	{
		$messages = array();
		foreach (self::$_errors as $error) {
            if ($error->isCaught()) {
                $messages[] = $error->getMessage();
            } else {
                if (ini_get('display_errors')) {
                    $errorNumber = $error->getCode();

                    if(($errorNumber & error_reporting()) == $errorNumber) {
                        $tag = preg_replace("/.*\//", "", $error->getTag());
                        $line = $error->getLine();
                        $errmsg = $error->getMessage();
                        $messages[] = sprintf("%s: %s (%d): %s", SpotSec_Error::getErrorType($errorNumber), $tag, $line, $errmsg);
                    }
                }
            }
		}

	    if ($purge){
	        self::$_errors = array();
	    }

		return $messages;
	}

	/**
	 * @access private
	 */
	function __destruct()
	{}
}
User avatar
Chris Corbyn
Breakbeat Nuttzer
Posts: 13098
Joined: Wed Mar 24, 2004 7:57 am
Location: Melbourne, Australia

Post by Chris Corbyn »

It looks really cool, but in my few minutes reading your code and comments I'm having difficulty figuring out a use-case example. Could you post something? :)
potatobob
Forum Newbie
Posts: 9
Joined: Thu Nov 23, 2006 3:34 pm

Post by potatobob »

I really haven't made it useful, but it was a good time waster.

Lets say we have an error somewhere in our application
1. Error handled by globalErrorHandler() or triggered/generated error
a. Create error object
b. Log error
c. Push error object to queue

Now you can do what ever you want with the error objects within the queue. Usually would just have the queue printed into a debug window, so obviously I haven't used it very well.
:oops:

The idea is that you can handle errors whenever appropriate. The code might be confusing still because it is part of an exception handler too.
User avatar
Chris Corbyn
Breakbeat Nuttzer
Posts: 13098
Joined: Wed Mar 24, 2004 7:57 am
Location: Melbourne, Australia

Post by Chris Corbyn »

The reason I was interested in what you might have been trying to do was because I've written some PHP5 code which does a reasonable amount of exception throwing/handling. I'm currently porting the code to PHP4 but because my exceptions are only for fatal errors (i.e. they are used more as an informative way to indicate a problem) I'm not overly happy with just returning boolean false or similar. I could use trigger_error() but then that's less friendly when the error is technically recoverable (such as a failing connection). I've been trying to come up with a way to trigger errors in PHP4 in a similar fashion to how exceptions work. It's a bit of a dead end really, far from having the user regularly check $obj->hasError() and $obj->getError().
potatobob
Forum Newbie
Posts: 9
Joined: Thu Nov 23, 2006 3:34 pm

Post by potatobob »

I pretty much cant think of any other way to do that in php4 other than that.


Or maybe something dumb like this

Code: Select all

$obj->myfunc($arg1, $arg2, &$error);
$obj->myfunc1($arg1, $arg2, &$error);
$obj->myfunc2($arg1, $arg2, &$error);

if ($error->isError()) {
 die();
}
User avatar
Chris Corbyn
Breakbeat Nuttzer
Posts: 13098
Joined: Wed Mar 24, 2004 7:57 am
Location: Melbourne, Australia

Post by Chris Corbyn »

I'm just writing something that's looking good so far. Will post back in 30-40 mins if it doesn't fall flat on it's face. No units... just rushing out a proof of concept :)
User avatar
Chris Corbyn
Breakbeat Nuttzer
Posts: 13098
Joined: Wed Mar 24, 2004 7:57 am
Location: Melbourne, Australia

Post by Chris Corbyn »

Actually, it's a complete mess (my idea I mean :P), I ended up having to make sure everything is in a function and that all functions check the error handler before executing code -- overkill of the century!

I apologise for the complete lack of commenting here:

Code: Select all

<?php

class ExceptionManager
{
        var $_wait = false;
        var $_expect = array();
        var $_caught = null;

        function &getInstance()
        {
                static $instance = false;
                if (!$instance)
                {
                        $instance = array(new ExceptionManager());
                }
                return $instance[0];
        }

        function expect($type="Php4Exception")
        {
                $me =& ExceptionManager::getInstance();
                $me->_expect[] = $type;
        }

        function caught()
        {
                $me =& ExceptionManager::getInstance();
                $e = $me->_caught;
                $me->_caught = null;
                return $e;
        }

        function _clearExpect($type)
        {
                $me =& ExceptionManager::getInstance();
                unset($me->_expect[$type]);
        }

        function _dieFatally($e)
        {
                $output = "<p><strong>Uncaught Php4 Exception</strong> of type " . get_class($e) . ".";
                $output .= "<br /><strong>Message</strong>: " . $e->getMessage();
                $output .= "<br /><strong>Number</strong>: " . $e->getNumber();
                $output .= "<br /><strong>Backtrace</strong>: <pre>" . print_r($e->getTrace(), true) . "</pre></p>";
                die($output);
        }

        function _wasExpected($e)
        {
                $me =& ExceptionManager::getInstance();
                foreach ($me->_expect as $type)
                {
                        if (is_a($e, $type)) { return $type; }
                }
                return false;
        }

        function nothingCaught()
        {
                $me =& ExceptionManager::getInstance();
                return ($me->_caught === null);
        }

        function notify($e)
        {
                $me =& ExceptionManager::getInstance();
                if ($me->nothingCaught())
                {
                        $me->_caught = $e;
                }
                if (!$type = $me->_wasExpected($e))
                {
                        $me->_dieFatally($e);
                }
                $me->_clearExpect($type);
        }
}

Code: Select all

<?php

require_once dirname(__FILE__) . "/ExceptionManager.php";

class Php4Exception
{
        var $message;
        var $number;
        var $trace;

        function Php4Exception($message, $errno=0)
        {
                $this->message = $message;
                $this->number = $errno;
                $this->trace = debug_backtrace();
        }

        function getMessage()
        {
                return $this->message;
        }

        function getNumber()
        {
                return $this->number;
        }

        function getTrace()
        {
                return $this->trace;
        }

        function throwIt()
        {
                ExceptionManager::notify($this);
                return false;
        }
}

Code: Select all

<?php

require_once "Php4Exception.php";

class FooException extends Php4Exception {}

class Foo
{
        function bar()
        {
                echo "Creating Bar<br />";
                return new Bar();
        }

        function doBar()
        {
                if (!ExceptionManager::nothingCaught()) return false;
                $e = new FooException("Foo errored here");
                return $e->throwIt();
        }
}

class BarException extends Php4Exception {}

class Bar
{
        function doFoo()
        {
                if (!ExceptionManager::nothingCaught()) return false;
                $e = new BarException("Bar errored here");
                return $e->throwIt();
        }

        function doZip()
        {
                if (!ExceptionManager::nothingCaught()) return false;
                $e = new Php4Exception("This shouldn't be thrown");
                return $e->throwIt();
        }
}

$foo = new Foo();

ExceptionManager::expect("FooException");

$bar = $foo->bar();

ExceptionManager::expect("BarException");

$bar->doFoo(); //Will throw a caught exception of BarException
$foo->doBar(); //Won't be executed

if ($e = ExceptionManager::caught()) //Is BarException
{
        echo "Error handled:<br />";
        echo $e->getMessage();
}

$bar->doZip(); //Throws uncaught exception - fatal

/* MODELLED ON THE SUPPOSEDLY EQUIVALENT PHP5
 ******************************************

$foo = new Foo();

try {
        $bar = $foo->bar();
        try {
                $bar->doFoo(); //BarException thrown
                $foo->doBar(); //Never runs
        } catch (BarException $e) {
                echo $e->getMessage(); //Caught
        }
} catch (FooException $e) {
       echo $e->getMessage(); //No
}

$bar->doZip(); //Uncaught - fatal

*/
potatobob
Forum Newbie
Posts: 9
Joined: Thu Nov 23, 2006 3:34 pm

Post by potatobob »

8O the the style does not seem to be very easy on the eyes
Post Reply