codex_convert_proxy/
logger.rs1use std::cmp::min;
9use std::path::Path;
10use tracing::info;
11use tracing_subscriber::{
12 fmt::{self, format::FmtSpan},
13 layer::SubscriberExt,
14 util::SubscriberInitExt,
15 EnvFilter, Layer, Registry,
16};
17
18static LOG_CONFIG: std::sync::OnceLock<LogConfig> = std::sync::OnceLock::new();
20
21struct LogConfig {
22 #[allow(dead_code)]
23 log_body: bool,
24 #[allow(dead_code)]
25 log_headers: bool,
26}
27
28pub fn init_logging(log_dir: &Path, log_body: bool, log_headers: bool) -> anyhow::Result<()> {
34 std::fs::create_dir_all(log_dir)?;
35
36 let file_appender = tracing_appender::rolling::daily(log_dir, "proxy.log");
38 let file_layer = fmt::layer()
39 .with_writer(file_appender)
40 .with_target(false)
41 .with_thread_ids(false)
42 .with_file(false)
43 .with_line_number(false)
44 .with_ansi(false)
45 .with_span_events(FmtSpan::CLOSE)
46 .with_filter(EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("debug")));
47
48 let stdout_layer = fmt::layer()
50 .with_writer(std::io::stdout)
51 .with_target(false)
52 .with_thread_ids(false)
53 .with_ansi(true)
54 .with_filter(create_timeline_filter());
55
56 Registry::default()
58 .with(file_layer)
59 .with(stdout_layer)
60 .try_init()
61 .map_err(|e| anyhow::anyhow!("Failed to initialize logging: {}", e))?;
62
63 LOG_CONFIG.get_or_init(|| LogConfig {
65 log_body,
66 log_headers,
67 });
68
69 info!("Logging initialized: {}", log_dir.display());
70 Ok(())
71}
72
73fn create_timeline_filter() -> EnvFilter {
75 EnvFilter::builder()
76 .parse("info")
77 .unwrap_or_else(|_| EnvFilter::new("info"))
78}
79
80pub fn is_sensitive_header(name: &str) -> bool {
82 let lower = name.to_lowercase();
83 lower == "x-api-key"
84 || lower == "authorization"
85 || lower == "api-key"
86 || lower == "x-api-token"
87 || lower == "cookie"
88 || lower == "set-cookie"
89}
90
91pub fn mask_sensitive(value: &str) -> String {
93 if value.len() <= 10 {
94 return "***".to_string();
95 }
96 if value.starts_with("Bearer ") {
97 return "Bearer ***".to_string();
99 }
100 format!("{}***", &value[..min(6, value.len())])
102}
103
104#[cfg(test)]
105mod tests {
106 use super::*;
107
108 #[test]
109 fn test_mask_sensitive() {
110 assert_eq!(mask_sensitive("short"), "***");
111 assert_eq!(mask_sensitive("Bearer sk-xxx"), "Bearer ***");
112 assert_eq!(
113 mask_sensitive("Bearer sk-project-12345"),
114 "Bearer ***"
115 );
116 assert_eq!(
117 mask_sensitive("sk-ant-api03-xxxxxxxxxxxx"),
118 "sk-ant***"
119 );
120 }
121
122 #[test]
123 fn test_is_sensitive_header() {
124 assert!(is_sensitive_header("x-api-key"));
125 assert!(is_sensitive_header("X-API-KEY"));
126 assert!(is_sensitive_header("authorization"));
127 assert!(is_sensitive_header("Authorization"));
128 assert!(!is_sensitive_header("content-type"));
129 assert!(!is_sensitive_header("x-request-id"));
130 }
131}