README.md000064400000001074144760111500006024 0ustar00 # webman-statistic 应用监控插件 ## 简介 > 用于监控上报 [webman](https://github.com/walkor/webman) 应用调用、请求量、调用耗时、状态码异常、sql日志、redis日志等。配合 [TransferStatistics](https://github.com/hsk99/transfer-statistics) v2+ 使用查看上报数据 ## 安装 ` composer require hsk99/webman-statistic ` ## 配置 修改 ` config/plugin/hsk99/statistic/app.php ` 文件,设置相关参数 ## 注意 ` illuminate/database ` 、` illuminate/redis ` 日志需要安装 ` composer require illuminate/events `composer.json000064400000000540144760111500007264 0ustar00{ "name": "hsk99/webman-statistic", "type": "library", "license": "MIT", "homepage": "http://hsk99.com.cn", "authors": [ { "name": "hsk99", "email": "hsk@hsk99.com.cn", "homepage": "http://hsk99.com.cn" } ], "require": { "workerman/http-client": "^1.0" }, "autoload": { "psr-4": { "Hsk99\\WebmanStatistic\\": "src" } } }src/Bootstrap.php000064400000002516144760111500010024 0ustar00 128, // 每个地址最多维持多少并发连接 'keepalive_timeout' => 15, // 连接多长时间不通讯就关闭 'connect_timeout' => 30, // 连接超时时间 'timeout' => 30, // 等待响应的超时时间 ]; self::$_instance = new \Workerman\Http\Client($options); \Workerman\Timer::add(config('plugin.hsk99.statistic.app.interval', 30), function () { \Hsk99\WebmanStatistic\Statistic::report(); }); $worker->onWorkerStop = function () { \Hsk99\WebmanStatistic\Statistic::report(); }; } } /** * @author HSK * @date 2022-06-17 15:34:41 * * @return \Workerman\Http\Client */ public static function instance(): \Workerman\Http\Client { return self::$_instance; } } src/Install.php000064400000003017144760111500007452 0ustar00 'config/plugin/hsk99/statistic', ]; /** * Install * @return void */ public static function install() { static::installByRelation(); } /** * Uninstall * @return void */ public static function uninstall() { self::uninstallByRelation(); } /** * installByRelation * @return void */ public static function installByRelation() { foreach (static::$pathRelation as $source => $dest) { if ($pos = strrpos($dest, '/')) { $parent_dir = base_path() . '/' . substr($dest, 0, $pos); if (!is_dir($parent_dir)) { mkdir($parent_dir, 0777, true); } } //symlink(__DIR__ . "/$source", base_path()."/$dest"); copy_dir(__DIR__ . "/$source", base_path() . "/$dest"); } } /** * uninstallByRelation * @return void */ public static function uninstallByRelation() { foreach (static::$pathRelation as $source => $dest) { $path = base_path() . "/$dest"; if (!is_dir($path) && !is_file($path)) { continue; } /*if (is_link($path) { unlink($path); }*/ remove_dir($path); } } } src/Middleware.php000064400000014773144760111500010134 0ustar00getRealIp($safe_mode = true); $transfer = $request->controller . '::' . $request->action; if ('::' === $transfer) { $transfer = $request->path(); } /** * @var \Webman\Http\Response */ $response = $next($request); $finishTime = microtime(true); $costTime = $finishTime - $startTime; static $initialized; if (!$initialized) { if (class_exists(\think\facade\Db::class)) { \think\facade\Db::listen(function ($sql, $runtime, $master) { if ($sql === 'select 1' || !is_numeric($runtime)) { return; } $this->sqlLogs[] = trim($sql) . " [ RunTime: " . $runtime * 1000 . " ms ]"; }); } if (class_exists(\Illuminate\Database\Events\QueryExecuted::class)) { foreach (config('database.connections', []) as $key => $config) { $driver = $config['driver'] ?? 'mysql'; try { \support\Db::connection($key)->listen(function (\Illuminate\Database\Events\QueryExecuted $query) use ($key, $driver) { $sql = trim($query->sql); if (strtolower($sql) === 'select 1') { return; } $sql = str_replace("?", "%s", $sql); foreach ($query->bindings as $i => $binding) { if ($binding instanceof \DateTime) { $query->bindings[$i] = $binding->format("'Y-m-d H:i:s'"); } else { if (is_string($binding)) { $query->bindings[$i] = "'$binding'"; } } } $log = vsprintf($sql, $query->bindings); $this->sqlLogs[] = "[driver:$driver] [connection:$key] $log [ RunTime: {$query->time} ms ]"; }); } catch (\Throwable $e) { } } } if (class_exists(\Illuminate\Redis\Events\CommandExecuted::class)) { foreach (config('redis', []) as $key => $config) { if (strpos($key, 'redis-queue') !== false) { continue; } try { \support\Redis::connection($key)->listen(function (\Illuminate\Redis\Events\CommandExecuted $command) use ($key) { $this->redisLogs[] = "[connection:$key] Redis::{$command->command}('" . implode('\', \'', $command->parameters) . "') [ RunTime: {$command->time} ms ]"; }); } catch (\Throwable $e) { } } } $initialized = true; } switch (true) { case method_exists($response, 'exception') && $exception = $response->exception(): $body = (string)$exception; break; case 'application/json' === strtolower($response->getHeader('Content-Type')): $body = json_decode($response->rawBody(), true); break; default: $body = 'Non Json data'; break; } $code = $response->getStatusCode(); $success = $code < 400; $details = [ 'time' => date('Y-m-d H:i:s.', (int)$startTime) . substr($startTime, 11), // 请求时间(包含毫秒时间) 'run_time' => $costTime, // 运行时长 'ip' => $request->getRealIp($safe_mode = true) ?? '', // 请求客户端IP 'url' => $request->fullUrl() ?? '', // 请求URL 'method' => $request->method() ?? '', // 请求方法 'request_param' => $request->all() ?? [], // 请求参数 'request_header' => $request->header() ?? [], // 请求头 'cookie' => $request->cookie() ?? [], // 请求cookie 'session' => $request->session()->all() ?? [], // 请求session 'response_code' => $response->getStatusCode() ?? '', // 响应码 'response_header' => $response->getHeaders() ?? [], // 响应头 'response_body' => $body, // 响应数据 'sql' => $this->sqlLogs, // 运行SQL 'redis' => $this->redisLogs, // 运行Redis ]; $this->sqlLogs = []; $this->redisLogs = []; \Hsk99\WebmanStatistic\Statistic::$transfer .= json_encode([ 'time' => date('Y-m-d H:i:s.', (int)$startTime) . substr($startTime, 11), 'project' => $project, 'ip' => $ip, 'transfer' => $transfer, 'costTime' => $costTime, 'success' => $success ? 1 : 0, 'code' => $code, 'details' => json_encode($details, 320), ], 320) . "\n"; if (strlen(\Hsk99\WebmanStatistic\Statistic::$transfer) > 1024 * 1024) { \Hsk99\WebmanStatistic\Statistic::report(); } return $response; } } src/Statistic.php000064400000002274144760111500010017 0ustar00request( config('plugin.hsk99.statistic.app.address'), [ 'method' => 'POST', 'version' => '1.1', 'headers' => [ 'Connection' => 'keep-alive', 'authorization' => config('plugin.hsk99.statistic.app.authorization', md5(date('Y'))) ], 'data' => [ 'transfer' => static::$transfer ], 'success' => function ($response) { }, 'error' => function ($exception) { } ] ); static::$transfer = ''; } catch (\Throwable $th) { } } } src/config/plugin/hsk99/statistic/app.php000064400000000656144760111500014353 0ustar00 true, 'project' => 'webman', // 应用名 'interval' => 30, // 上报间隔 'address' => 'http://127.0.0.1:8788/report/statistic/transfer', // 上报地址 'authorization' => null // 上报认证key,默认 null ]; src/config/plugin/hsk99/statistic/bootstrap.php000064400000000077144760111500015605 0ustar00 [ Hsk99\WebmanStatistic\Middleware::class, ] ]; src/config/plugin/hsk99/statistic/route.php000064400000005777144760111500014742 0ustar00getRealIp($safe_mode = true); $transfer = $request->path(); if ($oldFallback instanceof \Closure) { $response = $oldFallback($request); } else { $response = new Response(404, [], \file_get_contents(public_path() . '/404.html')); } $finishTime = microtime(true); $costTime = $finishTime - $startTime; switch (true) { case method_exists($response, 'exception') && $exception = $response->exception(): $body = (string)$exception; break; case 'application/json' === strtolower($response->getHeader('Content-Type')): $body = json_decode($response->rawBody(), true); break; default: $body = 'Non Json data'; break; } $code = $response->getStatusCode(); $success = $code < 400; $details = [ 'time' => date('Y-m-d H:i:s.', (int)$startTime) . substr($startTime, 11), // 请求时间(包含毫秒时间) 'run_time' => $costTime, // 运行时长 'ip' => $request->getRealIp($safe_mode = true) ?? '', // 请求客户端IP 'url' => $request->fullUrl() ?? '', // 请求URL 'method' => $request->method() ?? '', // 请求方法 'request_param' => $request->all() ?? [], // 请求参数 'request_header' => $request->header() ?? [], // 请求头 'cookie' => $request->cookie() ?? [], // 请求cookie 'session' => $request->session()->all() ?? [], // 请求session 'response_code' => $response->getStatusCode() ?? '', // 响应码 'response_header' => $response->getHeaders() ?? [], // 响应头 'response_body' => $body, // 响应数据 ]; Statistic::$transfer .= json_encode([ 'time' => date('Y-m-d H:i:s.', (int)$startTime) . substr($startTime, 11), 'project' => $project, 'ip' => $ip, 'transfer' => $transfer, 'costTime' => $costTime, 'success' => $success ? 1 : 0, 'code' => $code, 'details' => json_encode($details, 320), ], 320) . "\n"; if (strlen(\Hsk99\WebmanStatistic\Statistic::$transfer) > 1024 * 1024) { \Hsk99\WebmanStatistic\Statistic::report(); } return $response; });