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
      • Table
    • DI
      • Config
        • Adapters
      • Extensions
    • Forms
      • Controls
      • Rendering
    • Http
    • Iterators
    • Loaders
    • Localization
    • Mail
    • Neon
    • PhpGenerator
    • Reflection
    • Security
    • Utils
  • 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.4.3';
 20: 
 21:     /** server modes for Debugger::enable() */
 22:     const
 23:         DEVELOPMENT = FALSE,
 24:         PRODUCTION = TRUE,
 25:         DETECT = NULL;
 26: 
 27:     const COOKIE_SECRET = 'tracy-debug';
 28: 
 29:     /** @var bool in production mode is suppressed any debugging output */
 30:     public static $productionMode = self::DETECT;
 31: 
 32:     /** @var bool whether to display debug bar in development mode */
 33:     public static $showBar = TRUE;
 34: 
 35:     /** @var bool */
 36:     private static $enabled = FALSE;
 37: 
 38:     /** @var string reserved memory; also prevents double rendering */
 39:     private static $reserved;
 40: 
 41:     /** @var int initial output buffer level */
 42:     private static $obLevel;
 43: 
 44:     /********************* errors and exceptions reporting ****************d*g**/
 45: 
 46:     /** @var bool|int determines whether any error will cause immediate death in development mode; if integer that it's matched against error severity */
 47:     public static $strictMode = FALSE;
 48: 
 49:     /** @var bool disables the @ (shut-up) operator so that notices and warnings are no longer hidden */
 50:     public static $scream = FALSE;
 51: 
 52:     /** @var array of callables specifies the functions that are automatically called after fatal error */
 53:     public static $onFatalError = [];
 54: 
 55:     /********************* Debugger::dump() ****************d*g**/
 56: 
 57:     /** @var int  how many nested levels of array/object properties display by dump() */
 58:     public static $maxDepth = 3;
 59: 
 60:     /** @var int  how long strings display by dump() */
 61:     public static $maxLength = 150;
 62: 
 63:     /** @var bool display location by dump()? */
 64:     public static $showLocation = FALSE;
 65: 
 66:     /** @deprecated */
 67:     public static $maxLen = 150;
 68: 
 69:     /********************* logging ****************d*g**/
 70: 
 71:     /** @var string name of the directory where errors should be logged */
 72:     public static $logDirectory;
 73: 
 74:     /** @var int  log bluescreen in production mode for this error severity */
 75:     public static $logSeverity = 0;
 76: 
 77:     /** @var string|array email(s) to which send error notifications */
 78:     public static $email;
 79: 
 80:     /** for Debugger::log() and Debugger::fireLog() */
 81:     const
 82:         DEBUG = ILogger::DEBUG,
 83:         INFO = ILogger::INFO,
 84:         WARNING = ILogger::WARNING,
 85:         ERROR = ILogger::ERROR,
 86:         EXCEPTION = ILogger::EXCEPTION,
 87:         CRITICAL = ILogger::CRITICAL;
 88: 
 89:     /********************* misc ****************d*g**/
 90: 
 91:     /** @var int timestamp with microseconds of the start of the request */
 92:     public static $time;
 93: 
 94:     /** @var string URI pattern mask to open editor */
 95:     public static $editor = 'editor://open/?file=%file&line=%line';
 96: 
 97:     /** @var array replacements in path */
 98:     public static $editorMapping = [];
 99: 
