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