/** * @author David Grudl * @see https://github.com/nette/tracy */ protected static function formatMessage(Throwable $message) : string { $tmp = []; while ($message) { $tmp[] = ($message instanceof \ErrorException ? Helpers::errorTypeToString($message->getSeverity()) . ': ' . $message->getMessage() : Helpers::getClass($message) . ': ' . $message->getMessage()) . ' in ' . $message->getFile() . ':' . $message->getLine(); $message = $message->getPrevious(); } $message = implode($tmp, "\ncaused by "); return trim($message); }
/** * @return mixed */ private static function toJson(&$var, $options, $level = 0) { if (is_bool($var) || is_null($var) || is_int($var)) { return $var; } elseif (is_float($var)) { return is_finite($var) ? strpos($tmp = json_encode($var), '.') ? $var : array('number' => "{$tmp}.0") : array('type' => (string) $var); } elseif (is_string($var)) { return self::encodeString($var, $options[self::TRUNCATE]); } elseif (is_array($var)) { static $marker; if ($marker === NULL) { $marker = uniqid("", TRUE); } if (isset($var[$marker]) || $level >= $options[self::DEPTH]) { return array(NULL); } $res = array(); $var[$marker] = TRUE; foreach ($var as $k => &$v) { if ($k !== $marker) { $k = preg_match('#^\\w{1,50}\\z#', $k) ? $k : '"' . self::encodeString($k, $options[self::TRUNCATE]) . '"'; $res[] = array($k, self::toJson($v, $options, $level + 1)); } } unset($var[$marker]); return $res; } elseif (is_object($var)) { $obj =& self::$liveStorage[spl_object_hash($var)]; if ($obj && $obj['level'] <= $level) { return array('object' => $obj['id']); } if ($options[self::LOCATION] & self::LOCATION_CLASS) { $rc = $var instanceof \Closure ? new \ReflectionFunction($var) : new \ReflectionClass($var); $editor = Helpers::editorUri($rc->getFileName(), $rc->getStartLine()); } static $counter = 1; $obj = $obj ?: array('id' => self::$livePrefix . '0' . $counter++, 'name' => Helpers::getClass($var), 'editor' => empty($editor) ? NULL : array('file' => $rc->getFileName(), 'line' => $rc->getStartLine(), 'url' => $editor), 'level' => $level, 'object' => $var); if ($level < $options[self::DEPTH] || !$options[self::DEPTH]) { $obj['level'] = $level; $obj['items'] = array(); foreach (self::exportObject($var, $options[self::OBJECT_EXPORTERS]) as $k => $v) { $vis = 0; if (isset($k[0]) && $k[0] === "") { $vis = $k[1] === '*' ? 1 : 2; $k = substr($k, strrpos($k, "") + 1); } $k = preg_match('#^\\w{1,50}\\z#', $k) ? $k : '"' . self::encodeString($k, $options[self::TRUNCATE]) . '"'; $obj['items'][] = array($k, self::toJson($v, $options, $level + 1), $vis); } } return array('object' => $obj['id']); } elseif (is_resource($var)) { $obj =& self::$liveStorage[(string) $var]; if (!$obj) { $type = get_resource_type($var); $obj = array('id' => self::$livePrefix . (int) $var, 'name' => $type . ' resource'); if (isset(self::$resources[$type])) { foreach (call_user_func(self::$resources[$type], $var) as $k => $v) { $obj['items'][] = array($k, self::toJson($v, $options, $level + 1)); } } } return array('resource' => $obj['id']); } else { return array('type' => 'unknown type'); } }
/** * Dump implementation for JSON. * @param mixed variable to dump * @param int current recursion level * @return string */ private function jsonDump(&$var, $level = 0) { if (is_bool($var) || is_null($var) || is_int($var) || is_float($var)) { return $var; } elseif (is_string($var)) { return Dumper::encodeString($var, $this->maxLength); } elseif (is_array($var)) { static $marker; if ($marker === NULL) { $marker = uniqid("", TRUE); } if (isset($var[$marker])) { return "…RECURSION…"; } elseif ($level < $this->maxDepth || !$this->maxDepth) { $var[$marker] = TRUE; $res = []; foreach ($var as $k => &$v) { if ($k !== $marker) { $res[$this->jsonDump($k)] = $this->jsonDump($v, $level + 1); } } unset($var[$marker]); return $res; } else { return " … "; } } elseif (is_object($var)) { $arr = (array) $var; static $list = []; if (in_array($var, $list, TRUE)) { return "…RECURSION…"; } elseif ($level < $this->maxDepth || !$this->maxDepth) { $list[] = $var; $res = ["" => '(object) ' . Helpers::getClass($var)]; foreach ($arr as $k => &$v) { if (isset($k[0]) && $k[0] === "") { $k = substr($k, strrpos($k, "") + 1); } $res[$this->jsonDump($k)] = $this->jsonDump($v, $level + 1); } array_pop($list); return $res; } else { return " … "; } } elseif (is_resource($var)) { return 'resource ' . get_resource_type($var); } else { return 'unknown type'; } }
protected function getTitle($message, $priority) { if ($message instanceof \Exception) { return Tracy\Helpers::getClass($message); } else { return $priority; } }
/** * Dump implementation for JSON. * @param mixed variable to dump * @param int current recursion level * @return string */ private static function jsonDump(& $var, $level = 0) { if (is_bool($var) || is_null($var) || is_int($var) || is_float($var)) { return $var; } elseif (is_string($var)) { if (Debugger::$maxLen && strlen($var) > Debugger::$maxLen) { $var = substr($var, 0, Debugger::$maxLen) . " \xE2\x80\xA6 "; } return Helpers::fixEncoding($var); } elseif (is_array($var)) { static $marker; if ($marker === NULL) { $marker = uniqid("\x00", TRUE); } if (isset($var[$marker])) { return "\xE2\x80\xA6RECURSION\xE2\x80\xA6"; } elseif ($level < Debugger::$maxDepth || !Debugger::$maxDepth) { $var[$marker] = TRUE; $res = array(); foreach ($var as $k => & $v) { if ($k !== $marker) { $res[self::jsonDump($k)] = self::jsonDump($v, $level + 1); } } unset($var[$marker]); return $res; } else { return " \xE2\x80\xA6 "; } } elseif (is_object($var)) { $arr = (array) $var; static $list = array(); if (in_array($var, $list, TRUE)) { return "\xE2\x80\xA6RECURSION\xE2\x80\xA6"; } elseif ($level < Debugger::$maxDepth || !Debugger::$maxDepth) { $list[] = $var; $res = array("\x00" => '(object) ' . Helpers::getClass($var)); foreach ($arr as $k => & $v) { if ($k[0] === "\x00") { $k = substr($k, strrpos($k, "\x00") + 1); } $res[self::jsonDump($k)] = self::jsonDump($v, $level + 1); } array_pop($list); return $res; } else { return " \xE2\x80\xA6 "; } } elseif (is_resource($var)) { return 'resource ' . get_resource_type($var); } else { return 'unknown type'; } }
/** * Logs message or exception to file (if not disabled) and sends email notification (if enabled). * @param string|Exception * @param int one of constant Debugger::INFO, WARNING, ERROR (sends email), EXCEPTION (sends email), CRITICAL (sends email) * @return string logged error filename */ public static function log($message, $priority = self::INFO) { if (!self::$logDirectory) { return; } $exceptionFilename = NULL; if ($message instanceof \Exception || $message instanceof \Throwable) { $exception = $message; while ($exception) { $tmp[] = ($exception instanceof ErrorException ? 'Fatal error: ' . $exception->getMessage() : Helpers::getClass($exception) . ': ' . $exception->getMessage()) . ' in ' . $exception->getFile() . ':' . $exception->getLine(); $exception = $exception->getPrevious(); } $exception = $message; $message = implode($tmp, "\ncaused by "); $hash = md5(preg_replace('~(Resource id #)\d+~', '$1', $exception)); $exceptionFilename = 'exception-' . @date('Y-m-d-H-i-s') . "-$hash.html"; foreach (new \DirectoryIterator(self::$logDirectory) as $entry) { if (strpos($entry, $hash)) { $exceptionFilename = $entry; $saved = TRUE; break; } } } elseif (!is_string($message)) { $message = Dumper::toText($message); } if ($exceptionFilename) { $exceptionFilename = self::$logDirectory . '/' . $exceptionFilename; if (empty($saved) && $logHandle = @fopen($exceptionFilename, 'w')) { ob_start(); // double buffer prevents sending HTTP headers in some PHP ob_start(function ($buffer) use ($logHandle) { fwrite($logHandle, $buffer); }, 4096); self::getBlueScreen()->render($exception); ob_end_flush(); ob_end_clean(); fclose($logHandle); } } self::getLogger()->log(array( @date('[Y-m-d H-i-s]'), trim($message), self::$source ? ' @ ' . self::$source : NULL, $exceptionFilename ? ' @@ ' . basename($exceptionFilename) : NULL ), $priority); return $exceptionFilename ? strtr($exceptionFilename, '\\/', DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR) : NULL; }
private static function dumpObject(& $var, $options, $level) { if ($var instanceof \Closure) { $rc = new \ReflectionFunction($var); $fields = array(); foreach ($rc->getParameters() as $param) { $fields[] = '$' . $param->getName(); } $fields = array( 'file' => $rc->getFileName(), 'line' => $rc->getStartLine(), 'variables' => $rc->getStaticVariables(), 'parameters' => implode(', ', $fields) ); } elseif ($var instanceof \SplFileInfo) { $fields = array('path' => $var->getPathname()); } elseif ($var instanceof \SplObjectStorage) { $fields = array(); foreach (clone $var as $obj) { $fields[] = array('object' => $obj, 'data' => $var[$obj]); } } else { $fields = (array) $var; } static $list = array(); $rc = $var instanceof \Closure ? new \ReflectionFunction($var) : new \ReflectionClass($var); $out = '<span class="tracy-dump-object"' . ($options[self::LOCATION] && ($editor = Helpers::editorUri($rc->getFileName(), $rc->getStartLine())) ? ' data-tracy-href="' . htmlspecialchars($editor) . '"' : '') . '>' . htmlspecialchars(Helpers::getClass($var)) . '</span> <span class="tracy-dump-hash">#' . substr(md5(spl_object_hash($var)), 0, 4) . '</span>'; if (empty($fields)) { return $out . "\n"; } elseif (in_array($var, $list, TRUE)) { return $out . " { <i>RECURSION</i> }\n"; } elseif (!$options[self::DEPTH] || $level < $options[self::DEPTH] || $var instanceof \Closure) { $collapsed = $level ? count($fields) >= $options[self::COLLAPSE_COUNT] : $options[self::COLLAPSE]; $out = '<span class="tracy-toggle' . ($collapsed ? ' tracy-collapsed' : '') . '">' . $out . "</span>\n<div" . ($collapsed ? ' class="tracy-collapsed"' : '') . '>'; $list[] = $var; foreach ($fields as $k => & $v) { $vis = ''; if ($k[0] === "\x00") { $vis = ' <span class="tracy-dump-visibility">' . ($k[1] === '*' ? 'protected' : 'private') . '</span>'; $k = substr($k, strrpos($k, "\x00") + 1); } $k = preg_match('#^\w{1,50}\z#', $k) ? $k : '"' . htmlspecialchars(self::encodeString($k, $options[self::TRUNCATE]), ENT_NOQUOTES, 'UTF-8') . '"'; $out .= '<span class="tracy-dump-indent"> ' . str_repeat('| ', $level) . '</span>' . '<span class="tracy-dump-key">' . $k . "</span>$vis => " . self::dumpVar($v, $options, $level + 1); } array_pop($list); return $out . '</div>'; } else { return $out . " { ... }\n"; } }