1: <?php
2:
3: 4: 5: 6: 7: 8: 9: 10: 11:
12:
13:
14:
15: 16: 17: 18: 19: 20: 21: 22: 23: 24:
25: final class Debugger
26: {
27:
28: public static $productionMode;
29:
30:
31: public static $consoleMode;
32:
33:
34: public static $time;
35:
36:
37: private static $ajaxDetected;
38:
39:
40: public static $source;
41:
42:
43: public static $editor = 'editor://open/?file=%file&line=%line';
44:
45:
46: public static $browser;
47:
48:
49:
50:
51: public static $maxDepth = 3;
52:
53:
54: public static $maxLen = 150;
55:
56:
57: public static $showLocation = FALSE;
58:
59:
60:
61:
62: const DEVELOPMENT = FALSE,
63: PRODUCTION = TRUE,
64: DETECT = NULL;
65:
66:
67: public static $blueScreen;
68:
69:
70: public static $strictMode = FALSE;
71:
72:
73: public static $scream = FALSE;
74:
75:
76: public static $onFatalError = array();
77:
78:
79: private static $enabled = FALSE;
80:
81:
82: private static $lastError = FALSE;
83:
84:
85:
86:
87: public static $logger;
88:
89:
90: public static $fireLogger;
91:
92:
93: public static $logDirectory;
94:
95:
96: public static $email;
97:
98:
99: public static $mailer;
100:
101:
102: public static $emailSnooze;
103:
104:
105:
106:
107: public static $bar;
108:
109:
110: private static $errorPanel;
111:
112:
113: private static $dumpPanel;
114:
115:
116:
117:
118: const DEBUG = 'debug',
119: INFO = 'info',
120: WARNING = 'warning',
121: ERROR = 'error',
122: CRITICAL = 'critical';
123:
124:
125:
126: 127: 128:
129: final public function __construct()
130: {
131: throw new StaticClassException;
132: }
133:
134:
135:
136: 137: 138: 139:
140: public static function _init()
141: {
142: self::$time = microtime(TRUE);
143: self::$consoleMode = PHP_SAPI === 'cli';
144: self::$productionMode = self::DETECT;
145: if (self::$consoleMode) {
146: self::$source = empty($_SERVER['argv']) ? 'cli' : 'cli: ' . implode(' ', $_SERVER['argv']);
147: } else {
148: self::$ajaxDetected = isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest';
149: if (isset($_SERVER['REQUEST_URI'])) {
150: self::$source = (isset($_SERVER['HTTPS']) && strcasecmp($_SERVER['HTTPS'], 'off') ? 'https://' : 'http://')
151: . (isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : (isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : ''))
152: . $_SERVER['REQUEST_URI'];
153: }
154: }
155:
156: self::$logger = new Logger;
157: self::$logDirectory = & self::$logger->directory;
158: self::$email = & self::$logger->email;
159: self::$mailer = & self::$logger->mailer;
160: self::$emailSnooze = & Logger::$emailSnooze;
161:
162: self::$fireLogger = new FireLogger;
163:
164: self::$blueScreen = new DebugBlueScreen;
165: self::$blueScreen->addPanel(create_function('$e', '
166: if ($e instanceof TemplateException) {
167: return array(
168: \'tab\' => \'Template\',
169: \'panel\' => \'<p><b>File:</b> \' . DebugHelpers::editorLink($e->sourceFile, $e->sourceLine)
170: . \' <b>Line:</b> \' . ($e->sourceLine ? $e->sourceLine : \'n/a\') . \'</p>\'
171: . ($e->sourceLine ? DebugBlueScreen::highlightFile($e->sourceFile, $e->sourceLine) : \'\')
172: );
173: } elseif ($e instanceof NeonException && preg_match(\'#line (\\d+)#\', $e->getMessage(), $m)) {
174: if ($item = DebugHelpers::findTrace($e->getTrace(), \'ConfigNeonAdapter::load\')) {
175: return array(
176: \'tab\' => \'NEON\',
177: \'panel\' => \'<p><b>File:</b> \' . DebugHelpers::editorLink($item[\'args\'][0], $m[1]) . \' <b>Line:</b> \' . $m[1] . \'</p>\'
178: . DebugBlueScreen::highlightFile($item[\'args\'][0], $m[1])
179: );
180: } elseif ($item = DebugHelpers::findTrace($e->getTrace(), \'Neon::decode\')) {
181: return array(
182: \'tab\' => \'NEON\',
183: \'panel\' => DebugBlueScreen::highlightPhp($item[\'args\'][0], $m[1])
184: );
185: }
186: }
187: '));
188:
189: self::$bar = new DebugBar;
190: self::$bar->addPanel(new DefaultBarPanel('time'));
191: self::$bar->addPanel(new DefaultBarPanel('memory'));
192: self::$bar->addPanel(self::$errorPanel = new DefaultBarPanel('errors'));
193: self::$bar->addPanel(self::$dumpPanel = new DefaultBarPanel('dumps'));
194: }
195:
196:
197:
198:
199:
200:
201:
202: 203: 204: 205: 206: 207: 208:
209: public static function enable($mode = NULL, $logDirectory = NULL, $email = NULL)
210: {
211: error_reporting(E_ALL | E_STRICT);
212:
213:
214: if (is_bool($mode)) {
215: self::$productionMode = $mode;
216:
217: } elseif ($mode !== self::DETECT || self::$productionMode === NULL) {
218: $mode = is_string($mode) ? preg_split('#[,\s]+#', $mode) : (array) $mode;
219: $mode[] = '127.0.0.1';
220: $mode[] = '::1';
221: self::$productionMode = !in_array(isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : php_uname('n'), $mode, TRUE);
222: }
223:
224:
225: if (is_string($logDirectory)) {
226: self::$logDirectory = realpath($logDirectory);
227: if (self::$logDirectory === FALSE) {
228: throw new DirectoryNotFoundException("Directory '$logDirectory' is not found.");
229: }
230: } elseif ($logDirectory === FALSE) {
231: self::$logDirectory = FALSE;
232:
233: } elseif (self::$logDirectory === NULL) {
234: self::$logDirectory = defined('APP_DIR') ? APP_DIR . '/../log' : getcwd() . '/log';
235: }
236: if (self::$logDirectory) {
237: ini_set('error_log', self::$logDirectory . '/php_error.log');
238: }
239:
240:
241: if (function_exists('ini_set')) {
242: ini_set('display_errors', !self::$productionMode);
243: ini_set('html_errors', FALSE);
244: ini_set('log_errors', FALSE);
245:
246: } elseif (ini_get('display_errors') != !self::$productionMode && ini_get('display_errors') !== (self::$productionMode ? 'stderr' : 'stdout')) {
247: throw new NotSupportedException('Function ini_set() must be enabled.');
248: }
249:
250: if ($email) {
251: if (!is_string($email)) {
252: throw new InvalidArgumentException('Email address must be a string.');
253: }
254: self::$email = $email;
255: }
256:
257: if (!defined('E_DEPRECATED')) {
258: define('E_DEPRECATED', 8192);
259: }
260:
261: if (!defined('E_USER_WARNING')) {
262: define('E_USER_WARNING', 16384);
263: }
264:
265: if (!self::$enabled) {
266: register_shutdown_function(array(__CLASS__, '_shutdownHandler'));
267: set_exception_handler(array(__CLASS__, '_exceptionHandler'));
268: set_error_handler(array(__CLASS__, '_errorHandler'));
269: self::$enabled = TRUE;
270: }
271: }
272:
273:
274:
275: 276: 277: 278:
279: public static function isEnabled()
280: {
281: return self::$enabled;
282: }
283:
284:
285:
286: 287: 288: 289: 290: 291:
292: public static function log($message, $priority = self::INFO)
293: {
294: if (self::$logDirectory === FALSE) {
295: return;
296:
297: } elseif (!self::$logDirectory) {
298: throw new InvalidStateException('Logging directory is not specified in Debugger::$logDirectory.');
299: }
300:
301: if ($message instanceof Exception) {
302: $exception = $message;
303: $message = ($message instanceof FatalErrorException
304: ? 'Fatal error: ' . $exception->getMessage()
305: : get_class($exception) . ": " . $exception->getMessage())
306: . " in " . $exception->getFile() . ":" . $exception->getLine();
307:
308: $hash = md5($exception . (method_exists($exception, 'getPrevious') ? $exception->getPrevious() : (isset($exception->previous) ? $exception->previous : '')));
309: $exceptionFilename = "exception-" . @date('Y-m-d-H-i-s') . "-$hash.html";
310: foreach (new DirectoryIterator(self::$logDirectory) as $entry) {
311: if (strpos($entry, $hash)) {
312: $exceptionFilename = $entry;
313: $saved = TRUE;
314: break;
315: }
316: }
317: }
318:
319: self::$logger->log(array(
320: @date('[Y-m-d H-i-s]'),
321: $message,
322: self::$source ? ' @ ' . self::$source : NULL,
323: !empty($exceptionFilename) ? ' @@ ' . $exceptionFilename : NULL
324: ), $priority);
325:
326: if (!empty($exceptionFilename)) {
327: $exceptionFilename = self::$logDirectory . '/' . $exceptionFilename;
328: if (empty($saved) && $logHandle = @fopen($exceptionFilename, 'w')) {
329: ob_start();
330: ob_start(create_function('$buffer', 'extract(NCFix::$vars['.NCFix::uses(array('logHandle'=>$logHandle)).'], EXTR_REFS); fwrite($logHandle, $buffer); '), 4096);
331: self::$blueScreen->render($exception);
332: ob_end_flush();
333: ob_end_clean();
334: fclose($logHandle);
335: }
336: return strtr($exceptionFilename, '\\/', DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR);
337: }
338: }
339:
340:
341:
342: 343: 344: 345: 346:
347: public static function _shutdownHandler()
348: {
349: if (!self::$enabled) {
350: return;
351: }
352:
353:
354: static $types = array(
355: E_ERROR => 1,
356: E_CORE_ERROR => 1,
357: E_COMPILE_ERROR => 1,
358: E_PARSE => 1,
359: );
360: $error = error_get_last();
361: if (isset($types[$error['type']])) {
362: self::_exceptionHandler(new FatalErrorException($error['message'], 0, $error['type'], $error['file'], $error['line'], NULL));
363: }
364:
365:
366: if (self::$bar && !self::$productionMode && self::isHtmlMode()) {
367: self::$bar->render();
368: }
369: }
370:
371:
372:
373: 374: 375: 376: 377: 378:
379: public static function _exceptionHandler(Exception $exception)
380: {
381: if (!headers_sent()) {
382: $protocol = isset($_SERVER['SERVER_PROTOCOL']) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.1';
383: header($protocol . ' 500', TRUE, 500);
384: }
385:
386: try {
387: if (self::$productionMode) {
388: try {
389: self::log($exception, self::ERROR);
390: } catch (Exception $e) {
391: echo 'FATAL ERROR: unable to log error';
392: }
393:
394: if (self::$consoleMode) {
395: echo "ERROR: the server encountered an internal error and was unable to complete your request.\n";
396:
397: } elseif (self::isHtmlMode()) {
398: require dirname(__FILE__) . '/templates/error.phtml';
399: }
400:
401: } else {
402: if (self::$consoleMode) {
403: echo "$exception\n";
404: if ($file = self::log($exception)) {
405: echo "(stored in $file)\n";
406: if (self::$browser) {
407: exec(self::$browser . ' ' . escapeshellarg($file));
408: }
409: }
410:
411: } elseif (self::isHtmlMode()) {
412: self::$blueScreen->render($exception);
413: if (self::$bar) {
414: self::$bar->render();
415: }
416:
417: } elseif (!self::fireLog($exception, self::ERROR)) {
418: $file = self::log($exception);
419: if (!headers_sent()) {
420: header("X-Nette-Error-Log: $file");
421: }
422: }
423: }
424:
425: foreach (self::$onFatalError as $handler) {
426: call_user_func($handler, $exception);
427: }
428:
429: } catch (Exception $e) {
430: if (self::$productionMode) {
431: echo self::isHtmlMode() ? '<meta name=robots content=noindex>FATAL ERROR' : 'FATAL ERROR';
432: } else {
433: echo "FATAL ERROR: thrown ", get_class($e), ': ', $e->getMessage(),
434: "\nwhile processing ", get_class($exception), ': ', $exception->getMessage(), "\n";
435: }
436: }
437:
438: self::$enabled = FALSE;
439: exit(255);
440: }
441:
442:
443:
444: 445: 446: 447: 448: 449: 450: 451: 452: 453: 454:
455: public static function _errorHandler($severity, $message, $file, $line, $context)
456: {
457: if (self::$scream) {
458: error_reporting(E_ALL | E_STRICT);
459: }
460:
461: if (self::$lastError !== FALSE && ($severity & error_reporting()) === $severity) {
462: self::$lastError = new ErrorException($message, 0, $severity, $file, $line);
463: return NULL;
464: }
465:
466: if ($severity === E_RECOVERABLE_ERROR || $severity === E_USER_ERROR) {
467: throw new FatalErrorException($message, 0, $severity, $file, $line, $context);
468:
469: } elseif (($severity & error_reporting()) !== $severity) {
470: return FALSE;
471:
472: } elseif (!self::$productionMode && (is_bool(self::$strictMode) ? self::$strictMode : ((self::$strictMode & $severity) === $severity))) {
473: self::_exceptionHandler(new FatalErrorException($message, 0, $severity, $file, $line, $context));
474: }
475:
476: static $types = array(
477: E_WARNING => 'Warning',
478: E_COMPILE_WARNING => 'Warning',
479: E_USER_WARNING => 'Warning',
480: E_NOTICE => 'Notice',
481: E_USER_NOTICE => 'Notice',
482: E_STRICT => 'Strict standards',
483: E_DEPRECATED => 'Deprecated',
484: E_USER_WARNING => 'Deprecated',
485: );
486:
487: $message = 'PHP ' . (isset($types[$severity]) ? $types[$severity] : 'Unknown error') . ": $message";
488: $count = & self::$errorPanel->data["$message|$file|$line"];
489:
490: if ($count++) {
491: return NULL;
492:
493: } elseif (self::$productionMode) {
494: self::log("$message in $file:$line", self::ERROR);
495: return NULL;
496:
497: } else {
498: $ok = self::fireLog(new ErrorException($message, 0, $severity, $file, $line), self::WARNING);
499: return !self::isHtmlMode() || (!self::$bar && !$ok) ? FALSE : NULL;
500: }
501:
502: return FALSE;
503: }
504:
505:
506:
507: 508: 509: 510: 511:
512: public static function toStringException(Exception $exception)
513: {
514: if (self::$enabled) {
515: self::_exceptionHandler($exception);
516: } else {
517: trigger_error($exception->getMessage(), E_USER_ERROR);
518: }
519: }
520:
521:
522:
523: 524: 525: 526:
527: public static function tryError()
528: {
529: if (!self::$enabled && self::$lastError === FALSE) {
530: set_error_handler(array(__CLASS__, '_errorHandler'));
531: }
532: self::$lastError = NULL;
533: }
534:
535:
536:
537: 538: 539: 540: 541:
542: public static function catchError(& $error)
543: {
544: if (!self::$enabled && self::$lastError !== FALSE) {
545: restore_error_handler();
546: }
547: $error = self::$lastError;
548: self::$lastError = FALSE;
549: return (bool) $error;
550: }
551:
552:
553:
554:
555:
556:
557:
558: 559: 560: 561: 562: 563:
564: public static function dump($var, $return = FALSE)
565: {
566: if (!$return && self::$productionMode) {
567: return $var;
568: }
569:
570: $output = "<pre class=\"nette-dump\">" . DebugHelpers::htmlDump($var) . "</pre>\n";
571:
572: if (!$return) {
573: $trace = PHP_VERSION_ID < 50205 ? debug_backtrace() :debug_backtrace(FALSE);
574: $i = DebugHelpers::findTrace($trace, 'dump') ? 1 : 0;
575: if (isset($trace[$i]['file'], $trace[$i]['line']) && is_file($trace[$i]['file'])) {
576: $lines = file($trace[$i]['file']);
577: preg_match('#dump\((.*)\)#', $lines[$trace[$i]['line'] - 1], $m);
578: $output = substr_replace(
579: $output,
580: ' title="' . htmlspecialchars((isset($m[0]) ? "$m[0] \n" : '') . "in file {$trace[$i]['file']} on line {$trace[$i]['line']}") . '"',
581: 4, 0);
582:
583: if (self::$showLocation) {
584: $output = substr_replace(
585: $output,
586: ' <small>in ' . DebugHelpers::editorLink($trace[$i]['file'], $trace[$i]['line']) . ":{$trace[$i]['line']}</small>",
587: -8, 0);
588: }
589: }
590: }
591:
592: if (self::$consoleMode) {
593: $output = htmlspecialchars_decode(strip_tags($output), ENT_QUOTES);
594: }
595:
596: if ($return) {
597: return $output;
598:
599: } else {
600: echo $output;
601: return $var;
602: }
603: }
604:
605:
606:
607: 608: 609: 610: 611:
612: public static function timer($name = NULL)
613: {
614: static $time = array();
615: $now = microtime(TRUE);
616: $delta = isset($time[$name]) ? $now - $time[$name] : 0;
617: $time[$name] = $now;
618: return $delta;
619: }
620:
621:
622:
623: 624: 625: 626: 627: 628:
629: public static function barDump($var, $title = NULL)
630: {
631: if (!self::$productionMode) {
632: $dump = array();
633: foreach ((is_array($var) ? $var : array('' => $var)) as $key => $val) {
634: $dump[$key] = DebugHelpers::clickableDump($val);
635: }
636: self::$dumpPanel->data[] = array('title' => $title, 'dump' => $dump);
637: }
638: return $var;
639: }
640:
641:
642:
643: 644: 645: 646: 647:
648: public static function fireLog($message)
649: {
650: if (!self::$productionMode) {
651: return self::$fireLogger->log($message);
652: }
653: }
654:
655:
656:
657: private static function isHtmlMode()
658: {
659: return !self::$ajaxDetected && !self::$consoleMode
660: && !preg_match('#^Content-Type: (?!text/html)#im', implode("\n", headers_list()));
661: }
662:
663:
664:
665:
666: public static function addPanel(IBarPanel $panel, $id = NULL)
667: {
668: return self::$bar->addPanel($panel, $id);
669: }
670:
671: }
672: