mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-29 18:05:37 +08:00
feat(logging): persist diagnostics across restarts and redact secrets
Backend half of the logging overhaul. Retention: - Keep the last 4 rotated files at 20MB each and stop deleting logs on startup, so a crash's prior-run logs survive a restart. - Apply the persisted log level right after plugin registration instead of at the end of setup. - Bound crash.log with size-based rotation (5MB x 2). Secret redaction on every log path: - Proxy: strip userinfo/query from upstream URLs (keep path for diagnostics), exact-match redact known auth.api_key/access_token, classify request/response bodies instead of logging them, header allowlist. - Redact the Gemini `?key=` in cache-trace endpoints. - Omit MCP custom-field values (headers may carry tokens). - Redact deeplink and model-fetch URLs before logging. Docs: - FAQ points users to the persistent crash.log for support workflows.
This commit is contained in:
@@ -65,9 +65,13 @@ pub async fn fetch_models(
|
||||
let candidates = build_models_url_candidates(base_url, is_full_url, models_url_override)?;
|
||||
let client = crate::proxy::http_client::get();
|
||||
let mut last_err: Option<String> = None;
|
||||
let log_secrets = vec![api_key.to_string()];
|
||||
|
||||
for url in &candidates {
|
||||
log::debug!("[ModelFetch] Trying endpoint: {url}");
|
||||
log::debug!(
|
||||
"[ModelFetch] Trying endpoint: {}",
|
||||
crate::url_for_log_with_secrets(url, &log_secrets)
|
||||
);
|
||||
let mut request = client
|
||||
.get(url)
|
||||
.header("Authorization", format!("Bearer {api_key}"))
|
||||
|
||||
@@ -401,33 +401,7 @@ pub fn webdav_status_error(op: &str, status: StatusCode, url: &str) -> AppError
|
||||
}
|
||||
|
||||
fn redact_url(raw: &str) -> String {
|
||||
match Url::parse(raw) {
|
||||
Ok(mut parsed) => {
|
||||
let _ = parsed.set_username("");
|
||||
let _ = parsed.set_password(None);
|
||||
|
||||
let mut out = format!("{}://", parsed.scheme());
|
||||
if let Some(host) = parsed.host_str() {
|
||||
out.push_str(host);
|
||||
}
|
||||
if let Some(port) = parsed.port() {
|
||||
out.push(':');
|
||||
out.push_str(&port.to_string());
|
||||
}
|
||||
out.push_str(parsed.path());
|
||||
|
||||
let mut keys: Vec<String> = parsed.query_pairs().map(|(k, _)| k.into_owned()).collect();
|
||||
keys.sort();
|
||||
keys.dedup();
|
||||
if !keys.is_empty() {
|
||||
out.push_str("?[keys:");
|
||||
out.push_str(&keys.join(","));
|
||||
out.push(']');
|
||||
}
|
||||
out
|
||||
}
|
||||
Err(_) => raw.split('?').next().unwrap_or(raw).to_string(),
|
||||
}
|
||||
crate::redact_url_for_log(raw)
|
||||
}
|
||||
|
||||
fn response_too_large_error(url: &str, max_bytes: usize) -> AppError {
|
||||
@@ -520,9 +494,11 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn redact_url_hides_credentials_and_query_values() {
|
||||
// userinfo 与整个 query 一并剥离,host+path 保留用于诊断。
|
||||
let redacted = redact_url("https://alice:secret@example.com:8443/dav?token=abc&foo=1");
|
||||
assert_eq!(redacted, "https://example.com:8443/dav?[keys:foo,token]");
|
||||
assert_eq!(redacted, "https://example.com:8443/dav");
|
||||
assert!(!redacted.contains("secret"));
|
||||
assert!(!redacted.contains("abc"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user