Namespaces

  • Latte
    • Loaders
    • Macros
    • Runtime
  • Nette
    • Application
      • Responses
      • Routers
      • UI
    • Bridges
      • ApplicationLatte
      • ApplicationTracy
      • CacheLatte
      • DatabaseDI
      • DatabaseTracy
      • DITracy
      • FormsLatte
      • Framework
      • HttpTracy
      • SecurityTracy
    • Caching
      • Storages
    • ComponentModel
    • Database
      • Drivers
      • Reflection
      • Table
    • DI
      • Config
        • Adapters
      • Extensions
    • Diagnostics
    • Forms
      • Controls
      • Rendering
    • Http
    • Iterators
    • Latte
    • Loaders
    • Localization
    • Mail
    • Neon
    • PhpGenerator
    • Reflection
    • Security
    • Templating
    • Utils
  • NetteModule
  • none
  • Tracy

Classes

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

Interfaces

  • IBarPanel
  • Overview
  • Namespace
  • Class
  • Tree
  • Deprecated
  • Other releases
  • Nette homepage
  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:  * Behavior is determined by two factors: mode & output
 18:  * - modes: production / development
 19:  * - output: HTML / AJAX / CLI / other (e.g. XML)
 20:  *
 21:  * @author     David Grudl
 22:  */
 23: class Debugger
 24: {
 25:     const VERSION = '2.2.9';
 26: 
 27:     /** @deprecated */
 28:     public static $version = self::VERSION;
 29: 
 30:     /** @var bool in production mode is suppressed any debugging output */
 31:     public static $productionMode = self::DETECT;
 32: 
 33:     /** @var int timestamp with microseconds of the start of the request */
 34:     public static $time;
 35: 
 36:     /** @var string  requested URI or command line */
 37:     public static $source;
 38: 
 39:     /** @var string URI pattern mask to open editor */
 40:     public static $editor = 'editor://open/?file=%file&line=%line';
 41: 
 42:     /** @var string command to open browser (use 'start ""' in Windows) */
 43:     public static $browser;
 44: 
 45:     /********************* Debugger::dump() ****************d*g**/
 46: 
 47:     /** @var int  how many nested levels of array/object properties display {@link Debugger::dump()} */
 48:     public static $maxDepth = 3;
 49: 
 50:     /** @var int  how long strings display {@link Debugger::dump()} */
 51:     public static $maxLen = 150;
 52: 
 53:     /** @var bool display location? {@link Debugger::dump()} */
 54:     public static $showLocation = FALSE;
 55: 
 56:     /********************* errors and exceptions reporting ****************d*g**/
 57: 
 58:     /** server modes {@link Debugger::enable()} */
 59:     const DEVELOPMENT = FALSE,
 60:         PRODUCTION = TRUE,
 61:         DETECT = NULL;
 62: 
 63:     /** @var BlueScreen */
 64:     private static $blueScreen;
 65: 
 66:     /** @var bool|int determines whether any error will cause immediate death; if integer that it's matched against error severity */
 67:     public static $strictMode = FALSE; // $immediateDeath
 68: 
 69:     /** @var bool disables the @ (shut-up) operator so that notices and warnings are no longer hidden */
 70:     public static $scream = FALSE;
 71: 
 72:     /** @var array of callables specifies the functions that are automatically called after fatal error */
 73:     public static $onFatalError = array();
 74: 
 75:     /** @var bool {@link Debugger::enable()} */
 76:     private static $enabled = FALSE;
 77: 
 78:     /** @var bool prevent double rendering */
 79:     private static $done;
 80: 
 81:     /** @internal */
 82:     public static $errorTypes = array(
 83:         E_ERROR => 'Fatal Error',
 84:         E_USER_ERROR => 'User Error',
 85:         E_RECOVERABLE_ERROR => 'Recoverable Error',
 86:         E_CORE_ERROR => 'Core Error',
 87:         E_COMPILE_ERROR => 'Compile Error',
 88:         E_PARSE => 'Parse Error',
 89:         E_WARNING => 'Warning',
 90:         E_CORE_WARNING => 'Core Warning',
 91:         E_COMPILE_WARNING => 'Compile Warning',
 92:         E_USER_WARNING => 'User Warning',
 93:         E_NOTICE => 'Notice',
 94:         E_USER_NOTICE => 'User Notice',
 95:         E_STRICT => 'Strict standards',
 96:         E_DEPRECATED => 'Deprecated',
 97:         E_USER_DEPRECATED => 'User Deprecated',
 98:     );
 99: 
100:     /********************* logging ****************d*g**/
101: 
102:     /** @var Logger */
103:     private static $logger;
104: 
105:     /** @var FireLogger */
106:     private static $fireLogger;
107: 
108:     /** @var string name of the directory where errors should be logged */
109:     public static $logDirectory;
110: 
111:     /** @var int  log bluescreen in production mode for this error severity */
112:     public static $logSeverity = 0;
113: 
114:     /** @var string|array email(s) to which send error notifications */
115:     public static $email;
116: 
117:     /** @deprecated */
118:     public static $mailer = array('Tracy\Logger', 'defaultMailer');
119: 
120:     /** @deprecated */
121:     public static $emailSnooze = 172800;
122: 
123:     /** {@link Debugger::log()} and {@link Debugger::fireLog()} */
124:     const DEBUG = 'debug',
125:         INFO = 'info',
126:         WARNING = 'warning',
127:         ERROR = 'error',
128:         EXCEPTION = 'exception',
129:         CRITICAL = 'critical';
130: 
131:     /********************* debug bar ****************d*g**/
132: 
133:     /** @var Bar */
134:     private static $bar;
135: 
136: 
137:     /**
138:      * Static class - cannot be instantiated.
139:      */
140:     final public function __construct()
141:     {
142:         throw new \LogicException;
143:     }
144: 
145: 
146:     /**
147:      * Enables displaying or logging errors and exceptions.
148:      * @param  mixed         production, development mode, autodetection or IP address(es) whitelist.
149:      * @param  string        error log directory; enables logging in production mode, FALSE means that logging is disabled
150:      * @param  string        administrator email; enables email sending in production mode
151:      * @return void
152:      */
153:     public static function enable($mode = NULL, $logDirectory = NULL, $email = NULL)
154:     {
155:         self::$time = isset($_SERVER['REQUEST_TIME_FLOAT']) ? $_SERVER['REQUEST_TIME_FLOAT'] : microtime(TRUE);
156:         if (isset($_SERVER['REQUEST_URI'])) {
157:             self::$source = (!empty($_SERVER['HTTPS']) && strcasecmp($_SERVER['HTTPS'], 'off') ? 'https://' : 'http://')
158:                 . (isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : '')
159:                 . $_SERVER['REQUEST_URI'];
160:         } else {
161:             self::$source = empty($_SERVER['argv']) ? 'CLI' : 'CLI: ' . implode(' ', $_SERVER['argv']);
162:         }
163:         error_reporting(E_ALL | E_STRICT);
164: 
165:         // production/development mode detection
166:         if (is_bool($mode)) {
167:             self::$productionMode = $mode;
168: 
169:         } elseif ($mode !== self::DETECT || self::$productionMode === NULL) { // IP addresses or computer names whitelist detection
170:             $list = is_string($mode) ? preg_split('#[,\s]+#', $mode) : (array) $mode;
171:             if (!isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
172:                 $list[] = '127.0.0.1';
173:                 $list[] = '::1';
174:             }
175:             self::$productionMode = !in_array(isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : php_uname('n'), $list, TRUE);
176:         }
177: 
178:         // logging configuration
179:         if ($email !== NULL) {
180:             self::$email = $email;
181:         }
182:         if (is_string($logDirectory)) {
183:             self::$logDirectory = realpath($logDirectory);
184:             if (self::$logDirectory === FALSE) {
185:                 self::_exceptionHandler(new \RuntimeException("Log directory is not found or is not directory."));
186:             }
187:         } elseif ($logDirectory === FALSE) {
188:             self::$logDirectory = NULL;
189:         }
190: 
191:         // php configuration
192:         if (function_exists('ini_set')) {
193:             ini_set('display_errors', !self::$productionMode); // or 'stderr'
194:             ini_set('html_errors', FALSE);
195:             ini_set('log_errors', FALSE);
196: 
197:         } elseif (ini_get('display_errors') != !self::$productionMode // intentionally ==
198:             && ini_get('display_errors') !== (self::$productionMode ? 'stderr' : 'stdout')
199:         ) {
200:             self::_exceptionHandler(new \RuntimeException("Unable to set 'display_errors' because function ini_set() is disabled."));
201:         }
202: 
203:         if (!self::$enabled) {
204:             register_shutdown_function(array(__CLASS__, '_shutdownHandler'));
205:             set_exception_handler(array(__CLASS__, '_exceptionHandler'));
206:             set_error_handler(array(__CLASS__, '_errorHandler'));
207: 
208:             foreach (array('Tracy\Bar', 'Tracy\BlueScreen', 'Tracy\DefaultBarPanel', 'Tracy\Dumper',
209:                 'Tracy\FireLogger', 'Tracy\Helpers', 'Tracy\Logger') as $class) {
210:                 class_exists($class);
211:             }
212: 
213:             self::$enabled = TRUE;
214:         }
215:     }
216: 
217: 
218:     /**
219:      * @return BlueScreen
220:      */
221:     public static function getBlueScreen()
222:     {
223:         if (!self::$blueScreen) {
224:             self::$blueScreen = new BlueScreen;
225:             self::$blueScreen->info = array(
226:                 'PHP ' . PHP_VERSION,
227:                 isset($_SERVER['SERVER_SOFTWARE']) ? $_SERVER['SERVER_SOFTWARE'] : NULL,
228:                 'Tracy ' . self::VERSION,
229:             );
230:         }
231:         return self::$blueScreen;
232:     }
233: 
234: 
235:     /**
236:      * @return Bar
237:      */
238:     public static function getBar()
239:     {
240:         if (!self::$bar) {
241:             self::$bar = new Bar;
242:             self::$bar->addPanel(new DefaultBarPanel('time'));
243:             self::$bar->addPanel(new DefaultBarPanel('memory'));
244:             self::$bar->addPanel(new DefaultBarPanel('errors'), __CLASS__ . ':errors'); // filled by _errorHandler()
245:             self::$bar->addPanel(new DefaultBarPanel('dumps'), __CLASS__ . ':dumps'); // filled by barDump()
246:             self::$bar->info = array(
247:                 'PHP ' . PHP_VERSION,
248:                 isset($_SERVER['SERVER_SOFTWARE']) ? $_SERVER['SERVER_SOFTWARE'] : NULL,
249:                 'Tracy ' . self::VERSION,
250:             );
251:         }
252:         return self::$bar;
253:     }
254: 
255: 
256:     /**
257:      * @return void
258:      */
259:     public static function setLogger($logger)
260:     {
261:         self::$logger = $logger;
262:     }
263: 
264: 
265:     /**
266:      * @return Logger
267:      */
268:     public static function getLogger()
269:     {
270:         if (!self::$logger) {
271:             self::$logger = new Logger;
272:             self::$logger->directory = & self::$logDirectory;
273:             self::$logger->email = & self::$email;
274:             self::$logger->mailer = & self::$mailer;
275:             self::$logger->emailSnooze = & self::$emailSnooze;
276:         }
277:         return self::$logger;
278:     }
279: 
280: 
281:     /**
282:      * @return FireLogger
283:      */
284:     public static function getFireLogger()
285:     {
286:         if (!self::$fireLogger) {
287:             self::$fireLogger = new FireLogger;
288:         }
289:         return self::$fireLogger;
290:     }
291: 
292: 
293:     /**
294:      * Is Debug enabled?
295:      * @return bool
296:      */
297:     public static function isEnabled()
298:     {
299:         return self::$enabled;
300:     }
301: 
302: 
303:     /**
304:      * Logs message or exception to file (if not disabled) and sends email notification (if enabled).
305:      * @param  string|Exception
306:      * @param  int  one of constant Debugger::INFO, WARNING, ERROR (sends email), EXCEPTION (sends email), CRITICAL (sends email)
307:      * @return string logged error filename
308:      */
309:     public static function log($message, $priority = self::INFO)
310:     {
311:         if (!self::$logDirectory) {
312:             return;
313:         }
314: 
315:         $exceptionFilename = NULL;
316:         if ($message instanceof \Exception || $message instanceof \Throwable) {
317:             $exception = $message;
318:             while ($exception) {
319:                 $tmp[] = ($exception instanceof ErrorException
320:                     ? 'Fatal error: ' . $exception->getMessage()
321:                     : Helpers::getClass($exception) . ': ' . $exception->getMessage())
322:                     . ' in ' . $exception->getFile() . ':' . $exception->getLine();
323:                 $exception = $exception->getPrevious();
324:             }
325:             $exception = $message;
326:             $message = implode($tmp, "\ncaused by ");
327: 
328:             $hash = md5(preg_replace('~(Resource id #)\d+~', '$1', $exception));
329:             $exceptionFilename = 'exception-' . @date('Y-m-d-H-i-s') . "-$hash.html";
330:             foreach (new \DirectoryIterator(self::$logDirectory) as $entry) {
331:                 if (strpos($entry, $hash)) {
332:                     $exceptionFilename = $entry;
333:                     $saved = TRUE;
334:                     break;
335:                 }
336:             }
337:         } elseif (!is_string($message)) {
338:             $message = Dumper::toText($message);
339:         }
340: 
341:         if ($exceptionFilename) {
342:             $exceptionFilename = self::$logDirectory . '/' . $exceptionFilename;
343:             if (empty($saved) && $logHandle = @fopen($exceptionFilename, 'w')) {
344:                 ob_start(); // double buffer prevents sending HTTP headers in some PHP
345:                 ob_start(function ($buffer) use ($logHandle) { fwrite($logHandle, $buffer); }, 4096);
346:                 self::getBlueScreen()->render($exception);
347:                 ob_end_flush();
348:                 ob_end_clean();
349:                 fclose($logHandle);
350:             }
351:         }
352: 
353:         self::getLogger()->log(array(
354:             @date('[Y-m-d H-i-s]'),
355:             trim($message),
356:             self::$source ? ' @  ' . self::$source : NULL,
357:             $exceptionFilename ? ' @@  ' . basename($exceptionFilename) : NULL
358:         ), $priority);
359: 
360:         return $exceptionFilename ? strtr($exceptionFilename, '\\/', DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR) : NULL;
361:     }
362: 
363: 
364:     /**
365:      * Shutdown handler to catch fatal errors and execute of the planned activities.
366:      * @return void
367:      * @internal
368:      */
369:     public static function _shutdownHandler()
370:     {
371:         if (self::$done) {
372:             return;
373:         }
374: 
375:         $error = error_get_last();
376:         if (in_array($error['type'], array(E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE), TRUE)) {
377:             self::_exceptionHandler(Helpers::fixStack(new ErrorException($error['message'], 0, $error['type'], $error['file'], $error['line'])), FALSE);
378: 
379:         } elseif (!connection_aborted() && !self::$productionMode && self::isHtmlMode()) {
380:             self::getBar()->render();
381:         }
382:     }
383: 
384: 
385:     /**
386:      * Handler to catch uncaught exception.
387:      * @param  \Exception|\Throwable
388:      * @return void
389:      * @internal
390:      */
391:     public static function _exceptionHandler($exception, $exit = TRUE)
392:     {
393:         if (self::$done) {
394:             return;
395:         }
396:         self::$done = TRUE;
397: 
398:         if (!headers_sent()) {
399:             $protocol = isset($_SERVER['SERVER_PROTOCOL']) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.1';
400:             $code = isset($_SERVER['HTTP_USER_AGENT']) && strpos($_SERVER['HTTP_USER_AGENT'], 'MSIE ') !== FALSE ? 503 : 500;
401:             header("$protocol $code", TRUE, $code);
402:             if (self::isHtmlMode()) {
403:                 header('Content-Type: text/html; charset=UTF-8');
404:             }
405:         }
406: 
407:         if (self::$productionMode) {
408:             try {
409:                 self::log($exception, self::EXCEPTION);
410:             } catch (\Throwable $e) {
411:             } catch (\Exception $e) {
412:             }
413: 
414:             if (self::isHtmlMode()) {
415:                 $logged = empty($e);
416:                 require __DIR__ . '/templates/error.phtml';
417:             } elseif (PHP_SAPI === 'cli') {
418:                 fwrite(STDERR, 'ERROR: application encountered an error and can not continue. '
419:                     . (isset($e) ? "Unable to log error.\n" : "Error was logged.\n"));
420:             }
421: 
422:         } elseif (!connection_aborted() && self::isHtmlMode()) {
423:             self::getBlueScreen()->render($exception);
424:             self::getBar()->render();
425: 
426:         } elseif (connection_aborted() || !self::fireLog($exception)) {
427:             try {
428:                 $file = self::log($exception, self::EXCEPTION);
429:                 if ($file && !headers_sent()) {
430:                     header("X-Tracy-Error-Log: $file");
431:                 }
432:                 echo "$exception\n" . ($file ? "(stored in $file)\n" : '');
433:                 if ($file && self::$browser) {
434:                     exec(self::$browser . ' ' . escapeshellarg($file));
435:                 }
436:             } catch (\Throwable $e) {
437:                 echo "$s\nUnable to log error: {$e->getMessage()}\n";
438:             } catch (\Exception $e) {
439:                 echo "$exception\nUnable to log error: {$e->getMessage()}\n";
440:             }
441:         }
442: 
443:         try {
444:             $e = NULL;
445:             foreach (self::$onFatalError as $handler) {
446:                 call_user_func($handler, $exception);
447:             }
448:         } catch (\Throwable $e) {
449:         } catch (\Exception $e) {
450:         }
451:         if ($e) {
452:             try {
453:                 self::log($e, self::EXCEPTION);
454:             } catch (\Throwable $e) {
455:             } catch (\Exception $e) {
456:             }
457:         }
458: 
459:         if ($exit) {
460:             exit(254);
461:         }
462:     }
463: 
464: 
465:     /**
466:      * Handler to catch warnings and notices.
467:      * @param  int    level of the error raised
468:      * @param  string error message
469:      * @param  string file that the error was raised in
470:      * @param  int    line number the error was raised at
471:      * @param  array  an array of variables that existed in the scope the error was triggered in
472:      * @return bool   FALSE to call normal error handler, NULL otherwise
473:      * @throws ErrorException
474:      * @internal
475:      */
476:     public static function _errorHandler($severity, $message, $file, $line, $context)
477:     {
478:         if (self::$scream) {
479:             error_reporting(E_ALL | E_STRICT);
480:         }
481: 
482:         if ($severity === E_RECOVERABLE_ERROR || $severity === E_USER_ERROR) {
483:             if (Helpers::findTrace(debug_backtrace(PHP_VERSION_ID >= 50306 ? DEBUG_BACKTRACE_IGNORE_ARGS : FALSE), '*::__toString')) {
484:                 $previous = isset($context['e']) && ($context['e'] instanceof \Exception || $context['e'] instanceof \Throwable) ? $context['e'] : NULL;
485:                 $e = new ErrorException($message, 0, $severity, $file, $line, $previous);
486:                 $e->context = $context;
487:                 self::_exceptionHandler($e);
488:             }
489: 
490:             $e = new ErrorException($message, 0, $severity, $file, $line);
491:             $e->context = $context;
492:             throw $e;
493: 
494:         } elseif (($severity & error_reporting()) !== $severity) {
495:             return FALSE; // calls normal error handler to fill-in error_get_last()
496: 
497:         } elseif (self::$productionMode && ($severity & self::$logSeverity) === $severity) {
498:             $e = new ErrorException($message, 0, $severity, $file, $line);
499:             $e->context = $context;
500:             self::log($e, self::ERROR);
501:             return NULL;
502: 
503:         } elseif (!self::$productionMode && (is_bool(self::$strictMode) ? self::$strictMode : ((self::$strictMode & $severity) === $severity))) {
504:             $e = new ErrorException($message, 0, $severity, $file, $line);
505:             $e->context = $context;
506:             self::_exceptionHandler($e);
507:         }
508: 
509:         $message = 'PHP ' . (isset(self::$errorTypes[$severity]) ? self::$errorTypes[$severity] : 'Unknown error') . ": $message";
510:         $count = & self::getBar()->getPanel(__CLASS__ . ':errors')->data["$file|$line|$message"];
511: 
512:         if ($count++) { // repeated error
513:             return NULL;
514: 
515:         } elseif (self::$productionMode) {
516:             self::log("$message in $file:$line", self::ERROR);
517:             return NULL;
518: 
519:         } else {
520:             self::fireLog(new ErrorException($message, 0, $severity, $file, $line));
521:             return self::isHtmlMode() ? NULL : FALSE; // FALSE calls normal error handler
522:         }
523:     }
524: 
525: 
526:     /********************* useful tools ****************d*g**/
527: 
528: 
529:     /**
530:      * Dumps information about a variable in readable format.
531:      * @tracySkipLocation
532:      * @param  mixed  variable to dump
533:      * @param  bool   return output instead of printing it? (bypasses $productionMode)
534:      * @return mixed  variable itself or dump
535:      */
536:     public static function dump($var, $return = FALSE)
537:     {
538:         if ($return) {
539:             ob_start();
540:             Dumper::dump($var, array(
541:                 Dumper::DEPTH => self::$maxDepth,
542:                 Dumper::TRUNCATE => self::$maxLen,
543:             ));
544:             return ob_get_clean();
545: 
546:         } elseif (!self::$productionMode) {
547:             Dumper::dump($var, array(
548:                 Dumper::DEPTH => self::$maxDepth,
549:                 Dumper::TRUNCATE => self::$maxLen,
550:                 Dumper::LOCATION => self::$showLocation,
551:             ));
552:         }
553: 
554:         return $var;
555:     }
556: 
557: 
558:     /**
559:      * Starts/stops stopwatch.
560:      * @param  string  name
561:      * @return float   elapsed seconds
562:      */
563:     public static function timer($name = NULL)
564:     {
565:         static $time = array();
566:         $now = microtime(TRUE);
567:         $delta = isset($time[$name]) ? $now - $time[$name] : 0;
568:         $time[$name] = $now;
569:         return $delta;
570:     }
571: 
572: 
573:     /**
574:      * Dumps information about a variable in Tracy Debug Bar.
575:      * @tracySkipLocation
576:      * @param  mixed  variable to dump
577:      * @param  string optional title
578:      * @param  array  dumper options
579:      * @return mixed  variable itself
580:      */
581:     public static function barDump($var, $title = NULL, array $options = NULL)
582:     {
583:         if (!self::$productionMode) {
584:             self::getBar()->getPanel(__CLASS__ . ':dumps')->data[] = array('title' => $title, 'dump' => Dumper::toHtml($var, (array) $options + array(
585:                 Dumper::DEPTH => self::$maxDepth,
586:                 Dumper::TRUNCATE => self::$maxLen,
587:                 Dumper::LOCATION => self::$showLocation,
588:             )));
589:         }
590:         return $var;
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:     private static function isHtmlMode()
608:     {
609:         return empty($_SERVER['HTTP_X_REQUESTED_WITH'])
610:             && PHP_SAPI !== 'cli'
611:             && !preg_match('#^Content-Type: (?!text/html)#im', implode("\n", headers_list()));
612:     }
613: 
614: }
615: 
Nette 2.2 API documentation generated by ApiGen 2.8.0