config = array_merge($this->config, $config); } try { $this->initConnect(); } catch (\Throwable $th) { $this->pdo = null; $this->initFile(); } $this->tableName = $config['prefix'] . 'debug_log'; } public function save(array $log): bool { $app_name = app('http')->getName() ?: ''; $controller_name = ''; $action_name = ''; if (App::runningInConsole()) { $app_name = 'cli'; } else { $controller_name = request()->controller(); $action_name = request()->action(); } $create_time = time(); $create_time_title = date('Y-m-d H:i:s', $create_time); $log_key = ''; if (defined('REUQEST_UID')) { $log_key = REUQEST_UID; } else { $log_key = uniqid(); } foreach ($log as $log_item) { // 适配 ThinkPHP 8.x 的 LogRecord 对象格式 // 兼容旧格式:包含 type 和 message 属性的对象 if (is_object($log_item) && isset($log_item->type) && isset($log_item->message)) { $log_level = $log_item->type; $log_content = $log_item->message; } else { continue; } if (!is_string($log_content)) { $log_content = print_r($log_content, true); } $log_data = [ 'level' => $log_level, 'content' => $log_content, 'create_time' => $create_time, 'create_time_title' => $create_time_title, 'uid' => $log_key, 'app_name' => $app_name, 'controller_name' => $controller_name, 'action_name' => $action_name, ]; try { if (!is_null($this->pdo)) { $this->saveByConnect($log_data); } else { $this->saveByFile($log_data); } } catch (\Throwable $th) { $this->saveByFile($log_data); } } return true; } protected function saveByConnect($log_data) { if (is_null($this->pdo)) { $this->saveByFile($log_data); return; } $this->devLog('save by connect'); $prepare_name = []; foreach ($log_data as $key => $value) { $prepare_name[] = ':' . $key; } $data_keys = array_keys($log_data); $data_keys_in_sql = implode(',', $data_keys); $prepare_name_in_sql = implode(',', $prepare_name); $sql = "INSERT INTO {$this->tableName} ($data_keys_in_sql) VALUES ($prepare_name_in_sql);"; try { $stmt = $this->pdo->prepare($sql); $stmt->execute($log_data); } catch (\Exception $th) { if ($this->isBreak($th)) { if ($this->reConnectTimes > 3) { $this->initFile(); throw $th; } $this->initConnect(); $this->reConnectTimes++; $this->devLog('reconnect ' . $this->reConnectTimes); $this->saveByConnect($log_data); } else { $this->saveByFile($log_data); } } } protected function saveByFile($log_data) { $this->devLog('save by file'); // 如果文件日志超过100条,尝试重新通过数据库连接 if ($this->fileLogTimes > 10) { $this->fileLogTimes = 0; $this->initConnect(); $this->saveByConnect($log_data); return; } try { fputcsv($this->fileRescource, $log_data); $this->fileLogTimes++; } catch (\Throwable $th) { $this->initFile(); $this->fileLogTimes++; $this->saveByFile($log_data); } } protected function initConnect() { $this->devLog('init connect'); if (!is_null($this->pdo)) { $this->pdo = null; } $this->reConnectTimes = 0; $config = $this->config; $dsn = $this->parseDsn($config); try { $pdo = $this->createPdo($dsn, $config['username'], $config['password'], $config['params']); $this->pdo = $pdo; } catch (\Throwable $th) { $this->pdo = null; } return $this; } protected function initFile() { $this->devLog('init file'); if (!is_null($this->fileRescource)) { return $this; } $log_path = App::getRuntimePath() . 'log/' . date('ymd') . '.csv'; $dirname = dirname($log_path); if (!is_dir($dirname)) { mkdir($dirname, 0777, true); } $first_line = false; if (!file_exists($log_path)) { $first_line = true; } $this->fileRescource = fopen($log_path, 'a'); if ($first_line) { $fields = [ 'level', 'content', 'create_time', 'create_time_title', 'uid', 'app_name', 'controller_name', 'action_name', ]; fputcsv($this->fileRescource, $fields); } return $this; } /** * 是否断线 * * @param \PDOException|\Exception $e 异常对象 * * @return bool */ protected function isBreak($e): bool { $error = $e->getMessage(); foreach ($this->breakMatchStr as $msg) { if (false !== stripos($error, $msg)) { return true; } } return false; } /** * 解析pdo连接的dsn信息. * @param array $config 连接信息 * @return string */ protected function parseDsn(array $config): string { if (!empty($config['socket'])) { $dsn = 'mysql:unix_socket=' . $config['socket']; } elseif (!empty($config['hostport'])) { $dsn = 'mysql:host=' . $config['hostname'] . ';port=' . $config['hostport']; } else { $dsn = 'mysql:host=' . $config['hostname']; } $dsn .= ';dbname=' . $config['database']; if (!empty($config['charset'])) { $dsn .= ';charset=' . $config['charset']; } return $dsn; } protected function createPdo($dsn, $username, $password, $params) { return new PDO($dsn, $username, $password, $params); } public function __destruct() { $this->pdo = null; if (!is_null($this->fileRescource)) { fclose($this->fileRescource); } } protected function devLog($content) { if ($this->devMode) { dump($content); } } }