mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-08-01 12:22:09 +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:
@@ -84,7 +84,9 @@ pub fn import_from_codex(config: &mut MultiAppConfig) -> Result<usize, AppError>
|
||||
// 核心字段(需要手动处理的字段)
|
||||
let core_fields = match typ {
|
||||
"stdio" => vec!["type", "command", "args", "env", "cwd"],
|
||||
"http" | "sse" => vec!["type", "url", "http_headers"],
|
||||
// DB 中的统一规范使用 headers,Codex TOML 使用 http_headers。
|
||||
// 两者都必须视为核心字段,避免鉴权值落入通用日志路径。
|
||||
"http" | "sse" => vec!["type", "url", "headers", "http_headers"],
|
||||
_ => vec!["type"],
|
||||
};
|
||||
|
||||
@@ -204,7 +206,7 @@ pub fn import_from_codex(config: &mut MultiAppConfig) -> Result<usize, AppError>
|
||||
|
||||
if let Some(val) = json_val {
|
||||
spec.insert(key.clone(), val);
|
||||
log::debug!("导入扩展字段 '{key}' = {toml_val:?}");
|
||||
log::debug!("导入扩展字段 '{key}'(值已省略)");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -567,7 +569,7 @@ pub(super) fn json_server_to_toml_table(spec: &Value) -> Result<toml_edit::Table
|
||||
// 定义核心字段(已在下方处理,跳过通用转换)
|
||||
let core_fields = match typ {
|
||||
"stdio" => vec!["type", "command", "args", "env", "cwd"],
|
||||
"http" | "sse" => vec!["type", "url", "http_headers"],
|
||||
"http" | "sse" => vec!["type", "url", "headers", "http_headers"],
|
||||
_ => vec!["type"],
|
||||
};
|
||||
|
||||
@@ -664,11 +666,11 @@ pub(super) fn json_server_to_toml_table(spec: &Value) -> Result<toml_edit::Table
|
||||
if let Some(toml_item) = json_value_to_toml_item(value, key) {
|
||||
t[&key[..]] = toml_item;
|
||||
|
||||
// 记录扩展字段的处理
|
||||
// 只记录字段名:未知字段同样可能携带 token / secret。
|
||||
if extended_fields.contains(&key.as_str()) {
|
||||
log::debug!("已转换扩展字段 '{key}' = {value:?}");
|
||||
log::debug!("已转换扩展字段 '{key}'(值已省略)");
|
||||
} else {
|
||||
log::info!("已转换自定义字段 '{key}' = {value:?}");
|
||||
log::debug!("已转换自定义字段 '{key}'(值已省略)");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -676,3 +678,39 @@ pub(super) fn json_server_to_toml_table(spec: &Value) -> Result<toml_edit::Table
|
||||
|
||||
Ok(t)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn http_headers_are_only_written_to_codex_http_headers() {
|
||||
let table = json_server_to_toml_table(&json!({
|
||||
"type": "http",
|
||||
"url": "https://mcp.example.com",
|
||||
"headers": {
|
||||
"Authorization": "Bearer top-secret",
|
||||
"X-Api-Key": "also-secret"
|
||||
},
|
||||
"timeout": 30
|
||||
}))
|
||||
.unwrap();
|
||||
|
||||
let headers = table
|
||||
.get("http_headers")
|
||||
.and_then(|item| item.as_table())
|
||||
.expect("Codex http_headers table should be written");
|
||||
assert_eq!(
|
||||
headers.get("Authorization").and_then(|item| item.as_str()),
|
||||
Some("Bearer top-secret")
|
||||
);
|
||||
assert!(
|
||||
table.get("headers").is_none(),
|
||||
"legacy headers must not be emitted a second time"
|
||||
);
|
||||
assert_eq!(
|
||||
table.get("timeout").and_then(|item| item.as_integer()),
|
||||
Some(30)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user