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

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