diff --git a/src/routes/openaiRoutes.js b/src/routes/openaiRoutes.js index 07c7bcdc..a18f6d4a 100644 --- a/src/routes/openaiRoutes.js +++ b/src/routes/openaiRoutes.js @@ -168,7 +168,9 @@ router.post('/responses', authenticateApiKey, async (req, res) => { // 如果有代理,添加代理配置 if (proxyAgent) { axiosConfig.httpsAgent = proxyAgent - logger.info('Using proxy for OpenAI request') + logger.info(`🌐 Using proxy for OpenAI request: ${ProxyHelper.getProxyDescription(proxy)}`) + } else { + logger.debug('🌐 No proxy configured for OpenAI request') } // 根据 stream 参数决定请求类型 diff --git a/src/services/claudeAccountService.js b/src/services/claudeAccountService.js index 1195c370..7ef2c2d9 100644 --- a/src/services/claudeAccountService.js +++ b/src/services/claudeAccountService.js @@ -862,7 +862,17 @@ class ClaudeAccountService { // 🌐 创建代理agent(使用统一的代理工具) _createProxyAgent(proxyConfig) { - return ProxyHelper.createProxyAgent(proxyConfig) + const proxyAgent = ProxyHelper.createProxyAgent(proxyConfig) + if (proxyAgent) { + logger.info( + `🌐 Using proxy for Claude request: ${ProxyHelper.getProxyDescription(proxyConfig)}` + ) + } else if (proxyConfig) { + logger.debug('🌐 Failed to create proxy agent for Claude') + } else { + logger.debug('🌐 No proxy configured for Claude request') + } + return proxyAgent } // 🔐 加密敏感数据 diff --git a/src/services/claudeConsoleAccountService.js b/src/services/claudeConsoleAccountService.js index 73dc50b3..30b53fff 100644 --- a/src/services/claudeConsoleAccountService.js +++ b/src/services/claudeConsoleAccountService.js @@ -481,7 +481,17 @@ class ClaudeConsoleAccountService { // 🌐 创建代理agent(使用统一的代理工具) _createProxyAgent(proxyConfig) { - return ProxyHelper.createProxyAgent(proxyConfig) + const proxyAgent = ProxyHelper.createProxyAgent(proxyConfig) + if (proxyAgent) { + logger.info( + `🌐 Using proxy for Claude Console request: ${ProxyHelper.getProxyDescription(proxyConfig)}` + ) + } else if (proxyConfig) { + logger.debug('🌐 Failed to create proxy agent for Claude Console') + } else { + logger.debug('🌐 No proxy configured for Claude Console request') + } + return proxyAgent } // 🔐 加密敏感数据 diff --git a/src/services/claudeRelayService.js b/src/services/claudeRelayService.js index dcf087ff..cb7949bd 100644 --- a/src/services/claudeRelayService.js +++ b/src/services/claudeRelayService.js @@ -502,10 +502,17 @@ class ClaudeRelayService { const account = accountData.find((acc) => acc.id === accountId) if (!account || !account.proxy) { + logger.debug('🌐 No proxy configured for Claude account') return null } - return ProxyHelper.createProxyAgent(account.proxy) + const proxyAgent = ProxyHelper.createProxyAgent(account.proxy) + if (proxyAgent) { + logger.info( + `🌐 Using proxy for Claude request: ${ProxyHelper.getProxyDescription(account.proxy)}` + ) + } + return proxyAgent } catch (error) { logger.warn('⚠️ Failed to create proxy agent:', error) return null diff --git a/src/services/geminiAccountService.js b/src/services/geminiAccountService.js index a63ffc99..58d0b76d 100644 --- a/src/services/geminiAccountService.js +++ b/src/services/geminiAccountService.js @@ -5,6 +5,7 @@ const config = require('../../config/config') const logger = require('../utils/logger') const { OAuth2Client } = require('google-auth-library') const { maskToken } = require('../utils/tokenMask') +const ProxyHelper = require('../utils/proxyHelper') const { logRefreshStart, logRefreshSuccess, @@ -228,7 +229,7 @@ async function exchangeCodeForTokens(code, redirectUri = null, codeVerifier = nu } // 刷新访问令牌 -async function refreshAccessToken(refreshToken) { +async function refreshAccessToken(refreshToken, proxyConfig = null) { const oAuth2Client = createOAuth2Client() try { @@ -237,6 +238,20 @@ async function refreshAccessToken(refreshToken) { refresh_token: refreshToken }) + // 配置代理(如果提供) + const proxyAgent = ProxyHelper.createProxyAgent(proxyConfig) + if (proxyAgent) { + // Google Auth Library 使用 Axios,可以通过 transportOptions 配置代理 + oAuth2Client.transportOptions = { + httpsAgent: proxyAgent + } + logger.info( + `🔄 Using proxy for Gemini token refresh: ${ProxyHelper.maskProxyInfo(proxyConfig)}` + ) + } else { + logger.debug('🔄 No proxy configured for Gemini token refresh') + } + // 调用 refreshAccessToken 获取新的 tokens const response = await oAuth2Client.refreshAccessToken() const { credentials } = response @@ -261,7 +276,9 @@ async function refreshAccessToken(refreshToken) { logger.error('Error refreshing access token:', { message: error.message, code: error.code, - response: error.response?.data + response: error.response?.data, + hasProxy: !!proxyConfig, + proxy: proxyConfig ? ProxyHelper.maskProxyInfo(proxyConfig) : 'No proxy' }) throw new Error(`Failed to refresh access token: ${error.message}`) } @@ -786,7 +803,8 @@ async function refreshAccountToken(accountId) { logger.info(`🔄 Starting token refresh for Gemini account: ${account.name} (${accountId})`) // account.refreshToken 已经是解密后的值(从 getAccount 返回) - const newTokens = await refreshAccessToken(account.refreshToken) + // 传入账户的代理配置 + const newTokens = await refreshAccessToken(account.refreshToken, account.proxy) // 更新账户信息 const updates = { diff --git a/src/services/geminiRelayService.js b/src/services/geminiRelayService.js index 0a3f560b..60030d3e 100644 --- a/src/services/geminiRelayService.js +++ b/src/services/geminiRelayService.js @@ -280,7 +280,9 @@ async function sendGeminiRequest({ const proxyAgent = createProxyAgent(proxy) if (proxyAgent) { axiosConfig.httpsAgent = proxyAgent - logger.debug('Using proxy for Gemini request') + logger.info(`🌐 Using proxy for Gemini API request: ${ProxyHelper.getProxyDescription(proxy)}`) + } else { + logger.debug('🌐 No proxy configured for Gemini API request') } // 添加 AbortController 信号支持 @@ -386,6 +388,11 @@ async function getAvailableModels(accessToken, proxy, projectId, location = 'us- const proxyAgent = createProxyAgent(proxy) if (proxyAgent) { axiosConfig.httpsAgent = proxyAgent + logger.info( + `🌐 Using proxy for Gemini models request: ${ProxyHelper.getProxyDescription(proxy)}` + ) + } else { + logger.debug('🌐 No proxy configured for Gemini models request') } try { @@ -482,7 +489,11 @@ async function countTokens({ const proxyAgent = createProxyAgent(proxy) if (proxyAgent) { axiosConfig.httpsAgent = proxyAgent - logger.debug('Using proxy for Gemini countTokens request') + logger.info( + `🌐 Using proxy for Gemini countTokens request: ${ProxyHelper.getProxyDescription(proxy)}` + ) + } else { + logger.debug('🌐 No proxy configured for Gemini countTokens request') } try { diff --git a/src/services/openaiAccountService.js b/src/services/openaiAccountService.js index d4d1abae..1e88cdec 100644 --- a/src/services/openaiAccountService.js +++ b/src/services/openaiAccountService.js @@ -135,6 +135,11 @@ async function refreshAccessToken(refreshToken, proxy = null) { const proxyAgent = ProxyHelper.createProxyAgent(proxy) if (proxyAgent) { requestOptions.httpsAgent = proxyAgent + logger.info( + `🌐 Using proxy for OpenAI token refresh: ${ProxyHelper.getProxyDescription(proxy)}` + ) + } else { + logger.debug('🌐 No proxy configured for OpenAI token refresh') } // 发送请求 diff --git a/src/utils/logger.js b/src/utils/logger.js index 97a345bc..ac4cd618 100644 --- a/src/utils/logger.js +++ b/src/utils/logger.js @@ -5,7 +5,7 @@ const path = require('path') const fs = require('fs') const os = require('os') -// 安全的 JSON 序列化函数,处理循环引用 +// 安全的 JSON 序列化函数,处理循环引用和特殊字符 const safeStringify = (obj, maxDepth = 3, fullDepth = false) => { const seen = new WeakSet() // 如果是fullDepth模式,增加深度限制 @@ -16,6 +16,28 @@ const safeStringify = (obj, maxDepth = 3, fullDepth = false) => { return '[Max Depth Reached]' } + // 处理字符串值,清理可能导致JSON解析错误的特殊字符 + if (typeof value === 'string') { + try { + // 移除或转义可能导致JSON解析错误的字符 + let cleanValue = value + // eslint-disable-next-line no-control-regex + .replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/g, '') // 移除控制字符 + .replace(/[\uD800-\uDFFF]/g, '') // 移除孤立的代理对字符 + // eslint-disable-next-line no-control-regex + .replace(/\u0000/g, '') // 移除NUL字节 + + // 如果字符串过长,截断并添加省略号 + if (cleanValue.length > 1000) { + cleanValue = `${cleanValue.substring(0, 997)}...` + } + + return cleanValue + } catch (error) { + return '[Invalid String Data]' + } + } + if (value !== null && typeof value === 'object') { if (seen.has(value)) { return '[Circular Reference]' @@ -40,7 +62,10 @@ const safeStringify = (obj, maxDepth = 3, fullDepth = false) => { } else { const result = {} for (const [k, v] of Object.entries(value)) { - result[k] = replacer(k, v, depth + 1) + // 确保键名也是安全的 + // eslint-disable-next-line no-control-regex + const safeKey = typeof k === 'string' ? k.replace(/[\u0000-\u001F\u007F]/g, '') : k + result[safeKey] = replacer(safeKey, v, depth + 1) } return result } @@ -50,9 +75,20 @@ const safeStringify = (obj, maxDepth = 3, fullDepth = false) => { } try { - return JSON.stringify(replacer('', obj)) + const processed = replacer('', obj) + return JSON.stringify(processed) } catch (error) { - return JSON.stringify({ error: 'Failed to serialize object', message: error.message }) + // 如果JSON.stringify仍然失败,使用更保守的方法 + try { + return JSON.stringify({ + error: 'Failed to serialize object', + message: error.message, + type: typeof obj, + keys: obj && typeof obj === 'object' ? Object.keys(obj) : undefined + }) + } catch (finalError) { + return '{"error":"Critical serialization failure","message":"Unable to serialize any data"}' + } } } diff --git a/src/utils/oauthHelper.js b/src/utils/oauthHelper.js index 84cd2554..ac33b71e 100644 --- a/src/utils/oauthHelper.js +++ b/src/utils/oauthHelper.js @@ -157,6 +157,14 @@ async function exchangeCodeForTokens(authorizationCode, codeVerifier, state, pro const agent = createProxyAgent(proxyConfig) try { + if (agent) { + logger.info( + `🌐 Using proxy for OAuth token exchange: ${ProxyHelper.maskProxyInfo(proxyConfig)}` + ) + } else { + logger.debug('🌐 No proxy configured for OAuth token exchange') + } + logger.debug('🔄 Attempting OAuth token exchange', { url: OAUTH_CONFIG.TOKEN_URL, codeLength: cleanedCode.length, @@ -354,6 +362,14 @@ async function exchangeSetupTokenCode(authorizationCode, codeVerifier, state, pr const agent = createProxyAgent(proxyConfig) try { + if (agent) { + logger.info( + `🌐 Using proxy for Setup Token exchange: ${ProxyHelper.maskProxyInfo(proxyConfig)}` + ) + } else { + logger.debug('🌐 No proxy configured for Setup Token exchange') + } + logger.debug('🔄 Attempting Setup Token exchange', { url: OAUTH_CONFIG.TOKEN_URL, codeLength: cleanedCode.length, diff --git a/src/utils/proxyHelper.js b/src/utils/proxyHelper.js index 7bf2da86..ca409e62 100644 --- a/src/utils/proxyHelper.js +++ b/src/utils/proxyHelper.js @@ -163,6 +163,39 @@ class ProxyHelper { } } + /** + * 脱敏代理配置信息用于日志记录 + * @param {object|string} proxyConfig - 代理配置 + * @returns {string} 脱敏后的代理信息 + */ + static maskProxyInfo(proxyConfig) { + if (!proxyConfig) { + return 'No proxy' + } + + try { + const proxy = typeof proxyConfig === 'string' ? JSON.parse(proxyConfig) : proxyConfig + + let proxyDesc = `${proxy.type}://${proxy.host}:${proxy.port}` + + // 如果有认证信息,进行脱敏处理 + if (proxy.username && proxy.password) { + const maskedUsername = + proxy.username.length <= 2 + ? proxy.username + : proxy.username[0] + + '*'.repeat(Math.max(1, proxy.username.length - 2)) + + proxy.username.slice(-1) + const maskedPassword = '*'.repeat(Math.min(8, proxy.password.length)) + proxyDesc += ` (auth: ${maskedUsername}:${maskedPassword})` + } + + return proxyDesc + } catch (error) { + return 'Invalid proxy config' + } + } + /** * 创建代理 Agent(兼容旧的函数接口) * @param {object|string|null} proxyConfig - 代理配置