mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-30 02:14:43 +08:00
Feature/error request logging (#401)
* feat(proxy): add error mapper for HTTP status code mapping - Add error_mapper.rs module to map ProxyError to HTTP status codes - Implement map_proxy_error_to_status() for error classification - Implement get_error_message() for user-friendly error messages - Support all error types: upstream, timeout, connection, provider failures - Include comprehensive unit tests for all mappings * feat(proxy): enhance error logging with context support - Add log_error_with_context() method for detailed error recording - Support streaming flag, session_id, and provider_type fields - Remove dead_code warning from log_error() method - Enable comprehensive error request tracking in database * feat(proxy): implement error capture and logging in all handlers - Capture and log all failed requests in handle_messages (Claude) - Capture and log all failed requests in handle_gemini (Gemini) - Capture and log all failed requests in handle_responses (Codex) - Capture and log all failed requests in handle_chat_completions (Codex) - Record error status codes, messages, and latency for all failures - Generate unique session_id for each request - Support both streaming and non-streaming error scenarios * style: fix clippy warnings and typescript errors - Add allow(dead_code) for CircuitBreaker::get_state (reserved for future) - Fix all uninlined format string warnings (27 instances) - Use inline format syntax for better readability - Fix unused import and parameter warnings in ProviderActions.tsx - Achieve zero warnings in both Rust and TypeScript * style: apply code formatting - Remove trailing whitespace in misc.rs - Add trailing comma in App.tsx - Format multi-line className in ProviderCard.tsx * feat(proxy): add settings button to proxy panel Add configuration buttons in both running and stopped states to provide easy access to proxy settings dialog. * fix(speedtest): skip client build for invalid inputs * chore(clippy): fix uninlined format args * Merge branch 'main' into feature/error-request-logging
This commit is contained in:
@@ -142,8 +142,7 @@ impl ProxyService {
|
||||
log::warn!("同步 Claude Token 到数据库失败: {e}");
|
||||
} else {
|
||||
log::info!(
|
||||
"已同步 Claude Token 到数据库 (provider: {})",
|
||||
provider_id
|
||||
"已同步 Claude Token 到数据库 (provider: {provider_id})"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -182,8 +181,7 @@ impl ProxyService {
|
||||
log::warn!("同步 Codex Token 到数据库失败: {e}");
|
||||
} else {
|
||||
log::info!(
|
||||
"已同步 Codex Token 到数据库 (provider: {})",
|
||||
provider_id
|
||||
"已同步 Codex Token 到数据库 (provider: {provider_id})"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -222,8 +220,7 @@ impl ProxyService {
|
||||
log::warn!("同步 Gemini Token 到数据库失败: {e}");
|
||||
} else {
|
||||
log::info!(
|
||||
"已同步 Gemini Token 到数据库 (provider: {})",
|
||||
provider_id
|
||||
"已同步 Gemini Token 到数据库 (provider: {provider_id})"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -354,7 +351,7 @@ impl ProxyService {
|
||||
});
|
||||
}
|
||||
self.write_claude_live(&live_config)?;
|
||||
log::info!("Claude Live 配置已接管,代理地址: {}", proxy_url);
|
||||
log::info!("Claude Live 配置已接管,代理地址: {proxy_url}");
|
||||
}
|
||||
|
||||
// Codex: 修改 config.toml 的 base_url,auth.json 的 OPENAI_API_KEY(代理会注入真实 Token)
|
||||
@@ -373,7 +370,7 @@ impl ProxyService {
|
||||
live_config["config"] = json!(updated_config);
|
||||
|
||||
self.write_codex_live(&live_config)?;
|
||||
log::info!("Codex Live 配置已接管,代理地址: {}", proxy_url);
|
||||
log::info!("Codex Live 配置已接管,代理地址: {proxy_url}");
|
||||
}
|
||||
|
||||
// Gemini: 修改 GOOGLE_GEMINI_BASE_URL,使用占位符替代真实 Token(代理会注入真实 Token)
|
||||
@@ -389,7 +386,7 @@ impl ProxyService {
|
||||
});
|
||||
}
|
||||
self.write_gemini_live(&live_config)?;
|
||||
log::info!("Gemini Live 配置已接管,代理地址: {}", proxy_url);
|
||||
log::info!("Gemini Live 配置已接管,代理地址: {proxy_url}");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -513,11 +510,7 @@ impl ProxyService {
|
||||
.set_current_provider(app_type_enum.as_str(), provider_id)
|
||||
.map_err(|e| format!("更新当前供应商失败: {e}"))?;
|
||||
|
||||
log::info!(
|
||||
"代理模式:已切换 {} 的目标供应商为 {}",
|
||||
app_type,
|
||||
provider_id
|
||||
);
|
||||
log::info!("代理模式:已切换 {app_type} 的目标供应商为 {provider_id}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -721,11 +714,7 @@ impl ProxyService {
|
||||
server
|
||||
.reset_provider_circuit_breaker(provider_id, app_type)
|
||||
.await;
|
||||
log::info!(
|
||||
"已重置 Provider {} (app: {}) 的熔断器",
|
||||
provider_id,
|
||||
app_type
|
||||
);
|
||||
log::info!("已重置 Provider {provider_id} (app: {app_type}) 的熔断器");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -31,40 +31,51 @@ impl SpeedtestService {
|
||||
return Ok(vec![]);
|
||||
}
|
||||
|
||||
let mut results: Vec<Option<EndpointLatency>> = vec![None; urls.len()];
|
||||
let mut valid_targets = Vec::new();
|
||||
|
||||
for (idx, raw_url) in urls.into_iter().enumerate() {
|
||||
let trimmed = raw_url.trim().to_string();
|
||||
|
||||
if trimmed.is_empty() {
|
||||
results[idx] = Some(EndpointLatency {
|
||||
url: raw_url,
|
||||
latency: None,
|
||||
status: None,
|
||||
error: Some("URL 不能为空".to_string()),
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
match Url::parse(&trimmed) {
|
||||
Ok(parsed_url) => valid_targets.push((idx, trimmed, parsed_url)),
|
||||
Err(err) => {
|
||||
results[idx] = Some(EndpointLatency {
|
||||
url: trimmed,
|
||||
latency: None,
|
||||
status: None,
|
||||
error: Some(format!("URL 无效: {err}")),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if valid_targets.is_empty() {
|
||||
return Ok(results.into_iter().flatten().collect::<Vec<_>>());
|
||||
}
|
||||
|
||||
let timeout = Self::sanitize_timeout(timeout_secs);
|
||||
let client = Self::build_client(timeout)?;
|
||||
|
||||
let tasks = urls.into_iter().map(|raw_url| {
|
||||
let tasks = valid_targets.into_iter().map(|(idx, trimmed, parsed_url)| {
|
||||
let client = client.clone();
|
||||
async move {
|
||||
let trimmed = raw_url.trim().to_string();
|
||||
if trimmed.is_empty() {
|
||||
return EndpointLatency {
|
||||
url: raw_url,
|
||||
latency: None,
|
||||
status: None,
|
||||
error: Some("URL 不能为空".to_string()),
|
||||
};
|
||||
}
|
||||
|
||||
let parsed_url = match Url::parse(&trimmed) {
|
||||
Ok(url) => url,
|
||||
Err(err) => {
|
||||
return EndpointLatency {
|
||||
url: trimmed,
|
||||
latency: None,
|
||||
status: None,
|
||||
error: Some(format!("URL 无效: {err}")),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
// 先进行一次热身请求,忽略结果,仅用于复用连接/绕过首包惩罚。
|
||||
let _ = client.get(parsed_url.clone()).send().await;
|
||||
|
||||
// 第二次请求开始计时,并将其作为结果返回。
|
||||
let start = Instant::now();
|
||||
match client.get(parsed_url).send().await {
|
||||
let latency = match client.get(parsed_url).send().await {
|
||||
Ok(resp) => EndpointLatency {
|
||||
url: trimmed,
|
||||
latency: Some(start.elapsed().as_millis()),
|
||||
@@ -88,11 +99,17 @@ impl SpeedtestService {
|
||||
error: Some(error_message),
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
(idx, latency)
|
||||
}
|
||||
});
|
||||
|
||||
Ok(join_all(tasks).await)
|
||||
for (idx, latency) in join_all(tasks).await {
|
||||
results[idx] = Some(latency);
|
||||
}
|
||||
|
||||
Ok(results.into_iter().flatten().collect::<Vec<_>>())
|
||||
}
|
||||
|
||||
fn build_client(timeout_secs: u64) -> Result<Client, AppError> {
|
||||
|
||||
Reference in New Issue
Block a user