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