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