<?php
|
|
namespace SparkLib;
|
|
|
|
use \SparkLib\Blode\Event;
|
|
|
|
/**
|
|
* A simple error and exception logging facility.
|
|
*
|
|
* In public, send errors to the log file and display a brief "something broke"
|
|
* notice for uncaught exceptions. In the development environment, spit a log
|
|
* into the browser.
|
|
*
|
|
* This isn't pretty code. It embeds some silly HTML. Mainly, it is intended
|
|
* to be self-contained, so that when things go all pear shaped it can do the
|
|
* right thing without reference to external resources. It's also meant
|
|
* to be a simple development and diagnostic tool. Developers should expect
|
|
* Fail::log($msg) to do the right thing, mostly.
|
|
*
|
|
* To use, call <code>\SparkLib\Fail::setup($public_or_not)</code> 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:
|
|
*
|
|
* <code>
|
|
* \SparkLib\Fail::$exceptionOnErrors = array(E_RECOVERABLE_ERROR => true);
|
|
* </code>
|
|
*/
|
|
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) {
|
|
?><div style="border: 1px solid black; margin-left: 10%; margin-right: 10%; font-family: Georgia,Times,serif; z-index: 1000;">
|
|
<img src="<?= $img ?>" style="float: left;">
|
|
<h1>Something broke.</h1>
|
|
<p>...not to worry. We've logged this error and will be looking into it.</p>
|
|
<p>Reference #<?= self::$reference; ?>
|
|
<div style="width: 1px; height: 1px; clear: both !important;"></div>
|
|
</div><?php
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Spit out something useful for debugging.
|
|
*
|
|
* We install this as a shutdown function in the dev environment,
|
|
* just to force developers to pay attention
|
|
*/
|
|
public static function PukeDevelopment ()
|
|
{
|
|
// Did we actually get any failures? \
|
|
// --> skip it
|
|
// Are we supposed to just use error_log()? /
|
|
if (self::noFail() || self::$errorLogAll) {
|
|
return;
|
|
}
|
|
|
|
$img_url = static::$img_url;
|
|
|
|
// fuuuuuugly:
|
|
print <<<HTML
|
|
<style>
|
|
pre#sparkfail-errors {
|
|
position: absolute; top: 5px; left: 5px;
|
|
z-index: 2147483647;
|
|
text-align: left;
|
|
background: white;
|
|
color: red;
|
|
border: 1px solid black;
|
|
padding: 10px;
|
|
background-image: url({$img_url});
|
|
background-repeat: no-repeat;
|
|
padding-bottom: 150px;
|
|
background-position: bottom right;
|
|
}
|
|
|
|
pre#sparkfail-errors b { float: left; font-weight: bold; color: black; }
|
|
pre#sparkfail-errors b.close { cursor: pointer; color: blue; float: right; margin: 0; padding: 0; font-size: 1.1em; }
|
|
pre#sparkfail-errors hr { margin: 0; padding: 0; }
|
|
</style>
|
|
<pre id="sparkfail-errors">
|
|
<b>SparkFail</b><b class="close" onclick="foo=document.getElementById('sparkfail-errors');foo.style.display='none';">[X]</b>
|
|
<hr>
|
|
HTML;
|
|
|
|
print self::render() . "\n</pre>";
|
|
print "<script language=\"javascript\">$('body').keyup(function(e) { if(e.keyCode == 67) { $('#sparkfail-errors').hide() }; });</script>";
|
|
}
|
|
|
|
}
|