1: <?php
2:
3: 4: 5: 6:
7:
8: namespace Tracy;
9:
10: use Tracy,
11: ErrorException;
12:
13:
14: 15: 16: 17: 18:
19: class Debugger
20: {
21: const VERSION = '2.3.1';
22:
23:
24: const DEVELOPMENT = FALSE,
25: PRODUCTION = TRUE,
26: DETECT = NULL;
27:
28: const COOKIE_SECRET = 'tracy-debug';
29:
30:
31: public static $version = self::VERSION;
32:
33:
34: public static $productionMode = self::DETECT;
35:
36:
37: private static $enabled = FALSE;
38:
39:
40: private static $done;
41:
42:
43:
44:
45: public static $strictMode = FALSE;
46:
47:
48: public static $scream = FALSE;
49:
50:
51: public static $onFatalError = array();
52:
53:
54:
55:
56: public static $maxDepth = 3;
57:
58:
59: public static $maxLen = 150;
60:
61:
62: public static $showLocation = FALSE;
63:
64:
65:
66:
67: public static $logDirectory;
68:
69:
70: public static $logSeverity = 0;
71:
72:
73: public static $email;
74:
75:
76: const DEBUG = ILogger::DEBUG,
77: INFO = ILogger::INFO,
78: WARNING = ILogger::WARNING,
79: ERROR = ILogger::ERROR,
80: EXCEPTION = ILogger::EXCEPTION,
81: CRITICAL = ILogger::CRITICAL;
82:
83:
84:
85:
86: public static $time;
87:
88:
89: public static $source;
90:
91:
92: public static $editor = 'editor://open/?file=%file&line=%line';
93:
94:
95: public static $browser;
96:
97:
98: public static $errorTemplate;
99:
100:
101:
102:
103: private static $blueScreen;
104:
105:
106: private static $bar;
107:
108:
109: private static $logger;
110:
111:
112: private static $fireLogger;
113:
114:
115: 116: 117:
118: final public function __construct()
119: {
120: throw new \LogicException;
121: }
122:
123:
124: 125: 126: 127: 128: 129: 130:
131: public static function enable($mode = NULL, $logDirectory = NULL, $email = NULL)
132: {
133: self::$time = isset($_SERVER['REQUEST_TIME_FLOAT']) ? $_SERVER['REQUEST_TIME_FLOAT'] : microtime(TRUE);
134: error_reporting(E_ALL | E_STRICT);
135:
136: if ($mode !== NULL || self::$productionMode === NULL) {
137: self::$productionMode = is_bool($mode) ? $mode : !self::detectDebugMode($mode);
138: }
139:
140:
141: if ($email !== NULL) {
142: self::$email = $email;
143: }
144: if ($logDirectory !== NULL) {
145: self::$logDirectory = $logDirectory;
146: }
147: if (self::$logDirectory) {
148: if (!is_dir(self::$logDirectory) || !preg_match('#([a-z]:)?[/\\\\]#Ai', self::$logDirectory)) {
149: self::$logDirectory = NULL;
150: self::exceptionHandler(new \RuntimeException('Logging directory not found or is not absolute path.'));
151: }
152: }
153:
154:
155: if (function_exists('ini_set')) {
156: ini_set('display_errors', !self::$productionMode);
157: ini_set('html_errors', FALSE);
158: ini_set('log_errors', FALSE);
159:
160: } elseif (ini_get('display_errors') != !self::$productionMode
161: && ini_get('display_errors') !== (self::$productionMode ? 'stderr' : 'stdout')
162: ) {
163: self::exceptionHandler(new \RuntimeException("Unable to set 'display_errors' because function ini_set() is disabled."));
164: }
165:
166: if (!self::$enabled) {
167: register_shutdown_function(array(__CLASS__, 'shutdownHandler'));
168: set_exception_handler(array(__CLASS__, 'exceptionHandler'));
169: set_error_handler(array(__CLASS__, 'errorHandler'));
170:
171: array_map('class_exists', array('Tracy\Bar', 'Tracy\BlueScreen', 'Tracy\DefaultBarPanel', 'Tracy\Dumper',
172: 'Tracy\FireLogger', 'Tracy\Helpers', 'Tracy\Logger'));
173:
174: self::$enabled = TRUE;
175: }
176: }
177:
178:
179: 180: 181:
182: public static function isEnabled()
183: {
184: return self::$enabled;
185: }
186:
187:
188: 189: 190: 191: 192:
193: public static function shutdownHandler()
194: {
195: if (self::$done) {
196: return;
197: }
198:
199: $error = error_get_last();
200: if (in_array($error['type'], array(E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE, E_RECOVERABLE_ERROR, E_USER_ERROR), TRUE)) {
201: self::exceptionHandler(
202: Helpers::fixStack(new ErrorException($error['message'], 0, $error['type'], $error['file'], $error['line'])),
203: FALSE
204: );
205:
206: } elseif (!connection_aborted() && !self::$productionMode && self::isHtmlMode()) {
207: self::getBar()->render();
208: }
209: }
210:
211:
212: 213: 214: 215: 216:
217: public static function exceptionHandler(\Exception $exception, $exit = TRUE)
218: {
219: if (self::$done) {
220: return;
221: }
222: self::$done = TRUE;
223:
224: if (!headers_sent()) {
225: $protocol = isset($_SERVER['SERVER_PROTOCOL']) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.1';
226: $code = isset($_SERVER['HTTP_USER_AGENT']) && strpos($_SERVER['HTTP_USER_AGENT'], 'MSIE ') !== FALSE ? 503 : 500;
227: header("$protocol $code", TRUE, $code);
228: if (self::isHtmlMode()) {
229: header('Content-Type: text/html; charset=UTF-8');
230: }
231: }
232:
233: if (self::$productionMode) {
234: try {
235: self::log($exception, self::EXCEPTION);
236: } catch (\Exception $e) {
237: }
238:
239: if (self::isHtmlMode()) {
240: $logged = empty($e);
241: require self::$errorTemplate ?: __DIR__ . '/assets/Debugger/error.500.phtml';
242: } elseif (PHP_SAPI === 'cli') {
243: fwrite(STDERR, 'ERROR: application encountered an error and can not continue. '
244: . (isset($e) ? "Unable to log error.\n" : "Error was logged.\n"));
245: }
246:
247: } elseif (!connection_aborted() && self::isHtmlMode()) {
248: self::getBlueScreen()->render($exception);
249: self::getBar()->render();
250:
251: } else {
252: self::fireLog($exception);
253: try {
254: $file = self::log($exception, self::EXCEPTION);
255: if ($file && !headers_sent()) {
256: header("X-Tracy-Error-Log: $file");
257: }
258: echo "$exception\n" . ($file ? "(stored in $file)\n" : '');
259: if ($file && self::$browser) {
260: exec(self::$browser . ' ' . escapeshellarg($file));
261: }
262: } catch (\Exception $e) {
263: echo "$exception\nUnable to log error: {$e->getMessage()}\n";
264: }
265: }
266:
267: try {
268: foreach (self::$onFatalError as $handler) {
269: call_user_func($handler, $exception);
270: }
271: } catch (\Exception $e) {
272: try {
273: self::log($e, self::EXCEPTION);
274: } catch (\Exception $e) {}
275: }
276:
277: if ($exit) {
278: exit(254);
279: }
280: }
281:
282:
283: 284: 285: 286: 287: 288:
289: public static function errorHandler($severity, $message, $file, $line, $context)
290: {
291: if (self::$scream) {
292: error_reporting(E_ALL | E_STRICT);
293: }
294:
295: if ($severity === E_RECOVERABLE_ERROR || $severity === E_USER_ERROR) {
296: if (Helpers::findTrace(debug_backtrace(PHP_VERSION_ID >= 50306 ? DEBUG_BACKTRACE_IGNORE_ARGS : FALSE), '*::__toString')) {
297: $previous = isset($context['e']) && $context['e'] instanceof \Exception ? $context['e'] : NULL;
298: $e = new ErrorException($message, 0, $severity, $file, $line, $previous);
299: $e->context = $context;
300: self::exceptionHandler($e);
301: }
302:
303: $e = new ErrorException($message, 0, $severity, $file, $line);
304: $e->context = $context;
305: throw $e;
306:
307: } elseif (($severity & error_reporting()) !== $severity) {
308: return FALSE;
309:
310: } elseif (self::$productionMode && ($severity & self::$logSeverity) === $severity) {
311: $e = new ErrorException($message, 0, $severity, $file, $line);
312: $e->context = $context;
313: try {
314: self::log($e, self::ERROR);
315: } catch (\Exception $foo) {}
316: return NULL;
317:
318: } elseif (!self::$productionMode && !isset($_GET['_tracy_skip_error'])
319: && (is_bool(self::$strictMode) ? self::$strictMode : ((self::$strictMode & $severity) === $severity))
320: ) {
321: $e = new ErrorException($message, 0, $severity, $file, $line);
322: $e->context = $context;
323: $e->skippable = TRUE;
324: self::exceptionHandler($e);
325: }
326:
327: $message = 'PHP ' . Helpers::errorTypeToString($severity) . ": $message";
328: $count = & self::getBar()->getPanel('Tracy:errors')->data["$file|$line|$message"];
329:
330: if ($count++) {
331: return NULL;
332:
333: } elseif (self::$productionMode) {
334: try {
335: self::log("$message in $file:$line", self::ERROR);
336: } catch (\Exception $foo) {}
337: return NULL;
338:
339: } else {
340: self::fireLog(new ErrorException($message, 0, $severity, $file, $line));
341: return self::isHtmlMode() ? NULL : FALSE;
342: }
343: }
344:
345:
346: private static function isHtmlMode()
347: {
348: return empty($_SERVER['HTTP_X_REQUESTED_WITH'])
349: && PHP_SAPI !== 'cli'
350: && !preg_match('#^Content-Type: (?!text/html)#im', implode("\n", headers_list()));
351: }
352:
353:
354:
355:
356:
357: 358: 359:
360: public static function getBlueScreen()
361: {
362: if (!self::$blueScreen) {
363: self::$blueScreen = new BlueScreen;
364: self::$blueScreen->info = array(
365: 'PHP ' . PHP_VERSION,
366: isset($_SERVER['SERVER_SOFTWARE']) ? $_SERVER['SERVER_SOFTWARE'] : NULL,
367: 'Tracy ' . self::VERSION,
368: );
369: }
370: return self::$blueScreen;
371: }
372:
373:
374: 375: 376:
377: public static function getBar()
378: {
379: if (!self::$bar) {
380: self::$bar = new Bar;
381: self::$bar->addPanel(new DefaultBarPanel('info'), 'Tracy:info');
382: self::$bar->addPanel(new DefaultBarPanel('errors'), 'Tracy:errors');
383: }
384: return self::$bar;
385: }
386:
387:
388: 389: 390:
391: public static function setLogger(ILogger $logger)
392: {
393: self::$logger = $logger;
394: }
395:
396:
397: 398: 399:
400: public static function getLogger()
401: {
402: if (!self::$logger) {
403: self::$logger = new Logger(self::$logDirectory, self::$email, self::getBlueScreen());
404: self::$logger->directory = & self::$logDirectory;
405: self::$logger->email = & self::$email;
406: }
407: return self::$logger;
408: }
409:
410:
411: 412: 413:
414: public static function getFireLogger()
415: {
416: if (!self::$fireLogger) {
417: self::$fireLogger = new FireLogger;
418: }
419: return self::$fireLogger;
420: }
421:
422:
423:
424:
425:
426: 427: 428: 429: 430: 431: 432:
433: public static function dump($var, $return = FALSE)
434: {
435: if ($return) {
436: ob_start();
437: Dumper::dump($var, array(
438: Dumper::DEPTH => self::$maxDepth,
439: Dumper::TRUNCATE => self::$maxLen,
440: ));
441: return ob_get_clean();
442:
443: } elseif (!self::$productionMode) {
444: Dumper::dump($var, array(
445: Dumper::DEPTH => self::$maxDepth,
446: Dumper::TRUNCATE => self::$maxLen,
447: Dumper::LOCATION => self::$showLocation,
448: ));
449: }
450:
451: return $var;
452: }
453:
454:
455: 456: 457: 458: 459:
460: public static function timer($name = NULL)
461: {
462: static $time = array();
463: $now = microtime(TRUE);
464: $delta = isset($time[$name]) ? $now - $time[$name] : 0;
465: $time[$name] = $now;
466: return $delta;
467: }
468:
469:
470: 471: 472: 473: 474: 475: 476: 477:
478: public static function barDump($var, $title = NULL, array $options = NULL)
479: {
480: if (!self::$productionMode) {
481: static $panel;
482: if (!$panel) {
483: self::getBar()->addPanel($panel = new DefaultBarPanel('dumps'));
484: }
485: $panel->data[] = array('title' => $title, 'dump' => Dumper::toHtml($var, (array) $options + array(
486: Dumper::DEPTH => self::$maxDepth,
487: Dumper::TRUNCATE => self::$maxLen,
488: Dumper::LOCATION => self::$showLocation,
489: )));
490: }
491: return $var;
492: }
493:
494:
495: 496: 497: 498: 499:
500: public static function log($message, $priority = ILogger::INFO)
501: {
502: return self::getLogger()->log($message, $priority);
503: }
504:
505:
506: 507: 508: 509: 510:
511: public static function fireLog($message)
512: {
513: if (!self::$productionMode) {
514: return self::getFireLogger()->log($message);
515: }
516: }
517:
518:
519: 520: 521: 522: 523:
524: public static function detectDebugMode($list = NULL)
525: {
526: $addr = isset($_SERVER['REMOTE_ADDR'])
527: ? $_SERVER['REMOTE_ADDR']
528: : php_uname('n');
529: $secret = isset($_COOKIE[self::COOKIE_SECRET]) && is_string($_COOKIE[self::COOKIE_SECRET])
530: ? $_COOKIE[self::COOKIE_SECRET]
531: : NULL;
532: $list = is_string($list)
533: ? preg_split('#[,\s]+#', $list)
534: : (array) $list;
535: if (!isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
536: $list[] = '127.0.0.1';
537: $list[] = '::1';
538: }
539: return in_array($addr, $list, TRUE) || in_array("$secret@$addr", $list, TRUE);
540: }
541:
542: }
543: