mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-30 02:14:43 +08:00
Refactor/simplify proxy logs (#585)
* refactor(proxy): simplify logging for better readability - Delete 17 verbose debug logs from handlers, streaming, and response_processor - Convert excessive INFO logs to DEBUG level for internal processing details - Add 2 critical INFO logs in forwarder.rs for failover scenarios: - Log when switching to next provider after failure - Log when all providers have been exhausted - Fix clippy uninlined_format_args warning This reduces log noise while maintaining visibility into key user-facing decisions. * fix: replace unsafe unwrap() calls with proper error handling - database/dao/mcp.rs: Use map_err for serde_json serialization - database/dao/providers.rs: Use map_err for settings_config and meta serialization - commands/misc.rs: Use expect() for compile-time regex pattern - services/prompt.rs: Use unwrap_or_default() for SystemTime - deeplink/provider.rs: Replace unwrap() with is_none_or pattern for Option checks Reduces potential panic points from 26 to 1 (static regex init, safe). * refactor(proxy): simplify verbose logging output - Remove response JSON full output logging in response_processor - Remove per-request INFO logs in provider_router (failover status, provider selection) - Change model mapping log from INFO to DEBUG - Change usage logging failure from INFO to WARN - Remove redundant debug logs for circuit breaker operations Reduces log noise significantly while preserving important warnings and errors. * feat(proxy): add structured log codes for i18n support Add error code system to proxy module logs for multi-language support: - CB-001~006: Circuit breaker state transitions and triggers - SRV-001~004: Proxy server lifecycle events - FWD-001~002: Request forwarding and failover - FO-001~005: Failover switch operations - USG-001~002: Usage logging errors Log format: [CODE] Chinese message Frontend/log tools can map codes to any language. New file: src/proxy/log_codes.rs - centralized code definitions * chore: bump version to 3.9.1 * style: format code with prettier and rustfmt * fix(ui): allow number inputs to be fully cleared before saving - Convert numeric state to string type for controlled inputs - Use isNaN() check instead of || fallback to allow 0 values - Apply fix to ProxyPanel, CircuitBreakerConfigPanel, AutoFailoverConfigPanel, and ModelTestConfigPanel * feat(pricing): support @ separator in model name matching - Refactor model name cleaning into chained method calls - Add @ to - replacement (e.g., gpt-5.2-codex@low → gpt-5.2-codex-low) - Add test case for @ separator matching * fix(proxy): improve validation and error handling in proxy config panels - Add StopTimeout/StopFailed error types for proper stop() error reporting - Replace silent clamp with validation-and-block in config panels - Add listenAddress format validation in ProxyPanel - Use log_codes constants instead of hardcoded strings - Use once_cell::Lazy for regex precompilation * fix(proxy): harden error handling and input validation - Handle RwLock poisoning in settings.rs with unwrap_or_else - Add fallback for dirs::home_dir() in config modules - Normalize localhost to 127.0.0.1 in ProxyPanel - Format IPv6 addresses with brackets for valid URLs - Strict port validation with pure digit regex - Treat NaN as validation failure in config panels - Log warning on cost_multiplier parse failure - Align timeoutSeconds range to [0, 300] across all panels
This commit is contained in:
@@ -21,52 +21,157 @@ export function AutoFailoverConfigPanel({
|
||||
const { data: config, isLoading, error } = useAppProxyConfig(appType);
|
||||
const updateConfig = useUpdateAppProxyConfig();
|
||||
|
||||
// 使用字符串状态以支持完全清空数字输入框
|
||||
const [formData, setFormData] = useState({
|
||||
autoFailoverEnabled: false,
|
||||
maxRetries: 3,
|
||||
streamingFirstByteTimeout: 30,
|
||||
streamingIdleTimeout: 60,
|
||||
nonStreamingTimeout: 300,
|
||||
circuitFailureThreshold: 5,
|
||||
circuitSuccessThreshold: 2,
|
||||
circuitTimeoutSeconds: 60,
|
||||
circuitErrorRateThreshold: 0.5,
|
||||
circuitMinRequests: 10,
|
||||
maxRetries: "3",
|
||||
streamingFirstByteTimeout: "30",
|
||||
streamingIdleTimeout: "60",
|
||||
nonStreamingTimeout: "300",
|
||||
circuitFailureThreshold: "5",
|
||||
circuitSuccessThreshold: "2",
|
||||
circuitTimeoutSeconds: "60",
|
||||
circuitErrorRateThreshold: "50", // 存储百分比值
|
||||
circuitMinRequests: "10",
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (config) {
|
||||
setFormData({
|
||||
autoFailoverEnabled: config.autoFailoverEnabled,
|
||||
maxRetries: config.maxRetries,
|
||||
streamingFirstByteTimeout: config.streamingFirstByteTimeout,
|
||||
streamingIdleTimeout: config.streamingIdleTimeout,
|
||||
nonStreamingTimeout: config.nonStreamingTimeout,
|
||||
circuitFailureThreshold: config.circuitFailureThreshold,
|
||||
circuitSuccessThreshold: config.circuitSuccessThreshold,
|
||||
circuitTimeoutSeconds: config.circuitTimeoutSeconds,
|
||||
circuitErrorRateThreshold: config.circuitErrorRateThreshold,
|
||||
circuitMinRequests: config.circuitMinRequests,
|
||||
maxRetries: String(config.maxRetries),
|
||||
streamingFirstByteTimeout: String(config.streamingFirstByteTimeout),
|
||||
streamingIdleTimeout: String(config.streamingIdleTimeout),
|
||||
nonStreamingTimeout: String(config.nonStreamingTimeout),
|
||||
circuitFailureThreshold: String(config.circuitFailureThreshold),
|
||||
circuitSuccessThreshold: String(config.circuitSuccessThreshold),
|
||||
circuitTimeoutSeconds: String(config.circuitTimeoutSeconds),
|
||||
circuitErrorRateThreshold: String(
|
||||
Math.round(config.circuitErrorRateThreshold * 100),
|
||||
),
|
||||
circuitMinRequests: String(config.circuitMinRequests),
|
||||
});
|
||||
}
|
||||
}, [config]);
|
||||
|
||||
const handleSave = async () => {
|
||||
if (!config) return;
|
||||
// 解析数字,返回 NaN 表示无效输入
|
||||
const parseNum = (val: string) => {
|
||||
const trimmed = val.trim();
|
||||
// 必须是纯数字
|
||||
if (!/^-?\d+$/.test(trimmed)) return NaN;
|
||||
return parseInt(trimmed);
|
||||
};
|
||||
|
||||
// 定义各字段的有效范围
|
||||
const ranges = {
|
||||
maxRetries: { min: 0, max: 10 },
|
||||
streamingFirstByteTimeout: { min: 0, max: 180 },
|
||||
streamingIdleTimeout: { min: 0, max: 600 },
|
||||
nonStreamingTimeout: { min: 0, max: 1800 },
|
||||
circuitFailureThreshold: { min: 1, max: 20 },
|
||||
circuitSuccessThreshold: { min: 1, max: 10 },
|
||||
circuitTimeoutSeconds: { min: 0, max: 300 },
|
||||
circuitErrorRateThreshold: { min: 0, max: 100 },
|
||||
circuitMinRequests: { min: 5, max: 100 },
|
||||
};
|
||||
|
||||
// 解析原始值
|
||||
const raw = {
|
||||
maxRetries: parseNum(formData.maxRetries),
|
||||
streamingFirstByteTimeout: parseNum(formData.streamingFirstByteTimeout),
|
||||
streamingIdleTimeout: parseNum(formData.streamingIdleTimeout),
|
||||
nonStreamingTimeout: parseNum(formData.nonStreamingTimeout),
|
||||
circuitFailureThreshold: parseNum(formData.circuitFailureThreshold),
|
||||
circuitSuccessThreshold: parseNum(formData.circuitSuccessThreshold),
|
||||
circuitTimeoutSeconds: parseNum(formData.circuitTimeoutSeconds),
|
||||
circuitErrorRateThreshold: parseNum(formData.circuitErrorRateThreshold),
|
||||
circuitMinRequests: parseNum(formData.circuitMinRequests),
|
||||
};
|
||||
|
||||
// 校验是否超出范围(NaN 也视为无效)
|
||||
const errors: string[] = [];
|
||||
const checkRange = (
|
||||
value: number,
|
||||
range: { min: number; max: number },
|
||||
label: string,
|
||||
) => {
|
||||
if (isNaN(value) || value < range.min || value > range.max) {
|
||||
errors.push(`${label}: ${range.min}-${range.max}`);
|
||||
}
|
||||
};
|
||||
|
||||
checkRange(
|
||||
raw.maxRetries,
|
||||
ranges.maxRetries,
|
||||
t("proxy.autoFailover.maxRetries", "最大重试次数"),
|
||||
);
|
||||
checkRange(
|
||||
raw.streamingFirstByteTimeout,
|
||||
ranges.streamingFirstByteTimeout,
|
||||
t("proxy.autoFailover.streamingFirstByte", "流式首字节超时"),
|
||||
);
|
||||
checkRange(
|
||||
raw.streamingIdleTimeout,
|
||||
ranges.streamingIdleTimeout,
|
||||
t("proxy.autoFailover.streamingIdle", "流式静默超时"),
|
||||
);
|
||||
checkRange(
|
||||
raw.nonStreamingTimeout,
|
||||
ranges.nonStreamingTimeout,
|
||||
t("proxy.autoFailover.nonStreaming", "非流式超时"),
|
||||
);
|
||||
checkRange(
|
||||
raw.circuitFailureThreshold,
|
||||
ranges.circuitFailureThreshold,
|
||||
t("proxy.autoFailover.failureThreshold", "失败阈值"),
|
||||
);
|
||||
checkRange(
|
||||
raw.circuitSuccessThreshold,
|
||||
ranges.circuitSuccessThreshold,
|
||||
t("proxy.autoFailover.successThreshold", "恢复成功阈值"),
|
||||
);
|
||||
checkRange(
|
||||
raw.circuitTimeoutSeconds,
|
||||
ranges.circuitTimeoutSeconds,
|
||||
t("proxy.autoFailover.timeout", "恢复等待时间"),
|
||||
);
|
||||
checkRange(
|
||||
raw.circuitErrorRateThreshold,
|
||||
ranges.circuitErrorRateThreshold,
|
||||
t("proxy.autoFailover.errorRate", "错误率阈值"),
|
||||
);
|
||||
checkRange(
|
||||
raw.circuitMinRequests,
|
||||
ranges.circuitMinRequests,
|
||||
t("proxy.autoFailover.minRequests", "最小请求数"),
|
||||
);
|
||||
|
||||
if (errors.length > 0) {
|
||||
toast.error(
|
||||
t("proxy.autoFailover.validationFailed", {
|
||||
fields: errors.join("; "),
|
||||
defaultValue: `以下字段超出有效范围: ${errors.join("; ")}`,
|
||||
}),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await updateConfig.mutateAsync({
|
||||
appType,
|
||||
enabled: config.enabled,
|
||||
autoFailoverEnabled: formData.autoFailoverEnabled,
|
||||
maxRetries: formData.maxRetries,
|
||||
streamingFirstByteTimeout: formData.streamingFirstByteTimeout,
|
||||
streamingIdleTimeout: formData.streamingIdleTimeout,
|
||||
nonStreamingTimeout: formData.nonStreamingTimeout,
|
||||
circuitFailureThreshold: formData.circuitFailureThreshold,
|
||||
circuitSuccessThreshold: formData.circuitSuccessThreshold,
|
||||
circuitTimeoutSeconds: formData.circuitTimeoutSeconds,
|
||||
circuitErrorRateThreshold: formData.circuitErrorRateThreshold,
|
||||
circuitMinRequests: formData.circuitMinRequests,
|
||||
maxRetries: raw.maxRetries,
|
||||
streamingFirstByteTimeout: raw.streamingFirstByteTimeout,
|
||||
streamingIdleTimeout: raw.streamingIdleTimeout,
|
||||
nonStreamingTimeout: raw.nonStreamingTimeout,
|
||||
circuitFailureThreshold: raw.circuitFailureThreshold,
|
||||
circuitSuccessThreshold: raw.circuitSuccessThreshold,
|
||||
circuitTimeoutSeconds: raw.circuitTimeoutSeconds,
|
||||
circuitErrorRateThreshold: raw.circuitErrorRateThreshold / 100,
|
||||
circuitMinRequests: raw.circuitMinRequests,
|
||||
});
|
||||
toast.success(
|
||||
t("proxy.autoFailover.configSaved", "自动故障转移配置已保存"),
|
||||
@@ -83,15 +188,17 @@ export function AutoFailoverConfigPanel({
|
||||
if (config) {
|
||||
setFormData({
|
||||
autoFailoverEnabled: config.autoFailoverEnabled,
|
||||
maxRetries: config.maxRetries,
|
||||
streamingFirstByteTimeout: config.streamingFirstByteTimeout,
|
||||
streamingIdleTimeout: config.streamingIdleTimeout,
|
||||
nonStreamingTimeout: config.nonStreamingTimeout,
|
||||
circuitFailureThreshold: config.circuitFailureThreshold,
|
||||
circuitSuccessThreshold: config.circuitSuccessThreshold,
|
||||
circuitTimeoutSeconds: config.circuitTimeoutSeconds,
|
||||
circuitErrorRateThreshold: config.circuitErrorRateThreshold,
|
||||
circuitMinRequests: config.circuitMinRequests,
|
||||
maxRetries: String(config.maxRetries),
|
||||
streamingFirstByteTimeout: String(config.streamingFirstByteTimeout),
|
||||
streamingIdleTimeout: String(config.streamingIdleTimeout),
|
||||
nonStreamingTimeout: String(config.nonStreamingTimeout),
|
||||
circuitFailureThreshold: String(config.circuitFailureThreshold),
|
||||
circuitSuccessThreshold: String(config.circuitSuccessThreshold),
|
||||
circuitTimeoutSeconds: String(config.circuitTimeoutSeconds),
|
||||
circuitErrorRateThreshold: String(
|
||||
Math.round(config.circuitErrorRateThreshold * 100),
|
||||
),
|
||||
circuitMinRequests: String(config.circuitMinRequests),
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -142,13 +249,9 @@ export function AutoFailoverConfigPanel({
|
||||
min="0"
|
||||
max="10"
|
||||
value={formData.maxRetries}
|
||||
onChange={(e) => {
|
||||
const val = parseInt(e.target.value);
|
||||
setFormData({
|
||||
...formData,
|
||||
maxRetries: isNaN(val) ? 0 : val,
|
||||
});
|
||||
}}
|
||||
onChange={(e) =>
|
||||
setFormData({ ...formData, maxRetries: e.target.value })
|
||||
}
|
||||
disabled={isDisabled}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
@@ -169,13 +272,12 @@ export function AutoFailoverConfigPanel({
|
||||
min="1"
|
||||
max="20"
|
||||
value={formData.circuitFailureThreshold}
|
||||
onChange={(e) => {
|
||||
const val = parseInt(e.target.value);
|
||||
onChange={(e) =>
|
||||
setFormData({
|
||||
...formData,
|
||||
circuitFailureThreshold: isNaN(val) ? 1 : Math.max(1, val),
|
||||
});
|
||||
}}
|
||||
circuitFailureThreshold: e.target.value,
|
||||
})
|
||||
}
|
||||
disabled={isDisabled}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
@@ -208,13 +310,12 @@ export function AutoFailoverConfigPanel({
|
||||
min="0"
|
||||
max="180"
|
||||
value={formData.streamingFirstByteTimeout}
|
||||
onChange={(e) => {
|
||||
const val = parseInt(e.target.value);
|
||||
onChange={(e) =>
|
||||
setFormData({
|
||||
...formData,
|
||||
streamingFirstByteTimeout: isNaN(val) ? 0 : val,
|
||||
});
|
||||
}}
|
||||
streamingFirstByteTimeout: e.target.value,
|
||||
})
|
||||
}
|
||||
disabled={isDisabled}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
@@ -235,13 +336,12 @@ export function AutoFailoverConfigPanel({
|
||||
min="0"
|
||||
max="600"
|
||||
value={formData.streamingIdleTimeout}
|
||||
onChange={(e) => {
|
||||
const val = parseInt(e.target.value);
|
||||
onChange={(e) =>
|
||||
setFormData({
|
||||
...formData,
|
||||
streamingIdleTimeout: isNaN(val) ? 0 : val,
|
||||
});
|
||||
}}
|
||||
streamingIdleTimeout: e.target.value,
|
||||
})
|
||||
}
|
||||
disabled={isDisabled}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
@@ -262,13 +362,12 @@ export function AutoFailoverConfigPanel({
|
||||
min="0"
|
||||
max="1800"
|
||||
value={formData.nonStreamingTimeout}
|
||||
onChange={(e) => {
|
||||
const val = parseInt(e.target.value);
|
||||
onChange={(e) =>
|
||||
setFormData({
|
||||
...formData,
|
||||
nonStreamingTimeout: isNaN(val) ? 0 : val,
|
||||
});
|
||||
}}
|
||||
nonStreamingTimeout: e.target.value,
|
||||
})
|
||||
}
|
||||
disabled={isDisabled}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
@@ -298,13 +397,12 @@ export function AutoFailoverConfigPanel({
|
||||
min="1"
|
||||
max="10"
|
||||
value={formData.circuitSuccessThreshold}
|
||||
onChange={(e) => {
|
||||
const val = parseInt(e.target.value);
|
||||
onChange={(e) =>
|
||||
setFormData({
|
||||
...formData,
|
||||
circuitSuccessThreshold: isNaN(val) ? 1 : Math.max(1, val),
|
||||
});
|
||||
}}
|
||||
circuitSuccessThreshold: e.target.value,
|
||||
})
|
||||
}
|
||||
disabled={isDisabled}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
@@ -322,16 +420,15 @@ export function AutoFailoverConfigPanel({
|
||||
<Input
|
||||
id={`timeoutSeconds-${appType}`}
|
||||
type="number"
|
||||
min="10"
|
||||
min="0"
|
||||
max="300"
|
||||
value={formData.circuitTimeoutSeconds}
|
||||
onChange={(e) => {
|
||||
const val = parseInt(e.target.value);
|
||||
onChange={(e) =>
|
||||
setFormData({
|
||||
...formData,
|
||||
circuitTimeoutSeconds: isNaN(val) ? 10 : Math.max(10, val),
|
||||
});
|
||||
}}
|
||||
circuitTimeoutSeconds: e.target.value,
|
||||
})
|
||||
}
|
||||
disabled={isDisabled}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
@@ -352,14 +449,13 @@ export function AutoFailoverConfigPanel({
|
||||
min="0"
|
||||
max="100"
|
||||
step="5"
|
||||
value={Math.round(formData.circuitErrorRateThreshold * 100)}
|
||||
onChange={(e) => {
|
||||
const val = parseInt(e.target.value);
|
||||
value={formData.circuitErrorRateThreshold}
|
||||
onChange={(e) =>
|
||||
setFormData({
|
||||
...formData,
|
||||
circuitErrorRateThreshold: isNaN(val) ? 0.5 : val / 100,
|
||||
});
|
||||
}}
|
||||
circuitErrorRateThreshold: e.target.value,
|
||||
})
|
||||
}
|
||||
disabled={isDisabled}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
@@ -380,13 +476,12 @@ export function AutoFailoverConfigPanel({
|
||||
min="5"
|
||||
max="100"
|
||||
value={formData.circuitMinRequests}
|
||||
onChange={(e) => {
|
||||
const val = parseInt(e.target.value);
|
||||
onChange={(e) =>
|
||||
setFormData({
|
||||
...formData,
|
||||
circuitMinRequests: isNaN(val) ? 5 : Math.max(5, val),
|
||||
});
|
||||
}}
|
||||
circuitMinRequests: e.target.value,
|
||||
})
|
||||
}
|
||||
disabled={isDisabled}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
|
||||
Reference in New Issue
Block a user