Namespaces

  • Latte
    • Loaders
    • Macros
    • Runtime
  • Nette
    • Application
      • Responses
      • Routers
      • UI
    • Bridges
      • ApplicationDI
      • ApplicationLatte
      • ApplicationTracy
      • CacheDI
      • CacheLatte
      • DatabaseDI
      • DatabaseTracy
      • DITracy
      • FormsDI
      • FormsLatte
      • Framework
      • HttpDI
      • HttpTracy
      • MailDI
      • ReflectionDI
      • SecurityDI
      • SecurityTracy
    • Caching
      • Storages
    • ComponentModel
    • Database
      • Conventions
      • Drivers
      • Reflection
      • Table
    • DI
      • Config
        • Adapters
      • Extensions
    • Diagnostics
    • Forms
      • Controls
      • Rendering
    • Http
    • Iterators
    • Latte
    • Loaders
    • Localization
    • Mail
    • Neon
    • PhpGenerator
    • Reflection
    • Security
    • Templating
    • Utils
  • NetteModule
  • none
  • Tracy
    • Bridges
      • Nette

Classes

  • Bar
  • BlueScreen
  • Debugger
  • Dumper
  • FireLogger
  • Helpers
  • Logger
  • OutputDebugger

Interfaces

  • IBarPanel
  • ILogger
  • Overview
  • Namespace
  • Class
  • Tree
  • Deprecated
  1: <?php
  2: 
  3: /**
  4:  * This file is part of the Tracy (https://tracy.nette.org)
  5:  * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
  6:  */
  7: 
  8: namespace Tracy;
  9: 
 10: use Tracy;
 11: use ErrorException;
 12: 
 13: 
 14: /**
 15:  * Debugger: displays and logs errors.
 16:  */
 17: class Debugger
 18: {
 19:     const VERSION = '2.3.7';
 20: 
 21:     /** server modes {@link Debugger::enable()} */
 22:     const
 23:         DEVELOPMENT = FALSE,
 24:         PRODUCTION = TRUE,
 25:         DETECT = NULL;
 26: 
 27:     const COOKIE_SECRET = 'tracy-debug';
 28: 
 29:     /** @deprecated */
 30:     public static $version = self::VERSION;
 31: 
 32:     /** @var bool in production mode is suppressed any debugging output */
 33:     public static $productionMode = self::DETECT;
 34: 
 35:     /** @var bool {@link Debugger::enable()} */
 36:     private static $enabled = FALSE;
 37: 
 38:     /** @var string reserved memory; also prevents double rendering */
 39:     private static $reserved;
 40: 
 41:     /********************* errors and exceptions reporting ****************d*g**/
 42: 
 43:     /** @var bool|int determines whether any error will cause immediate death; if integer that it's matched against error severity */
 44:     public static $strictMode = FALSE;
 45: 
 46:     /** @var bool disables the @ (shut-up) operator so that notices and warnings are no longer hidden */
 47:     public static $scream = FALSE;
 48: 
 49:     /** @var array of callables specifies the functions that are automatically called after fatal error */
 50:     public static $onFatalError = array();
 51: 
 52:     /********************* Debugger::dump() ****************d*g**/
 53: 
 54:     /** @var int  how many nested levels of array/object properties display {@link Debugger::dump()} */
 55:     public static $maxDepth = 3;
 56: 
 57:     /** @var int  how long strings display {@link Debugger::dump()} */
 58:     public static $maxLen = 150;
 59: 
 60:     /** @var bool display location? {@link Debugger::dump()} */
 61:     public static $showLocation = FALSE;
 62: 
 63:     /********************* logging ****************d*g**/
 64: 
 65:     /** @var string name of the directory where errors should be logged */
 66:     public static $logDirectory;
 67: 
 68:     /** @var int  log bluescreen in production mode for this error severity */
 69:     public static $logSeverity = 0;
 70: 
 71:     /** @var string|array email(s) to which send error notifications */
 72:     public static $email;
 73: 
 74:     /** {@link Debugger::log()} and {@link Debugger::fireLog()} */
 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:     /********************* misc ****************d*g**/
 84: 
 85:     /** @var int timestamp with microseconds of the start of the request */
 86:     public static $time;
 87: 
 88:     /** @deprecated */
 89:     public static $source;
 90: 
 91:     /** @var string URI pattern mask to open editor */
 92:     public static $editor = 'editor://open/?file=%file&line=%line';
 93: 
 94:     /** @var string command to open browser (use 'start ""' in Windows) */
 95:     public static $browser;
 96: 
 97:     /** @var string custom static error template */
 98:     public static $errorTemplate;
 99: 
100:     /********************* services ****************d*g**/
101: 
102:     /** @var BlueScreen */
103:     private static $blueScreen;
104: 
105:     /** @var Bar */
106:     private static $bar;
107: 
108:     /** @var ILogger */
109:     private static $logger;
110: 
111:     /** @var ILogger */
112:     private static $fireLogger;
113: 
114: 
115:     /**
116:      * Static class - cannot be instantiated.
117:      */
118:     final public function __construct()
119:     {
120:         throw new \LogicException;
121:     }
122: 
123: 
124:     /**
125:      * Enables displaying or logging errors and exceptions.
126:      * @param  mixed   production, development mode, autodetection or IP address(es) whitelist.
127:      * @param  string  error log directory
128:      * @param  string  administrator email; enables email sending in production mode
129:      * @return void
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:         // logging configuration
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:         // php configuration
156:         if (function_exists('ini_set')) {
157:             ini_set('display_errors', !self::$productionMode); // or 'stderr'
158:             ini_set('html_errors', FALSE);
159:             ini_set('log_errors', FALSE);
160: 
161:         } elseif (ini_get('display_errors') != !self::$productionMode // intentionally ==
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:      * @return bool
182:      */
183:     public static function isEnabled()
184:     {
185:         return self::$enabled;
186:     }
187: 
188: 
189:     /**
190:      * Shutdown handler to catch fatal errors and execute of the planned activities.
191:      * @return void
192:      * @internal
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:      * Handler to catch uncaught exception.
215:      * @param  \Exception|\Throwable
216:      * @return void
217:      * @internal
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:      * Handler to catch warnings and notices.
301:      * @return bool   FALSE to call normal error handler, NULL otherwise
302:      * @throws ErrorException
303:      * @internal
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; // calls normal error handler to fill-in error_get_last()
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(); // @ may not exist or is not removable
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++) { // repeated error
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; // FALSE calls normal error handler
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:     /********************* services ****************d*g**/
379: 
380: 
381:     /**
382:      * @return BlueScreen
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:      * @return Bar
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'); // filled by errorHandler()
407:         }
408:         return self::$bar;
409:     }
410: 
411: 
412:     /**
413:      * @return void
414:      */
415:     public static function setLogger(ILogger $logger)
416:     {
417:         self::$logger = $logger;
418:     }
419: 
420: 
421:     /**
422:      * @return ILogger
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; // back compatiblity
429:             self::$logger->email = & self::$email;
430:         }
431:         return self::$logger;
432:     }
433: 
434: 
435:     /**
436:      * @return ILogger
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:     /********************* useful tools ****************d*g**/
448: 
449: 
450:     /**
451:      * Dumps information about a variable in readable format.
452:      * @tracySkipLocation
453:      * @param  mixed  variable to dump
454:      * @param  bool   return output instead of printing it? (bypasses $productionMode)
455:      * @return mixed  variable itself or dump
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:      * Starts/stops stopwatch.
481:      * @param  string  name
482:      * @return float   elapsed seconds
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:      * Dumps information about a variable in Tracy Debug Bar.
496:      * @tracySkipLocation
497:      * @param  mixed  variable to dump
498:      * @param  string optional title
499:      * @param  array  dumper options
500:      * @return mixed  variable itself
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:      * Logs message or exception.
521:      * @param  string|\Exception|\Throwable
522:      * @return mixed
523:      */
524:     public static function log($message, $priority = ILogger::INFO)
525:     {
526:         return self::getLogger()->log($message, $priority);
527:     }
528: 
529: 
530:     /**
531:      * Sends message to FireLogger console.
532:      * @param  mixed   message to log
533:      * @return bool    was successful?
534:      */
535:     public static function fireLog($message)
536:     {
537:         if (!self::$productionMode) {
538:             return self::getFireLogger()->log($message);
539:         }
540:     }
541: 
542: 
543:     /**
544:      * Detects debug mode by IP address.
545:      * @param  string|array  IP addresses or computer names whitelist detection
546:      * @return bool
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: 
Nette 2.3.8 API API documentation generated by ApiGen 2.8.0