\SparkLib\Fail::setup($public_or_not) somewhere near
* the start of your application. At SparkFun, we do this in a configuration file
* shortly after setting up \SparkLib\Autoloader.
*/
class Fail {
protected static $failCount = 0;
protected static $exceptionCount = 0;
protected static $failText = '';
protected static $reference = '';
/**
* Image to display with public errors.
*/
public static $img_url = 'https://dlnmh9ip6v2uc.cloudfront.net/images/sparkfail.png';
/**
* Send all errors straight to the log?
*/
public static $errorLogAll = false;
/**
* Include user agent with fail logging?
*/
public static $logUserAgent = false;
/**
* Log to a specific file via file_put_contents() instead of using
* error_log().
*/
public static $logFile = null;
/**
* Log only strings matching this regex.
*/
public static $grep = null;
/**
* Break out backtraces for php E_ errors/warning/notices/whatever
*/
public static $doBacktraces = false;
/**
* Map numbers to human-readable error types.
*/
public static $errorList = array(
1 => 'E_ERROR',
2 => 'E_WARNING',
4 => 'E_PARSE',
8 => 'E_NOTICE',
16 => 'E_CORE_ERROR',
32 => 'E_CORE_WARNING',
64 => 'E_COMPILE_ERROR',
128 => 'E_COMPILE_WARNING',
256 => 'E_USER_ERROR',
512 => 'E_USER_WARNING',
1024 => 'E_USER_NOTICE',
2048 => 'E_STRICT',
4096 => 'E_RECOVERABLE_ERROR',
8192 => 'E_DEPRECATED',
16384 => 'E_USER_DEPRECATED',
30719 => 'E_ALL',
);
/**
* List errors which should be promoted to exceptions.
*
* Something like:
*
*
* \SparkLib\Fail::$exceptionOnErrors = array(E_RECOVERABLE_ERROR => true);
*
*/
public static $exceptionOnErrors = array();
/**
* Set up error & exception handling, logging, etc.
*
* @param $public boolean are we operating in public?
*/
public static function setup ($public = true)
{
$class = get_called_class();
// We always log exceptions
// The array here is a callback for \SparkLib\Fail::log()
set_exception_handler(array($class, 'log'));
if ($public) {
// We're in public - this will display something safe for users:
register_shutdown_function(array($class, 'PukePublic'));
} else {
// We're on a development box.
set_error_handler(array($class, 'handleError'));
// Did this come from a browser?
if (isset($_SERVER['QUERY_STRING']) || isset($_SERVER['REMOTE_HOST']))
register_shutdown_function(array($class, 'PukeDevelopment'));
}
}
/**
* Log an arbitrary string or Exception.
*
* @param $message string|Exception message to log
* @param $type string optional type of message
*/
public static function log ($message, $type = 'error')
{
// Did we get an Exception?
if ($message instanceof \Exception) {
// Get a unique string - we can log this, and then display it to the
// user as a reference number.
self::$reference .= substr(sha1(time() . __FILE__), 0, 10) . ' ';
self::$exceptionCount++;
$message_text = self::$reference . ':: Uncaught ' . get_class($message) . ' - '
. $message->getMessage() . "\n"
. $message->getTraceAsString();
} elseif (is_array($message) || is_object($message)) {
$message_text = print_r($message, 1);
} else {
$message_text = $message;
}
// if we're grepping for something specific, make sure this message matches:
if (isset(static::$grep) && (! preg_match(static::$grep, $message_text))) {
return;
}
// If blode is sitting around, send it our message.
if (class_exists('Event')) {
Event::err($message_text);
}
if (static::$logUserAgent && isset($_SERVER['HTTP_USER_AGENT'])) {
$message_text .= ' [' . $_SERVER['HTTP_USER_AGENT'] . ']';
}
$message_text .= "\n";
self::$failCount++;
self::$failText .= $message_text;
if (isset(self::$logFile)) {
// Note deliberate error suppression; there's a good chance this
// write will fail from the command line.
@file_put_contents(self::$logFile, $message_text, \FILE_APPEND);
} else {
error_log($message_text);
}
}
/**
* Catch an error and do something useful with it, ignoring a
* few less helpful things.
*/
public static function handleError ($errno, $errstr, $errfile, $errline, $errcontext)
{
// Filter out low-urgency stuff we don't care about right now:
// XXX: Get rid of SFE-specific stuff here.
if ($errno === \E_NOTICE) {
if (stristr($errstr, 'undefined index')) return;
}
if ($errno === \E_DEPRECATED) {
if (strstr($errfile, 'Barcode.php')) return;
}
if (($errno === \E_WARNING) || ($errno === \E_STRICT)) {
if (strstr($errfile, 'Barcode.php')) return;
if (strstr($errfile, 'Code39.php')) return;
}
$errorType = static::$errorList[(int)$errno];
self::log("$errfile +$errline - {$errorType} - $errstr\n");
if (static::$doBacktraces) {
// pull this current function call off of the trace
$trace = debug_backtrace();
array_shift($trace);
self::log(static::compile_backtrace($trace));
}
if (isset(static::$exceptionOnErrors[ $errno ])) {
// these get thrown on type errors and possibly elsewhere - let's
// see what happens if we make sure they're fatal
throw new \ErrorException($errstr, $errno, 0, $errfile, $errline);
}
}
/**
* Check whether we've seen any failure.
*/
public static function noFail ()
{
return (self::$failCount === 0);
}
/**
* How many failures have we logged?
*/
public static function failCount () { return self::$failCount; }
/**
* How many exceptions have we seen?
*
* Fundamentally this is kind of silly, since we're probably only
* ever going to get _one_.
*/
public static function exceptionCount () { return self::$exceptionCount; }
/**
* Return a simple version of the failure log.
*/
public static function render ()
{
if (self::noFail()) {
return 'No known failures.';
}
return self::$failText . "\n " . self::$failCount . ' failures';
}
/**
* Build a human-readable backtrace.
*/
public static function compile_backtrace (array $backtrace)
{
$str = '';
foreach ($backtrace as $stack_frame) {
$str .= "{$stack_frame['file']} +{$stack_frame['line']}: {$stack_frame['function']}(";
$first_argument = true;
foreach ($stack_frame['args'] as $function_argument) {
if (! $first_argument )
$str .= ', ';
$str .= $type = gettype($function_argument);
switch ($type){
case 'integer':
case 'double':
case 'boolean':
$str .= "({$function_argument})";
break;
case 'string':
$str .= '("';
$str .= substr($function_argument,0, min(15, strlen($function_argument)) );
$str .= '")';
break;
case 'array':
$str .= '[' . count($function_argument) . ']';
break;
// a bit untested...
case 'object':
$str .= '('. get_class($function_argument) . ')';
break;
case 'resource':
break;
}
$first_argument = false;
}
$str .= ")\n";
}
$str .= "\n\n";
return $str;
}
/**
* If we've seen any exceptions, spit out a friendly-ish error message,
* safe for consumption by the general public.
*/
public static function PukePublic ()
{
$img = static::$img_url;
if (self::$exceptionCount) {
?>
...not to worry. We've logged this error and will be looking into it.
Reference #= self::$reference; ?>
SparkFail[X]"; print ""; } }
HTML; print self::render() . "\n