use std::cmp::min;
use std::path::Path;
use tracing::info;
use tracing_subscriber::{
fmt::{self, format::FmtSpan},
layer::SubscriberExt,
util::SubscriberInitExt,
EnvFilter, Layer, Registry,
};
static LOG_CONFIG: std::sync::OnceLock<LogConfig> = std::sync::OnceLock::new();
struct LogConfig {
#[allow(dead_code)]
log_body: bool,
#[allow(dead_code)]
log_headers: bool,
}
pub fn init_logging(log_dir: &Path, log_body: bool, log_headers: bool) -> anyhow::Result<()> {
std::fs::create_dir_all(log_dir)?;
let file_appender = tracing_appender::rolling::daily(log_dir, "proxy.log");
let file_layer = fmt::layer()
.with_writer(file_appender)
.with_target(false)
.with_thread_ids(false)
.with_file(false)
.with_line_number(false)
.with_ansi(false)
.with_span_events(FmtSpan::CLOSE)
.with_filter(EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("debug")));
let stdout_layer = fmt::layer()
.with_writer(std::io::stdout)
.with_target(false)
.with_thread_ids(false)
.with_ansi(true)
.with_filter(create_timeline_filter());
Registry::default()
.with(file_layer)
.with(stdout_layer)
.try_init()
.map_err(|e| anyhow::anyhow!("Failed to initialize logging: {}", e))?;
LOG_CONFIG.get_or_init(|| LogConfig {
log_body,
log_headers,
});
info!("Logging initialized: {}", log_dir.display());
Ok(())
}
fn create_timeline_filter() -> EnvFilter {
EnvFilter::builder()
.parse("info")
.unwrap_or_else(|_| EnvFilter::new("info"))
}
pub fn is_sensitive_header(name: &str) -> bool {
let lower = name.to_lowercase();
lower == "x-api-key"
|| lower == "authorization"
|| lower == "api-key"
|| lower == "x-api-token"
|| lower == "cookie"
|| lower == "set-cookie"
}
pub fn mask_sensitive(value: &str) -> String {
if value.len() <= 10 {
return "***".to_string();
}
if value.starts_with("Bearer ") {
return "Bearer ***".to_string();
}
format!("{}***", &value[..min(6, value.len())])
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_mask_sensitive() {
assert_eq!(mask_sensitive("short"), "***");
assert_eq!(mask_sensitive("Bearer sk-xxx"), "Bearer ***");
assert_eq!(
mask_sensitive("Bearer sk-project-12345"),
"Bearer ***"
);
assert_eq!(
mask_sensitive("sk-ant-api03-xxxxxxxxxxxx"),
"sk-ant***"
);
}
#[test]
fn test_is_sensitive_header() {
assert!(is_sensitive_header("x-api-key"));
assert!(is_sensitive_header("X-API-KEY"));
assert!(is_sensitive_header("authorization"));
assert!(is_sensitive_header("Authorization"));
assert!(!is_sensitive_header("content-type"));
assert!(!is_sensitive_header("x-request-id"));
}
}