100:     /** @var string command to open browser (use 'start ""' in Windows) */
101:     public static $browser;
102: 
103:     /** @var string custom static error template */
104:     public static $errorTemplate;
105: 
106:     /** @var array */
107:     private static $cpuUsage;
108: 
109:     /********************* services ****************d*g**/
110: 
111:     /** @var BlueScreen */
112:     private static $blueScreen;
113: 
114:     /** @var Bar */
115:     private static $bar;
116: 
117:     /** @var ILogger */
118:     private static $logger;
119: 
120:     /** @var ILogger */
121:     private static $fireLogger;
122: 
123: 
124:     /**
125:      * Static class - cannot be instantiated.
126:      */
127:     final public function __construct()
128:     {
129:         throw new \LogicException;
130:     }
131: 
132: 
133:     /**
134:      * Enables displaying or logging errors and exceptions.
135:      * @param  mixed   production, development mode, autodetection or IP address(es) whitelist.
136:      * @param  string  error log directory
137:      * @param  string  administrator email; enables email sending in production mode
138:      * @return void
139:      */
140:     public static function enable($mode = NULL, $logDirectory = NULL, $email = NULL)
141:     {
142:         if ($mode !== NULL || self::$productionMode === NULL) {
143:             self::$productionMode = is_bool($mode) ? $mode : !self::detectDebugMode($mode);
144:         }
145: 
146:         self::$maxLen = & self::$maxLength;
147:         self::$reserved = str_repeat('t', 3e5);
148:         self::$time = isset($_SERVER['REQUEST_TIME_FLOAT']) ? $_SERVER['REQUEST_TIME_FLOAT'] : microtime(TRUE);
149:         self::$obLevel = ob_get_level();
150:         self::$cpuUsage = !self::$productionMode && function_exists('getrusage') ? getrusage() : NULL;
151: 
152:         // logging configuration
153:         if ($email !== NULL) {
154:             self::$email = $email;
155:         }
156:         if ($logDirectory !== NULL) {
157:             self::$logDirectory = $logDirectory;
158:         }
159:         if (self::$logDirectory) {
160:             if (!is_dir(self::$logDirectory) || !preg_match('#([a-z]+:)?[/\\\\]#Ai', self::$logDirectory)) {
161:                 self::$logDirectory = NULL;
162:                 self::exceptionHandler(new \RuntimeException('Logging directory not found or is not absolute path.'));
163:             }
164:         }
165: 
166:         // php configuration
167:         if (function_exists('ini_set')) {
168:             ini_set('display_errors', !self::$productionMode); // or 'stderr'
169:             ini_set('html_errors', FALSE);
170:             ini_set('log_errors', FALSE);
171: 
172:         } elseif (ini_get('display_errors') != !self::$productionMode // intentionally ==
173:             && ini_get('display_errors') !== (self::$productionMode ? 'stderr' : 'stdout')
174:         ) {
175:             self::exceptionHandler(new \RuntimeException("Unable to set 'display_errors' because function ini_set() is disabled."));
176:         }
177:         error_reporting(E_ALL);
178: 
179:         if (self::$enabled) {
180:             return;
181:         }
182:         self::$enabled = TRUE;
183: 
184:         register_shutdown_function([__CLASS__, 'shutdownHandler']);
185:         set_exception_handler([__CLASS__, 'exceptionHandler']);
186:         set_error_handler([__CLASS__, 'errorHandler']);
187: 
188:         array_map('class_exists', ['Tracy\Bar', 'Tracy\BlueScreen', 'Tracy\DefaultBarPanel', 'Tracy\Dumper',
189:             'Tracy\FireLogger', 'Tracy\Helpers', 'Tracy\Logger']);
190: 
191:         if (self::$productionMode) {
192: 
193:         } elseif (headers_sent($file, $line) || ob_get_length()) {
194:             throw new \LogicException(
195:                 __METHOD__ . '() called after some output has been sent. '
196:                 . ($file ? "Output started at $file:$line." : 'Try Tracy\OutputDebugger to find where output started.')
197:             );
198: 
199:         } elseif (self::getBar()->dispatchAssets()) {
200:             exit;
201: 
202:         } elseif (session_status() === PHP_SESSION_ACTIVE) {
203:             self::dispatch();
204:         }
205:     }
206: 
207: 
208:     /**
209:      * @return void
210:      */
211:     public static function dispatch()
212:     {
213:         if (self::$productionMode) {
214:             return;
215: 
216:         } elseif (headers_sent($file, $line) || ob_get_length()) {
217:             throw new \LogicException(
218:                 __METHOD__ . '() called after some output has been sent. '
219:                 . ($file ? "Output started at $file:$line." : 'Try Tracy\OutputDebugger to find where output started.')
220:             );
221: 
222:         } elseif (session_status() !== PHP_SESSION_ACTIVE) {
223:             ini_set('session.use_cookies', '1');
224:             ini_set('session.use_only_cookies', '1');
225:             ini_set('session.use_trans_sid', '0');
226:             ini_set('session.cookie_path', '/');
227:             ini_set('session.cookie_httponly', '1');
228:             session_start();
229:         }
230:         if (self::getBar()->dispatchContent()) {
231:             exit;
232:         }
233:     }
234: 
235: 
236:     /**
237:      * @return bool
238:      */
239:     public static function isEnabled()
240:     {
241:         return self::$enabled;
242:     }
243: 
244: 
245:     /**
246:      * Shutdown handler to catch fatal errors and execute of the planned activities.
247:      * @return void
248:      * @internal
249:      */
250:     public static function shutdownHandler()
251:     {
252:         if (!self::$reserved) {
253:             return;
254:         }
255: 
256:         $error = error_get_last();
257:         if (in_array($error['type'], [E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE, E_RECOVERABLE_ERROR, E_USER_ERROR], TRUE)) {
258:             self::exceptionHandler(
259:                 Helpers::fixStack(new ErrorException($error['message'], 0, $error['type'], $error['file'], $error['line'])),
260:                 FALSE
261:             );
262: 
263:         } elseif (self::$showBar && !self::$productionMode) {
264:             self::$reserved = NULL;
265:             self::removeOutputBuffers(FALSE);
266:             self::getBar()->render();
267:         }
268:     }
269: 
270: 
271:     /**
272:      * Handler to catch uncaught exception.
273:      * @param  \Exception|\Throwable
274:      * @return void
275:      * @internal
276:      */
277:     public static function exceptionHandler($exception, $exit = TRUE)
278:     {
279:         if (!self::$reserved) {
280:             return;
281:         }
282:         self::$reserved = NULL;
283: 
284:         if (!headers_sent()) {
285:             $protocol = isset($_SERVER['SERVER_PROTOCOL']) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.1';
286:             $code = isset($_SERVER['HTTP_USER_AGENT']) && strpos($_SERVER['HTTP_USER_AGENT'], 'MSIE ') !== FALSE ? 503 : 500;
287:             header("$protocol $code", TRUE, $code);
288:             if (Helpers::isHtmlMode()) {
289:                 header('Content-Type: text/html; charset=UTF-8');
290:             }
291:         }
292: 
293:         Helpers::improveException($exception);
294:         self::removeOutputBuffers(TRUE);
295: 
296:         if (self::$productionMode) {
297:             try {
298:                 self::log($exception, self::EXCEPTION);
299:             } catch (\Throwable $e) {
300:             } catch (\Exception $e) {
301:             }
302: 
303:             if (Helpers::isHtmlMode()) {
304:                 $logged = empty($e);
305:                 require self::$errorTemplate ?: __DIR__ . '/assets/Debugger/error.500.phtml';
306:             } elseif (PHP_SAPI === 'cli') {
307:                 fwrite(STDERR, 'ERROR: application encountered an error and can not continue. '
308:                     . (isset($e) ? "Unable to log error.\n" : "Error was logged.\n"));
309:             }
310: 
311:         } elseif (!connection_aborted() && (Helpers::isHtmlMode() || Helpers::isAjax())) {
312:             self::getBlueScreen()->render($exception);
313:             if (self::$showBar) {
314:                 self::getBar()->render();
315:             }
316: 
317:         } else {
318:             self::fireLog($exception);
319:             $s = get_class($exception) . ($exception->getMessage() === '' ? '' : ': ' . $exception->getMessage())
320:                 . ' in ' . $exception->getFile() . ':' . $exception->getLine()
321:                 . "\nStack trace:\n" . $exception->getTraceAsString();
322:             try {
323:                 $file = self::log($exception, self::EXCEPTION);
324:                 if ($file && !headers_sent()) {
325:                     header("X-Tracy-Error-Log: $file");
326:                 }
327:                 echo "$s\n" . ($file ? "(stored in $file)\n" : '');
328:                 if ($file && self::$browser) {
329:                     exec(self::$browser . ' ' . escapeshellarg($file));
330:                 }
331:             } catch (\Throwable $e) {
332:                 echo "$s\nUnable to log error: {$e->getMessage()}\n";
333:             } catch (\Exception $e) {
334:                 echo "$s\nUnable to log error: {$e->getMessage()}\n";
335:             }
336:         }
337: 
338:         try {
339:             $e = NULL;
340:             foreach (self::$onFatalError as $handler) {
341:                 call_user_func($handler, $exception);
342:             }
343:         } catch (\Throwable $e) {
344:         } catch (\Exception $e) {
345:         }
346:         if ($e) {
347:             try {
348:                 self::log($e, self::EXCEPTION);
349:             } catch (\Throwable $e) {
350:             } catch (\Exception $e) {
351:             }
352:         }
353: 
354:         if ($exit) {
355:             exit(255);
356:         }
357:     }
358: 
359: 
360:     /**
361:      * Handler to catch warnings and notices.
362:      * @return bool   FALSE to call normal error handler, NULL otherwise
363:      * @throws ErrorException
364:      * @internal
365:      */
366:     public static function errorHandler($severity, $message, $file, $line, $context)
367:     {
368:         if (self::$scream) {
369:             error_reporting(E_ALL);
370:         }
371: 
372:         if ($severity === E_RECOVERABLE_ERROR || $severity === E_USER_ERROR) {
373:             if (Helpers::findTrace(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS), '*::__toString')) {
374:                 $previous = isset($context['e']) && ($context['e'] instanceof \Exception || $context['e'] instanceof \Throwable) ? $context['e'] : NULL;
375:                 $e = new ErrorException($message, 0, $severity, $file, $line, $previous);
376:                 $e->context = $context;
377:                 self::exceptionHandler($e);
378:             }
379: 
380:             $e = new ErrorException($message, 0, $severity, $file, $line);
381:             $e->context = $context;
382:             throw $e;
383: 
384:         } elseif (($severity & error_reporting()) !== $severity) {
385:             return FALSE; // calls normal error handler to fill-in error_get_last()
386: 
387:         } elseif (self::$productionMode && ($severity & self::$logSeverity) === $severity) {
388:             $e = new ErrorException($message, 0, $severity, $file, $line);
389:             $e->context = $context;
390:             Helpers::improveException($e);
391:             try {
392:                 self::log($e, self::ERROR);
393:             } catch (\Throwable $e) {
394:             } catch (\Exception $foo) {
395:             }
396:             return NULL;
397: 
398:         } elseif (!self::$productionMode && !isset($_GET['_tracy_skip_error'])
399:             && (is_bool(self::$strictMode) ? self::$strictMode : ((self::$strictMode & $severity) === $severity))
400:         ) {
401:             $e = new ErrorException($message, 0, $severity, $file, $line);
402:             $e->context = $context;
403:             $e->skippable = TRUE;
404:             self::exceptionHandler($e);
405:         }
406: 
407:         $message = 'PHP ' . Helpers::errorTypeToString($severity) . ": $message";
408:         $count = & self::getBar()->getPanel('Tracy:errors')->data["$file|$line|$message"];
409: 
410:         if ($count++) { // repeated error
411:             return NULL;
412: 
413:         } elseif (self::$productionMode) {
414:             try {
415:                 self::log("$message in $file:$line", self::ERROR);
416:             } catch (\Throwable $e) {
417:             } catch (\Exception $foo) {
418:             }
419:             return NULL;
420: 
421:         } else {
422:             self::fireLog(new ErrorException($message, 0, $severity, $file, $line));
423:             return Helpers::isHtmlMode() || Helpers::isAjax() ? NULL : FALSE; // FALSE calls normal error handler
424:         }
425:     }
426: 
427: 
428:     private static function removeOutputBuffers($errorOccurred)
429:     {
430:         while (ob_get_level() > self::$obLevel) {
431:             $status = ob_get_status();
432:             if (in_array($status['name'], ['ob_gzhandler', 'zlib output compression'])) {
433:                 break;
434:             }
435:             $fnc = $status['chunk_size'] || !$errorOccurred ? 'ob_end_flush' : 'ob_end_clean';
436:             if (!@$fnc()) { // @ may be not removable
437:                 break;
438:             }
439:         }
440:     }
441: 
442: 
443:     /********************* services ****************d*g**/
444: 
445: 
446:     /**
447:      * @return BlueScreen
448:      */
449:     public static function getBlueScreen()
450:     {
451:         if (!self::$blueScreen) {
452:             self::$blueScreen = new BlueScreen;
453:             self::$blueScreen->info = [
454:                 'PHP ' . PHP_VERSION,
455:                 isset($_SERVER['SERVER_SOFTWARE']) ? $_SERVER['SERVER_SOFTWARE'] : NULL,
456:                 'Tracy ' . self::VERSION,
457:             ];
458:         }
459:         return self::$blueScreen;
460:     }
461: 
462: 
463:     /**
464:      * @return Bar
465:      */
466:     public static function getBar()
467:     {
468:         if (!self::$bar) {
469:             self::$bar = new Bar;
470:             self::$bar->addPanel($info = new DefaultBarPanel('info'), 'Tracy:info');
471:             $info->cpuUsage = self::$cpuUsage;
472:             self::$bar->addPanel(new DefaultBarPanel('errors'), 'Tracy:errors'); // filled by errorHandler()
473:         }
474:         return self::$bar;
475:     }
476: 
477: 
478:     /**
479:      * @return void
480:      */
481:     public static function setLogger(ILogger $logger)
482:     {
483:         self::$logger = $logger;
484:     }
485: 
486: 
487:     /**
488:      * @return ILogger
489:      */
490:     public static function getLogger()
491:     {
492:         if (!self::$logger) {
493:             self::$logger = new Logger(self::$logDirectory, self::$email, self::getBlueScreen());
494:             self::$logger->directory = & self::$logDirectory; // back compatiblity
495:             self::$logger->email = & self::$email;
496:         }
497:         return self::$logger;
498:     }
499: 
500: 
501:     /**
502:      * @return ILogger
503:      */
504:     public static function getFireLogger()
505:     {
506:         if (!self::$fireLogger) {
507:             self::$fireLogger = new FireLogger;
508:         }
509:         return self::$fireLogger;
510:     }
511: 
512: 
513:     /********************* useful tools ****************d*g**/
514: 
515: 
516:     /**
517:      * Dumps information about a variable in readable format.
518:      * @tracySkipLocation
519:      * @param  mixed  variable to dump
520:      * @param  bool   return output instead of printing it? (bypasses $productionMode)
521:      * @return mixed  variable itself or dump
522:      */
523:     public static function dump($var, $return = FALSE)
524:     {
525:         if ($return) {
526:             ob_start(function () {});
527:             Dumper::dump($var, [
528:                 Dumper::DEPTH => self::$maxDepth,
529:                 Dumper::TRUNCATE => self::$maxLength,
530:             ]);
531:             return ob_get_clean();
532: 
533:         } elseif (!self::$productionMode) {
534:             Dumper::dump($var, [
535:                 Dumper::DEPTH => self::$maxDepth,
536:                 Dumper::TRUNCATE => self::$maxLength,
537:                 Dumper::LOCATION => self::$showLocation,
538:             ]);
539:         }
540: 
541:         return $var;
542:     }
543: 
544: 
545:     /**
546:      * Starts/stops stopwatch.
547:      * @param  string  name
548:      * @return float   elapsed seconds
549:      */
550:     public static function timer($name = NULL)
551:     {
552:         static $time = [];
553:         $now = microtime(TRUE);
554:         $delta = isset($time[$name]) ? $now - $time[$name] : 0;
555:         $time[$name] = $now;
556:         return $delta;
557:     }
558: 
559: 
560:     /**
561:      * Dumps information about a variable in Tracy Debug Bar.
562:      * @tracySkipLocation
563:      * @param  mixed  variable to dump
564:      * @param  string optional title
565:      * @param  array  dumper options
566:      * @return mixed  variable itself
567:      */
568:     public static function barDump($var, $title = NULL, array $options = NULL)
569:     {
570:         if (!self::$productionMode) {
571:             static $panel;
572:             if (!$panel) {
573:                 self::getBar()->addPanel($panel = new DefaultBarPanel('dumps'), 'Tracy:dumps');
574:             }
575:             $panel->data[] = ['title' => $title, 'dump' => Dumper::toHtml($var, (array) $options + [
576:                 Dumper::DEPTH => self::$maxDepth,
577:                 Dumper::TRUNCATE => self::$maxLength,
578:                 Dumper::LOCATION => self::$showLocation ?: Dumper::LOCATION_CLASS | Dumper::LOCATION_SOURCE,
579:             ])];
580:         }
581:         return $var;
582:     }
583: 
584: 
585:     /**
586:      * Logs message or exception.
587:      * @param  string|\Exception|\Throwable
588:      * @return mixed
589:      */
590:     public static function log($message, $priority = ILogger::INFO)
591:     {
592:         return self::getLogger()->log($message, $priority);
593:     }
594: 
595: 
596:     /**
597:      * Sends message to FireLogger console.
598:      * @param  mixed   message to log
599:      * @return bool    was successful?
600:      */
601:     public static function fireLog($message)
602:     {
603:         if (!self::$productionMode) {
604:             return self::getFireLogger()->log($message);
605:         }
606:     }
607: 
608: 
609:     /**
610:      * Detects debug mode by IP address.
611:      * @param  string|array  IP addresses or computer names whitelist detection
612:      * @return bool
613:      */
614:     public static function detectDebugMode($list = NULL)
615:     {
616:         $addr = isset($_SERVER['REMOTE_ADDR'])
617:             ? $_SERVER['REMOTE_ADDR']
618:             : php_uname('n');
619:         $secret = isset($_COOKIE[self::COOKIE_SECRET]) && is_string($_COOKIE[self::COOKIE_SECRET])
620:             ? $_COOKIE[self::COOKIE_SECRET]
621:             : NULL;
622:         $list = is_string($list)
623:             ? preg_split('#[,\s]+#', $list)
624:             : (array) $list;
625:         if (!isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
626:             $list[] = '127.0.0.1';
627:             $list[] = '::1';
628:         }
629:         return in_array($addr, $list, TRUE) || in_array("$secret@$addr", $list, TRUE);
630:     }
631: 
632: }
633: 
Nette 2.4-20160930 API API documentation generated by ApiGen 2.8.0