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