From a5abc1d90167a25c7b7e5700c61b129b17bad4ee Mon Sep 17 00:00:00 2001 From: thinkphp Date: Tue, 1 Oct 2019 09:59:05 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + composer.json | 31 +++++++++ src/Console.php | 170 +++++++++++++++++++++++++++++++++++++++++++++ src/Html.php | 125 +++++++++++++++++++++++++++++++++ src/Service.php | 21 ++++++ src/TraceDebug.php | 109 +++++++++++++++++++++++++++++ src/config.php | 10 +++ 7 files changed, 467 insertions(+) create mode 100644 .gitignore create mode 100644 composer.json create mode 100644 src/Console.php create mode 100644 src/Html.php create mode 100644 src/Service.php create mode 100644 src/TraceDebug.php create mode 100644 src/config.php diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..485dee6 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.idea diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..bdfe924 --- /dev/null +++ b/composer.json @@ -0,0 +1,31 @@ +{ + "name": "topthink/think-trace", + "description": "thinkphp debug trace", + "license": "Apache-2.0", + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + } + ], + "require": { + "php": ">=7.1.0", + "topthink/framework": "^6.0.0", + }, + "autoload": { + "psr-4": { + "think\\trace\\": "src" + } + }, + "extra": { + "think":{ + "services":[ + "think\\trace\\Service" + ], + "config":{ + "trace": "src/config.php" + } + } + }, + "minimum-stability": "dev" +} diff --git a/src/Console.php b/src/Console.php new file mode 100644 index 0000000..9dbf66b --- /dev/null +++ b/src/Console.php @@ -0,0 +1,170 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); +namespace think\trace; + +use think\App; +use think\Response; + +/** + * 浏览器调试输出 + */ +class Console +{ + protected $config = [ + 'tabs' => ['base' => '基本', 'file' => '文件', 'info' => '流程', 'notice|error' => '错误', 'sql' => 'SQL', 'debug|log' => '调试'], + ]; + + // 实例化并传入参数 + public function __construct(array $config = []) + { + $this->config = array_merge($this->config, $config); + } + + /** + * 调试输出接口 + * @access public + * @param Response $response Response对象 + * @param array $log 日志信息 + * @return string|bool + */ + public function output(App $app, Response $response, array $log = []) + { + $request = $app->request; + $contentType = $response->getHeader('Content-Type'); + $accept = $request->header('accept', ''); + if (strpos($accept, 'application/json') === 0 || $request->isAjax()) { + return false; + } elseif (!empty($contentType) && strpos($contentType, 'html') === false) { + return false; + } + // 获取基本信息 + $runtime = number_format(microtime(true) - $app->getBeginTime(), 10); + $reqs = $runtime > 0 ? number_format(1 / $runtime, 2) : '∞'; + $mem = number_format((memory_get_usage() - $app->getBeginMem()) / 1024, 2); + + if ($request->host()) { + $uri = $request->protocol() . ' ' . $request->method() . ' : ' . $request->url(true); + } else { + $uri = 'cmd:' . implode(' ', $_SERVER['argv']); + } + + // 页面Trace信息 + $base = [ + '请求信息' => date('Y-m-d H:i:s', $request->time()) . ' ' . $uri, + '运行时间' => number_format((float) $runtime, 6) . 's [ 吞吐率:' . $reqs . 'req/s ] 内存消耗:' . $mem . 'kb 文件加载:' . count(get_included_files()), + '查询信息' => $app->db->getQueryTimes() . ' queries', + '缓存信息' => $app->cache->getReadTimes() . ' reads,' . $app->cache->getWriteTimes() . ' writes', + ]; + + if ($app->session->getId(false)) { + $base['会话信息'] = 'SESSION_ID=' . $app->session->getId(); + } + + $info = $this->getFileInfo(); + + // 页面Trace信息 + $trace = []; + foreach ($this->config['tabs'] as $name => $title) { + $name = strtolower($name); + switch ($name) { + case 'base': // 基本信息 + $trace[$title] = $base; + break; + case 'file': // 文件信息 + $trace[$title] = $info; + break; + default: // 调试信息 + if (strpos($name, '|')) { + // 多组信息 + $names = explode('|', $name); + $result = []; + foreach ($names as $item) { + $result = array_merge($result, $log[$item] ?? []); + } + $trace[$title] = $result; + } else { + $trace[$title] = $log[$name] ?? ''; + } + } + } + + //输出到控制台 + $lines = ''; + foreach ($trace as $type => $msg) { + $lines .= $this->console($type, $msg); + } + $js = << +{$lines} + +JS; + return $js; + } + + protected function console(string $type, $msg) + { + $type = strtolower($type); + $trace_tabs = array_values($this->config['tabs']); + $line = []; + $line[] = ($type == $trace_tabs[0] || '调试' == $type || '错误' == $type) + ? "console.group('{$type}');" + : "console.groupCollapsed('{$type}');"; + + foreach ((array) $msg as $key => $m) { + switch ($type) { + case '调试': + $var_type = gettype($m); + if (in_array($var_type, ['array', 'string'])) { + $line[] = "console.log(" . json_encode($m) . ");"; + } else { + $line[] = "console.log(" . json_encode(var_export($m, true)) . ");"; + } + break; + case '错误': + $msg = str_replace("\n", '\n', addslashes(is_scalar($m) ? $m : json_encode($m))); + $style = 'color:#F4006B;font-size:14px;'; + $line[] = "console.error(\"%c{$msg}\", \"{$style}\");"; + break; + case 'sql': + $msg = str_replace("\n", '\n', addslashes($m)); + $style = "color:#009bb4;"; + $line[] = "console.log(\"%c{$msg}\", \"{$style}\");"; + break; + default: + $m = is_string($key) ? $key . ' ' . $m : $key + 1 . ' ' . $m; + $msg = json_encode($m); + $line[] = "console.log({$msg});"; + break; + } + } + $line[] = "console.groupEnd();"; + return implode(PHP_EOL, $line); + } + + /** + * 获取文件加载信息 + * @access protected + * @return integer|array + */ + protected function getFileInfo() + { + $files = get_included_files(); + $info = []; + + foreach ($files as $key => $file) { + $info[] = $file . ' ( ' . number_format(filesize($file) / 1024, 2) . ' KB )'; + } + + return $info; + } +} diff --git a/src/Html.php b/src/Html.php new file mode 100644 index 0000000..a7a12aa --- /dev/null +++ b/src/Html.php @@ -0,0 +1,125 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); +namespace think\trace; + +use think\App; +use think\Response; + +/** + * 页面Trace调试 + */ +class Html +{ + protected $config = [ + 'file' => '', + 'tabs' => ['base' => '基本', 'file' => '文件', 'info' => '流程', 'notice|error' => '错误', 'sql' => 'SQL', 'debug|log' => '调试'], + ]; + + // 实例化并传入参数 + public function __construct(array $config = []) + { + $this->config = array_merge($this->config, $config); + } + + /** + * 调试输出接口 + * @access public + * @param App $app 应用实例 + * @param Response $response Response对象 + * @param array $log 日志信息 + * @return bool|string + */ + public function output(App $app, Response $response, array $log = []) + { + $request = $app->request; + + $contentType = $response->getHeader('Content-Type'); + $accept = $request->header('accept', ''); + if (strpos($accept, 'application/json') === 0 || $request->isAjax()) { + return false; + } elseif (!empty($contentType) && strpos($contentType, 'html') === false) { + return false; + } + + // 获取基本信息 + $runtime = number_format(microtime(true) - $app->getBeginTime(), 10, '.', ''); + $reqs = $runtime > 0 ? number_format(1 / $runtime, 2) : '∞'; + $mem = number_format((memory_get_usage() - $app->getBeginMem()) / 1024, 2); + + // 页面Trace信息 + if ($request->host()) { + $uri = $request->protocol() . ' ' . $request->method() . ' : ' . $request->url(true); + } else { + $uri = 'cmd:' . implode(' ', $_SERVER['argv']); + } + + $base = [ + '请求信息' => date('Y-m-d H:i:s', $request->time()) . ' ' . $uri, + '运行时间' => number_format((float) $runtime, 6) . 's [ 吞吐率:' . $reqs . 'req/s ] 内存消耗:' . $mem . 'kb 文件加载:' . count(get_included_files()), + '查询信息' => $app->db->getQueryTimes() . ' queries', + '缓存信息' => $app->cache->getReadTimes() . ' reads,' . $app->cache->getWriteTimes() . ' writes', + ]; + + if ($app->session->getId(false)) { + $base['会话信息'] = 'SESSION_ID=' . $app->session->getId(); + } + + $info = $this->getFileInfo(); + + // 页面Trace信息 + $trace = []; + foreach ($this->config['tabs'] as $name => $title) { + $name = strtolower($name); + switch ($name) { + case 'base': // 基本信息 + $trace[$title] = $base; + break; + case 'file': // 文件信息 + $trace[$title] = $info; + break; + default: // 调试信息 + if (strpos($name, '|')) { + // 多组信息 + $names = explode('|', $name); + $result = []; + foreach ($names as $item) { + $result = array_merge($result, $log[$item] ?? []); + } + $trace[$title] = $result; + } else { + $trace[$title] = $log[$name] ?? ''; + } + } + } + // 调用Trace页面模板 + ob_start(); + include $this->config['file'] ?: __DIR__ . '/../../tpl/page_trace.tpl'; + return ob_get_clean(); + } + + /** + * 获取文件加载信息 + * @access protected + * @return integer|array + */ + protected function getFileInfo() + { + $files = get_included_files(); + $info = []; + + foreach ($files as $key => $file) { + $info[] = $file . ' ( ' . number_format(filesize($file) / 1024, 2) . ' KB )'; + } + + return $info; + } +} diff --git a/src/Service.php b/src/Service.php new file mode 100644 index 0000000..3e78ecc --- /dev/null +++ b/src/Service.php @@ -0,0 +1,21 @@ + +// +---------------------------------------------------------------------- +namespace think\trace; + +use think\Service as BaseService; + +class Service extends BaseService +{ + public function register() + { + $this->app->middleware->add(TraceDebug::class); + } +} diff --git a/src/TraceDebug.php b/src/TraceDebug.php new file mode 100644 index 0000000..5ed9cbf --- /dev/null +++ b/src/TraceDebug.php @@ -0,0 +1,109 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\trace; + +use Closure; +use think\App; +use think\Config; +use think\event\LogWrite; +use think\Request; +use think\Response; +use think\response\Redirect; + +/** + * 页面Trace中间件 + */ +class TraceDebug +{ + + /** + * Trace日志 + * @var array + */ + protected $log = []; + + /** + * 配置参数 + * @var array + */ + protected $config = []; + + /** @var App */ + protected $app; + + public function __construct(App $app, Config $config) + { + $this->app = $app; + $this->config = $config->get('trace'); + } + + /** + * 页面Trace调试 + * @access public + * @param Request $request + * @param Closure $next + * @return void + */ + public function handle($request, Closure $next) + { + $debug = $this->app->isDebug(); + + // 注册日志监听 + if ($debug) { + $this->log = []; + $this->app->event->listen(LogWrite::class, function ($event) { + if (empty($this->config['channel']) || $this->config['channel'] == $event->channel) { + $this->log = array_merge_recursive($this->log, $event->log); + } + }); + } + + $response = $next($request); + + // Trace调试注入 + if ($debug) { + $data = $response->getContent(); + $this->traceDebug($response, $data); + $response->content($data); + } + + return $response; + } + + public function traceDebug(Response $response, &$content) + { + $config = $this->config; + $type = $config['type'] ?? 'Html'; + + unset($config['type']); + + $trace = App::factory($type, '\\think\\trace\\', $config); + + if ($response instanceof Redirect) { + //TODO 记录 + } else { + $log = $this->app->log->getLog($config['channel'] ?? ''); + $log = array_merge_recursive($this->log, $log); + $output = $trace->output($this->app, $response, $log); + if (is_string($output)) { + // trace调试信息注入 + $pos = strripos($content, ''); + if (false !== $pos) { + $content = substr($content, 0, $pos) . $output . substr($content, $pos); + } else { + $content = $content . $output; + } + } + } + } +} diff --git a/src/config.php b/src/config.php new file mode 100644 index 0000000..52211ec --- /dev/null +++ b/src/config.php @@ -0,0 +1,10 @@ + 'Html', + // 读取的日志通道名 + 'channel' => '', +];