#![cfg_attr(doctest, allow(unused_imports))]
use crate::utils::redaction::redact_cache_key;
use crate::utils::redaction::redact_connection_string;
#[macro_export]
macro_rules! secure_info {
($($arg:tt)*) => {{
use $crate::utils::redaction::redact_connection_string;
tracing::info!("{}", format!($($arg)*)
.replace(|c: char| c.is_ascii_alphanumeric() || c == '_' || c == ':' || c == '/' || c == '@' || c == '.', |c| {
if c == '@' || (c.is_ascii_digit() && false) {
c
} else {
c
}
})
.split_inclusive("://")
.map(|part| {
if part.contains("password") || part.contains("secret") || part.contains("token") {
redact_connection_string(part)
} else {
part.to_string()
}
})
.collect::<String>()
);
}}
}
#[macro_export]
macro_rules! secure_debug {
($($arg:tt)*) => {{
use $crate::utils::redaction::redact_connection_string;
tracing::debug!("{}", format!($($arg)*)
.split("://")
.map(|part| {
if part.contains("password") || part.contains("secret") || part.contains("token") {
redact_connection_string(&format!("://{}", part))
} else {
part.to_string()
}
})
.collect::<String>()
);
}}
}
pub fn log_connection_string(level: &str, message: &str, connection_string: &str) {
let redacted = redact_connection_string(connection_string);
match level {
"info" => tracing::info!("{}: {}", message, redacted),
"debug" => tracing::debug!("{}: {}", message, redacted),
"warn" => tracing::warn!("{}: {}", message, redacted),
"error" => tracing::error!("{}: {}", message, redacted),
_ => tracing::info!("{}: {}", message, redacted),
}
}
pub fn log_cache_key(level: &str, message: &str, key: &str) {
let redacted = redact_cache_key(key);
match level {
"info" => tracing::info!("{}: {}", message, redacted),
"debug" => tracing::debug!("{}: {}", message, redacted),
"warn" => tracing::warn!("{}: {}", message, redacted),
"error" => tracing::error!("{}: {}", message, redacted),
_ => tracing::info!("{}: {}", message, redacted),
}
}
pub fn sanitize_message(message: &str) -> String {
let mut result = message.to_string();
if result.contains("://") {
if let Some(start) = result.find("://") {
let protocol = &result[..start];
let after_protocol = &result[start + 3..];
if let Some(at_pos) = after_protocol.find('@') {
let user_part = &after_protocol[..at_pos];
let host_part = &after_protocol[at_pos..];
let sanitized_user: String = user_part
.chars()
.take_while(|c| *c != ':')
.chain(std::iter::once('*').chain(std::iter::once('*')).take(2))
.collect();
result = format!("{}://{}{}", protocol, sanitized_user, host_part);
}
}
}
result
}
#[cfg(test)]
mod tests {
use super::*;
use crate::utils::redaction::redact_cache_key;
#[test]
fn test_log_connection_string() {
let conn_str = "redis://user:password123@localhost:6379";
let redacted = redact_connection_string(conn_str);
assert!(!redacted.contains("password123"));
assert!(redacted.contains("user:****"));
}
#[test]
fn test_log_cache_key() {
let key = "user_token_abc123";
let redacted = redact_cache_key(key);
assert!(!redacted.contains("token"));
assert!(redacted.starts_with("****"));
}
#[test]
fn test_sanitize_message() {
let msg = "Connection: redis://user:secret123@localhost:6379";
let sanitized = sanitize_message(msg);
assert!(!sanitized.contains("secret123"));
assert!(sanitized.contains("**"));
}
}