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.3';
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 $done;
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::$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:
218: public static function exceptionHandler($exception, $exit = TRUE)
219: {
220: if (self::$done) {
221: return;
222: }
223: self::$done = TRUE;
224:
225: if (!headers_sent()) {
226: $protocol = isset($_SERVER['SERVER_PROTOCOL']) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.1';
227: $code = isset($_SERVER['HTTP_USER_AGENT']) && strpos($_SERVER['HTTP_USER_AGENT'], 'MSIE ') !== FALSE ? 503 : 500;
228: header("$protocol $code", TRUE, $code);
229: if (self::isHtmlMode()) {
230: header('Content-Type: text/html; charset=UTF-8');
231: }
232: }
233:
234: if (self::$productionMode) {
235: try {
236: self::log($exception, self::EXCEPTION);
237: } catch (\Exception $e) {
238: }
239:
240: if (self::isHtmlMode()) {
241: $logged = empty($e);
242: require self::$errorTemplate ?: __DIR__ . '/assets/Debugger/error.500.phtml';
243: } elseif (PHP_SAPI === 'cli') {
244: fwrite(STDERR, 'ERROR: application encountered an error and can not continue. '
245: . (isset($e) ? "Unable to log error.\n" : "Error was logged.\n"));
246: }
247:
248: } elseif (!connection_aborted() && self::isHtmlMode()) {
249: self::getBlueScreen()->render($exception);
250: self::getBar()->render();
251:
252: } else {
253: self::fireLog($exception);
254: try {
255: $file = self::log($exception, self::EXCEPTION);
256: if ($file && !headers_sent()) {
257: header("X-Tracy-Error-Log: $file");
258: }
259: echo "$exception\n" . ($file ? "(stored in $file)\n" : '');
260: if ($file && self::$browser) {
261: exec(self::$browser . ' ' . escapeshellarg($file));
262: }
263: } catch (\Exception $e) {
264: echo "$exception\nUnable to log error: {$e->getMessage()}\n";
265: }
266: }
267:
268: try {
269: foreach (self::$onFatalError as $handler) {
270: call_user_func($handler, $exception);
271: }
272: } catch (\Exception $e) {
273: try {
274: self::log($e, self::EXCEPTION);
275: } catch (\Exception $e) {
276: }
277: }
278:
279: if ($exit) {
280: exit(254);
281: }
282: }
283:
284:
285: 286: 287: 288: 289: 290:
291: public static function errorHandler($severity, $message, $file, $line, $context)
292: {
293: if (self::$scream) {
294: error_reporting(E_ALL | E_STRICT);
295: }
296:
297: if ($severity === E_RECOVERABLE_ERROR || $severity === E_USER_ERROR) {
298: if (Helpers::findTrace(debug_backtrace(PHP_VERSION_ID >= 50306 ? DEBUG_BACKTRACE_IGNORE_ARGS : FALSE), '*::__toString')) {
299: $previous = isset($context['e']) && $context['e'] instanceof \Exception ? $context['e'] : NULL;
300: $e = new ErrorException($message, 0, $severity, $file, $line, $previous);
301: $e->context = $context;
302: self::exceptionHandler($e);
303: }
304:
305: $e = new ErrorException($message, 0, $severity, $file, $line);
306: $e->context = $context;
307: throw $e;
308:
309: } elseif (($severity & error_reporting()) !== $severity) {
310: return FALSE;
311:
312: } elseif (self::$productionMode && ($severity & self::$logSeverity) === $severity) {
313: $e = new ErrorException($message, 0, $severity, $file, $line);
314: $e->context = $context;
315: try {
316: self::log($e, self::ERROR);
317: } catch (\Exception $foo) {
318: }
319: return NULL;
320:
321: } elseif (!self::$productionMode && !isset($_GET['_tracy_skip_error'])
322: && (is_bool(self::$strictMode) ? self::$strictMode : ((self::$strictMode & $severity) === $severity))
323: ) {
324: $e = new ErrorException($message, 0, $severity, $file, $line);
325: $e->context = $context;
326: $e->skippable = TRUE;
327: self::exceptionHandler($e);
328: }
329:
330: $message = 'PHP ' . Helpers::errorTypeToString($severity) . ": $message";
331: $count = & self::getBar()->getPanel('Tracy:errors')->data["$file|$line|$message"];
332:
333: if ($count++) {
334: return NULL;
335:
336: } elseif (self::$productionMode) {
337: try {
338: self::log("$message in $file:$line", self::ERROR);
339: } catch (\Exception $foo) {
340: }
341: return NULL;
342:
343: } else {
344: self::fireLog(new ErrorException($message, 0, $severity, $file, $line));
345: return self::isHtmlMode() ? NULL : FALSE;
346: }
347: }
348:
349:
350: private static function isHtmlMode()
351: {
352: return empty($_SERVER['HTTP_X_REQUESTED_WITH'])
353: && PHP_SAPI !== 'cli'
354: && !preg_match('#^Content-Type: (?!text/html)#im', implode("\n", headers_list()));
355: }
356:
357:
358:
359:
360:
361: 362: 363:
364: public static function getBlueScreen()
365: {
366: if (!self::$blueScreen) {
367: self::$blueScreen = new BlueScreen;
368: self::$blueScreen->info = array(
369: 'PHP ' . PHP_VERSION,
370: isset($_SERVER['SERVER_SOFTWARE']) ? $_SERVER['SERVER_SOFTWARE'] : NULL,
371: 'Tracy ' . self::VERSION,
372: );
373: }
374: return self::$blueScreen;
375: }
376:
377:
378: 379: 380:
381: public static function getBar()
382: {
383: if (!self::$bar) {
384: self::$bar = new Bar;
385: self::$bar->addPanel(new DefaultBarPanel('info'), 'Tracy:info');
386: self::$bar->addPanel(new DefaultBarPanel('errors'), 'Tracy:errors');
387: }
388: return self::$bar;
389: }
390:
391:
392: 393: 394:
395: public static function setLogger(ILogger $logger)
396: {
397: self::$logger = $logger;
398: }
399:
400:
401: 402: 403:
404: public static function getLogger()
405: {
406: if (!self::$logger) {
407: self::$logger = new Logger(self::$logDirectory, self::$email, self::getBlueScreen());
408: self::$logger->directory = & self::$logDirectory;
409: self::$logger->email = & self::$email;
410: }
411: return self::$logger;
412: }
413:
414:
415: 416: 417:
418: public static function getFireLogger()
419: {
420: if (!self::$fireLogger) {
421: self::$fireLogger = new FireLogger;
422: }
423: return self::$fireLogger;
424: }
425:
426:
427:
428:
429:
430: 431: 432: 433: 434: 435: 436:
437: public static function dump($var, $return = FALSE)
438: {
439: if ($return) {
440: ob_start();
441: Dumper::dump($var, array(
442: Dumper::DEPTH => self::$maxDepth,
443: Dumper::TRUNCATE => self::$maxLen,
444: ));
445: return ob_get_clean();
446:
447: } elseif (!self::$productionMode) {
448: Dumper::dump($var, array(
449: Dumper::DEPTH => self::$maxDepth,
450: Dumper::TRUNCATE => self::$maxLen,
451: Dumper::LOCATION => self::$showLocation,
452: ));
453: }
454:
455: return $var;
456: }
457:
458:
459: 460: 461: 462: 463:
464: public static function timer($name = NULL)
465: {
466: static $time = array();
467: $now = microtime(TRUE);
468: $delta = isset($time[$name]) ? $now - $time[$name] : 0;
469: $time[$name] = $now;
470: return $delta;
471: }
472:
473:
474: 475: 476: 477: 478: 479: 480: 481:
482: public static function barDump($var, $title = NULL, array $options = NULL)
483: {
484: if (!self::$productionMode) {
485: static $panel;
486: if (!$panel) {
487: self::getBar()->addPanel($panel = new DefaultBarPanel('dumps'));
488: }
489: $panel->data[] = array('title' => $title, 'dump' => Dumper::toHtml($var, (array) $options + array(
490: Dumper::DEPTH => self::$maxDepth,
491: Dumper::TRUNCATE => self::$maxLen,
492: Dumper::LOCATION => self::$showLocation,
493: )));
494: }
495: return $var;
496: }
497:
498:
499: 500: 501: 502: 503:
504: public static function log($message, $priority = ILogger::INFO)
505: {
506: return self::getLogger()->log($message, $priority);
507: }
508:
509:
510: 511: 512: 513: 514:
515: public static function fireLog($message)
516: {
517: if (!self::$productionMode) {
518: return self::getFireLogger()->log($message);
519: }
520: }
521:
522:
523: 524: 525: 526: 527:
528: public static function detectDebugMode($list = NULL)
529: {
530: $addr = isset($_SERVER['REMOTE_ADDR'])
531: ? $_SERVER['REMOTE_ADDR']
532: : php_uname('n');
533: $secret = isset($_COOKIE[self::COOKIE_SECRET]) && is_string($_COOKIE[self::COOKIE_SECRET])
534: ? $_COOKIE[self::COOKIE_SECRET]
535: : NULL;
536: $list = is_string($list)
537: ? preg_split('#[,\s]+#', $list)
538: : (array) $list;
539: if (!isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
540: $list[] = '127.0.0.1';
541: $list[] = '::1';
542: }
543: return in_array($addr, $list, TRUE) || in_array("$secret@$addr", $list, TRUE);
544: }
545:
546: }
547: