A modest collection of PHP libraries used at SparkFun.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

362 lines
10 KiB

12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
  1. <?php
  2. namespace SparkLib;
  3. use \SparkLib\Blode\Event;
  4. /**
  5. * A simple error and exception logging facility.
  6. *
  7. * In public, send errors to the log file and display a brief "something broke"
  8. * notice for uncaught exceptions. In the development environment, spit a log
  9. * into the browser.
  10. *
  11. * This isn't pretty code. It embeds some silly HTML. Mainly, it is intended
  12. * to be self-contained, so that when things go all pear shaped it can do the
  13. * right thing without reference to external resources. It's also meant
  14. * to be a simple development and diagnostic tool. Developers should expect
  15. * Fail::log($msg) to do the right thing, mostly.
  16. *
  17. * To use, call <code>\SparkLib\Fail::setup($public_or_not)</code> somewhere near
  18. * the start of your application. At SparkFun, we do this in a configuration file
  19. * shortly after setting up \SparkLib\Autoloader.
  20. */
  21. class Fail {
  22. protected static $failCount = 0;
  23. protected static $exceptionCount = 0;
  24. protected static $failText = '';
  25. protected static $reference = '';
  26. /**
  27. * Image to display with public errors.
  28. */
  29. public static $img_url = 'https://dlnmh9ip6v2uc.cloudfront.net/images/sparkfail.png';
  30. /**
  31. * Send all errors straight to the log?
  32. */
  33. public static $errorLogAll = false;
  34. /**
  35. * Include user agent with fail logging?
  36. */
  37. public static $logUserAgent = false;
  38. /**
  39. * Log to a specific file via file_put_contents() instead of using
  40. * error_log().
  41. */
  42. public static $logFile = null;
  43. /**
  44. * Log only strings matching this regex.
  45. */
  46. public static $grep = null;
  47. /**
  48. * Break out backtraces for php E_ errors/warning/notices/whatever
  49. */
  50. public static $doBacktraces = false;
  51. /**
  52. * Map numbers to human-readable error types.
  53. */
  54. public static $errorList = array(
  55. 1 => 'E_ERROR',
  56. 2 => 'E_WARNING',
  57. 4 => 'E_PARSE',
  58. 8 => 'E_NOTICE',
  59. 16 => 'E_CORE_ERROR',
  60. 32 => 'E_CORE_WARNING',
  61. 64 => 'E_COMPILE_ERROR',
  62. 128 => 'E_COMPILE_WARNING',
  63. 256 => 'E_USER_ERROR',
  64. 512 => 'E_USER_WARNING',
  65. 1024 => 'E_USER_NOTICE',
  66. 2048 => 'E_STRICT',
  67. 4096 => 'E_RECOVERABLE_ERROR',
  68. 8192 => 'E_DEPRECATED',
  69. 16384 => 'E_USER_DEPRECATED',
  70. 30719 => 'E_ALL',
  71. );
  72. /**
  73. * List errors which should be promoted to exceptions.
  74. *
  75. * Something like:
  76. *
  77. * <code>
  78. * \SparkLib\Fail::$exceptionOnErrors = array(E_RECOVERABLE_ERROR => true);
  79. * </code>
  80. */
  81. public static $exceptionOnErrors = array();
  82. /**
  83. * Set up error & exception handling, logging, etc.
  84. *
  85. * @param $public boolean are we operating in public?
  86. */
  87. public static function setup ($public = true)
  88. {
  89. $class = get_called_class();
  90. // We always log exceptions
  91. // The array here is a callback for \SparkLib\Fail::log()
  92. set_exception_handler(array($class, 'log'));
  93. if ($public) {
  94. // We're in public - this will display something safe for users:
  95. register_shutdown_function(array($class, 'PukePublic'));
  96. } else {
  97. // We're on a development box.
  98. set_error_handler(array($class, 'handleError'));
  99. // Did this come from a browser?
  100. if (isset($_SERVER['QUERY_STRING']) || isset($_SERVER['REMOTE_HOST']))
  101. register_shutdown_function(array($class, 'PukeDevelopment'));
  102. }
  103. }
  104. /**
  105. * Log an arbitrary string or Exception.
  106. *
  107. * @param $message string|Exception message to log
  108. * @param $type string optional type of message
  109. */
  110. public static function log ($message, $type = 'error')
  111. {
  112. // Did we get an Exception?
  113. if ($message instanceof \Exception) {
  114. // Get a unique string - we can log this, and then display it to the
  115. // user as a reference number.
  116. self::$reference .= substr(sha1(time() . __FILE__), 0, 10) . ' ';
  117. self::$exceptionCount++;
  118. $message_text = self::$reference . ':: Uncaught ' . get_class($message) . ' - '
  119. . $message->getMessage() . "\n"
  120. . $message->getTraceAsString();
  121. } elseif (is_array($message) || is_object($message)) {
  122. $message_text = print_r($message, 1);
  123. } else {
  124. $message_text = $message;
  125. }
  126. // if we're grepping for something specific, make sure this message matches:
  127. if (isset(static::$grep) && (! preg_match(static::$grep, $message_text))) {
  128. return;
  129. }
  130. // If blode is sitting around, send it our message.
  131. if (class_exists('Event')) {
  132. Event::err($message_text);
  133. }
  134. if (static::$logUserAgent && isset($_SERVER['HTTP_USER_AGENT'])) {
  135. $message_text .= ' [' . $_SERVER['HTTP_USER_AGENT'] . ']';
  136. }
  137. $message_text .= "\n";
  138. self::$failCount++;
  139. self::$failText .= $message_text;
  140. if (isset(self::$logFile)) {
  141. // Note deliberate error suppression; there's a good chance this
  142. // write will fail from the command line.
  143. @file_put_contents(self::$logFile, $message_text, \FILE_APPEND);
  144. } else {
  145. error_log($message_text);
  146. }
  147. }
  148. /**
  149. * Catch an error and do something useful with it, ignoring a
  150. * few less helpful things.
  151. */
  152. public static function handleError ($errno, $errstr, $errfile, $errline, $errcontext)
  153. {
  154. // Filter out low-urgency stuff we don't care about right now:
  155. // XXX: Get rid of SFE-specific stuff here.
  156. if ($errno === \E_NOTICE) {
  157. if (stristr($errstr, 'undefined index')) return;
  158. }
  159. if ($errno === \E_DEPRECATED) {
  160. if (strstr($errfile, 'Barcode.php')) return;
  161. }
  162. if (($errno === \E_WARNING) || ($errno === \E_STRICT)) {
  163. if (strstr($errfile, 'Barcode.php')) return;
  164. if (strstr($errfile, 'Code39.php')) return;
  165. }
  166. $errorType = static::$errorList[(int)$errno];
  167. self::log("$errfile +$errline - {$errorType} - $errstr\n");
  168. if (static::$doBacktraces) {
  169. // pull this current function call off of the trace
  170. $trace = debug_backtrace();
  171. array_shift($trace);
  172. self::log(static::compile_backtrace($trace));
  173. }
  174. if (isset(static::$exceptionOnErrors[ $errno ])) {
  175. // these get thrown on type errors and possibly elsewhere - let's
  176. // see what happens if we make sure they're fatal
  177. throw new \ErrorException($errstr, $errno, 0, $errfile, $errline);
  178. }
  179. }
  180. /**
  181. * Check whether we've seen any failure.
  182. */
  183. public static function noFail ()
  184. {
  185. return (self::$failCount === 0);
  186. }
  187. /**
  188. * How many failures have we logged?
  189. */
  190. public static function failCount () { return self::$failCount; }
  191. /**
  192. * How many exceptions have we seen?
  193. *
  194. * Fundamentally this is kind of silly, since we're probably only
  195. * ever going to get _one_.
  196. */
  197. public static function exceptionCount () { return self::$exceptionCount; }
  198. /**
  199. * Return a simple version of the failure log.
  200. */
  201. public static function render ()
  202. {
  203. if (self::noFail()) {
  204. return 'No known failures.';
  205. }
  206. return self::$failText . "\n " . self::$failCount . ' failures';
  207. }
  208. /**
  209. * Build a human-readable backtrace.
  210. */
  211. public static function compile_backtrace (array $backtrace)
  212. {
  213. $str = '';
  214. foreach ($backtrace as $stack_frame) {
  215. $str .= "{$stack_frame['file']} +{$stack_frame['line']}: {$stack_frame['function']}(";
  216. $first_argument = true;
  217. foreach ($stack_frame['args'] as $function_argument) {
  218. if (! $first_argument )
  219. $str .= ', ';
  220. $str .= $type = gettype($function_argument);
  221. switch ($type){
  222. case 'integer':
  223. case 'double':
  224. case 'boolean':
  225. $str .= "({$function_argument})";
  226. break;
  227. case 'string':
  228. $str .= '("';
  229. $str .= substr($function_argument,0, min(15, strlen($function_argument)) );
  230. $str .= '")';
  231. break;
  232. case 'array':
  233. $str .= '[' . count($function_argument) . ']';
  234. break;
  235. // a bit untested...
  236. case 'object':
  237. $str .= '('. get_class($function_argument) . ')';
  238. break;
  239. case 'resource':
  240. break;
  241. }
  242. $first_argument = false;
  243. }
  244. $str .= ")\n";
  245. }
  246. $str .= "\n\n";
  247. return $str;
  248. }
  249. /**
  250. * If we've seen any exceptions, spit out a friendly-ish error message,
  251. * safe for consumption by the general public.
  252. */
  253. public static function PukePublic ()
  254. {
  255. $img = static::$img_url;
  256. if (self::$exceptionCount) {
  257. ?><div style="border: 1px solid black; margin-left: 10%; margin-right: 10%; font-family: Georgia,Times,serif; z-index: 1000;">
  258. <img src="<?= $img ?>" style="float: left;">
  259. <h1>Something broke.</h1>
  260. <p>...not to worry. We've logged this error and will be looking into it.</p>
  261. <p>Reference #<?= self::$reference; ?>
  262. <div style="width: 1px; height: 1px; clear: both !important;"></div>
  263. </div><?php
  264. }
  265. }
  266. /**
  267. * Spit out something useful for debugging.
  268. *
  269. * We install this as a shutdown function in the dev environment,
  270. * just to force developers to pay attention
  271. */
  272. public static function PukeDevelopment ()
  273. {
  274. // Did we actually get any failures? \
  275. // --> skip it
  276. // Are we supposed to just use error_log()? /
  277. if (self::noFail() || self::$errorLogAll) {
  278. return;
  279. }
  280. $img_url = static::$img_url;
  281. // fuuuuuugly:
  282. print <<<HTML
  283. <style>
  284. pre#sparkfail-errors {
  285. position: absolute; top: 5px; left: 5px;
  286. z-index: 2147483647;
  287. text-align: left;
  288. background: white;
  289. color: red;
  290. border: 1px solid black;
  291. padding: 10px;
  292. background-image: url({$img_url});
  293. background-repeat: no-repeat;
  294. padding-bottom: 150px;
  295. background-position: bottom right;
  296. }
  297. pre#sparkfail-errors b { float: left; font-weight: bold; color: black; }
  298. pre#sparkfail-errors b.close { cursor: pointer; color: blue; float: right; margin: 0; padding: 0; font-size: 1.1em; }
  299. pre#sparkfail-errors hr { margin: 0; padding: 0; }
  300. </style>
  301. <pre id="sparkfail-errors">
  302. <b>SparkFail</b><b class="close" onclick="foo=document.getElementById('sparkfail-errors');foo.style.display='none';">[X]</b>
  303. <hr>
  304. HTML;
  305. print self::render() . "\n</pre>";
  306. print "<script language=\"javascript\">$('body').keyup(function(e) { if(e.keyCode == 67) { $('#sparkfail-errors').hide() }; });</script>";
  307. }
  308. }