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