app,$conf['dontReport']['app'],true)){ return $next($request); } //跳过配置的path if(!empty($conf['dontReport']['path']) && is_array($conf['dontReport']['path'])){ $requestPath=$request->path(); foreach ($conf['dontReport']['path'] as $_path){ if(strpos($requestPath,$_path)===0){ return $next($request); } } } //跳过配置的控制器日志记录 if(!empty($conf['dontReport']['controller']) && is_array($conf['dontReport']['controller']) && in_array($request->controller,$conf['dontReport']['controller'],true)){ return $next($request); } //跳过配置的方法 if(!empty($conf['dontReport']['action']) && is_array($conf['dontReport']['action'])){ foreach ($conf['dontReport']['action'] as $_action){ if($_action[0]===$request->controller && $_action[1]===$request->action){ return $next($request); } } } // 请求开始时间 $start_time = microtime(true); // 记录ip 请求等信息 $logs = $request->getRealIp() . ' ' . $request->method() . ' ' . trim($request->fullUrl(), '/'); Context::get()->webmanLogs = ''; // 清理think-orm的日志 if (class_exists(ThinkDb::class, false) && class_exists(Mysql::class, false)) { ThinkDb::getDbLog(true); } // 初始化数据库监听 if (!$initialized_db) { $initialized_db = true; $this->initDbListen(); } // 初始化think-orm日志监听 if (!$initialized_think_orm) { try { ThinkDb::setLog(function ($type, $log) { Context::get()->webmanLogs = (Context::get()->webmanLogs ?? '') . "[SQL]\t" . trim($log) . PHP_EOL; }); } catch (Throwable $e) {} $initialized_think_orm = true; } // 得到响应 $response = $next($request); $time_diff = substr((microtime(true) - $start_time) * 1000, 0, 7); $logs .= " [{$time_diff}ms] [webman/log]" . PHP_EOL; if ($request->method() === 'POST') { $logs .= "[POST]\t" . var_export($request->post(), true) . PHP_EOL; } $logs = $logs . (Context::get()->webmanLogs ?? ''); // think-orm如果被使用,则记录think-orm的日志 if ($loaded_think_db = (class_exists(ThinkDb::class, false) && class_exists(Mysql::class, false))) { $sql_logs = ThinkDb::getDbLog(true); if (!empty($sql_logs['sql'])) { foreach ($sql_logs['sql'] as $sql) { $logs .= "[SQL]\t" . trim($sql) . PHP_EOL; } } } // 判断业务是否出现异常 $exception = null; if (method_exists($response, 'exception')) { $exception = $response->exception(); } // 尝试记录异常 $method = 'info'; if ($exception && config('plugin.webman.log.app.exception.enable', true) && !$this->shouldntReport($exception)) { $logs .= $exception . PHP_EOL; $method = 'error'; } // 判断Db是否有未提交的事务 $has_uncommitted_transaction = false; if (class_exists(Connection::class, false)) { if ($log = $this->checkDbUncommittedTransaction()) { $has_uncommitted_transaction = true; $method = 'error'; $logs .= $log; } } // 判断think-orm是否有未提交的事务 if ($loaded_think_db) { if ($log = $this->checkTpUncommittedTransaction()) { $has_uncommitted_transaction = true; $method = 'error'; $logs .= $log; } } /** * 初始化redis监听 * 注意:由于redis是延迟监听,所以第一个请求不会记录redis具体日志 */ $new_names = $this->tryInitRedisListen(); foreach ($new_names as $name) { $logs .= "[Redis]\t[connection:{$name}] ..." . PHP_EOL; } call_user_func([Log::class, $method], $logs); if ($has_uncommitted_transaction) { throw new RuntimeException('Uncommitted transactions found'); } return $response; } /** * 初始化数据库日志监听 * * @return void */ protected function initDbListen() { if (!class_exists(QueryExecuted::class)) { return; } try { $capsule = $this->getCapsule(); if (!$capsule) { return; } $dispatcher = $capsule->getEventDispatcher(); if (!$dispatcher) { $dispatcher = new Dispatcher(new Container); } $dispatcher->listen(QueryExecuted::class, function (QueryExecuted $query) { $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 = $sql; try { $log = vsprintf($sql, $query->bindings); } catch (\Throwable $e) {} Context::get()->webmanLogs = (Context::get()->webmanLogs ?? '') . "[SQL]\t[connection:{$query->connectionName}] $log [{$query->time} ms]" . PHP_EOL; }); $capsule->setEventDispatcher($dispatcher); } catch (\Throwable $e) { echo $e; } } /** * 尝试初始化redis日志监听 * * @return array */ protected function tryInitRedisListen(): array { static $listened_names = []; if (!class_exists(CommandExecuted::class)) { return []; } $new_names = []; try { foreach (Redis::instance()->connections() ?: [] as $connection) { /* @var \Illuminate\Redis\Connections\Connection $connection */ $name = $connection->getName(); if (isset($listened_names[$name])) { continue; } $connection->listen(function (CommandExecuted $command) { foreach ($command->parameters as &$item) { if (is_array($item)) { $item = implode('\', \'', $item); } } Context::get()->webmanLogs = (Context::get()->webmanLogs ?? '') . "[Redis]\t[connection:{$command->connectionName}] Redis::{$command->command}('" . implode('\', \'', $command->parameters) . "') ({$command->time} ms)" . PHP_EOL; }); $listened_names[$name] = $name; $new_names[] = $name; } } catch (Throwable $e) { echo $e; } return $new_names; } /** * 获得Db的Manager * * @return mixed */ protected function getCapsule() { static $capsule; if (!$capsule) { $reflect = new \ReflectionClass(Db::class); $property = $reflect->getProperty('instance'); $property->setAccessible(true); $capsule = $property->getValue(); } return $capsule; } /** * 检查Db是否有未提交的事务 * * @return string */ protected function checkDbUncommittedTransaction(): string { $logs = ''; try { foreach ($this->getCapsule()->getDatabaseManager()->getConnections() as $connection) { /* @var \Illuminate\Database\MySqlConnection $connection * */ if (\in_array($connection->getConfig('driver'), ['mysql', 'pgsql', 'sqlite', 'sqlsrv'])) { $pdo = $connection->getPdo(); if ($pdo && $pdo->inTransaction()) { $connection->rollBack(); $logs .= "[ERROR]\tUncommitted transaction found and try to rollback" . PHP_EOL; } } } } catch (Throwable $e) { echo $e; } return $logs; } /** * 检查think-orm是否有未提交的事务 * * @return string */ protected function checkTpUncommittedTransaction(): string { static $property, $manager_instance; $logs = ''; try { if (!$property) { if (class_exists(ThinkContainer::class, false)) { $manager_instance = ThinkContainer::getInstance()->make(DbManager::class); } else { $reflect = new \ReflectionClass(ThinkDb::class); $property = $reflect->getProperty('instance'); $property->setAccessible(true); $manager_instance = $property->getValue(); } $reflect = new \ReflectionClass($manager_instance); $property = $reflect->getProperty('instance'); $property->setAccessible(true); } $instances = $property->getValue($manager_instance); foreach ($instances as $connection) { /* @var \think\db\connector\Mysql $connection */ if (method_exists($connection, 'getPdo')) { $pdo = $connection->getPdo(); if ($pdo && $pdo->inTransaction()) { $connection->rollBack(); $logs .= "[ERROR]\tUncommitted transaction found and try to rollback" . PHP_EOL; } } } } catch (Throwable $e) { echo $e; } return $logs; } /** * 判断是否需要记录异常 * * @param Throwable $e * @return bool */ protected function shouldntReport($e): bool { foreach (config('plugin.webman.log.app.exception.dontReport', []) as $type) { if ($e instanceof $type) { return true; } } return false; } }