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
      • Traits
    • Reflection
    • Security
    • Tokenizer
    • Utils
  • Tracy
    • Bridges
      • Nette
  • none

Classes

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

Interfaces

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