Skip to main content

ralph/redaction/
logging.rs

1//! Purpose: Provide redaction-aware string wrappers, logger plumbing, and
2//! redacted logging macros.
3//!
4//! Responsibilities:
5//! - Wrap strings so display/debug output is redacted automatically.
6//! - Wrap a `log::Log` implementation and redact emitted messages.
7//! - Export convenience macros for explicit redacted logging.
8//!
9//! Scope:
10//! - Logging and display behavior only; redaction pattern matching lives in
11//!   `patterns.rs`.
12//!
13//! Usage:
14//! - Used by runner/error surfaces and logger bootstrap code.
15//!
16//! Invariants/Assumptions:
17//! - User-visible log/display output always flows through `redact_text`.
18//! - Raw debug-log capture still happens before terminal redaction.
19
20use std::fmt;
21
22use super::patterns::redact_text;
23
24#[derive(Clone, Default, PartialEq, Eq)]
25pub struct RedactedString(pub String);
26
27impl From<String> for RedactedString {
28    fn from(s: String) -> Self {
29        Self(s)
30    }
31}
32
33impl From<&str> for RedactedString {
34    fn from(s: &str) -> Self {
35        Self(s.to_string())
36    }
37}
38
39impl fmt::Display for RedactedString {
40    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41        write!(f, "{}", redact_text(&self.0))
42    }
43}
44
45impl fmt::Debug for RedactedString {
46    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
47        if f.alternate() {
48            write!(f, "RedactedString({:#?})", redact_text(&self.0))
49        } else {
50            write!(f, "RedactedString({:?})", redact_text(&self.0))
51        }
52    }
53}
54
55pub struct RedactedLogger {
56    inner: Box<dyn log::Log>,
57}
58
59impl RedactedLogger {
60    pub fn new(inner: Box<dyn log::Log>) -> Self {
61        Self { inner }
62    }
63
64    pub fn init(
65        inner: Box<dyn log::Log>,
66        max_level: log::LevelFilter,
67    ) -> Result<(), log::SetLoggerError> {
68        log::set_boxed_logger(Box::new(Self::new(inner)))?;
69        log::set_max_level(max_level);
70        Ok(())
71    }
72}
73
74impl log::Log for RedactedLogger {
75    fn enabled(&self, metadata: &log::Metadata) -> bool {
76        self.inner.enabled(metadata)
77    }
78
79    fn log(&self, record: &log::Record) {
80        if self.enabled(record.metadata()) {
81            crate::debuglog::write_log_record(record);
82            let redacted_msg = redact_text(&format!("{}", record.args()));
83            self.inner.log(
84                &log::Record::builder()
85                    .args(format_args!("{}", redacted_msg))
86                    .level(record.level())
87                    .target(record.target())
88                    .file(record.file())
89                    .line(record.line())
90                    .module_path(record.module_path())
91                    .build(),
92            );
93        }
94    }
95
96    fn flush(&self) {
97        self.inner.flush();
98    }
99}
100
101#[macro_export]
102macro_rules! rinfo {
103    ($($arg:tt)+) => {
104        log::info!("{}", $crate::redaction::redact_text(&format!($($arg)+)))
105    }
106}
107
108#[macro_export]
109macro_rules! rwarn {
110    ($($arg:tt)+) => {
111        log::warn!("{}", $crate::redaction::redact_text(&format!($($arg)+)))
112    }
113}
114
115#[macro_export]
116macro_rules! rerror {
117    ($($arg:tt)+) => {
118        log::error!("{}", $crate::redaction::redact_text(&format!($($arg)+)))
119    }
120}
121
122#[macro_export]
123macro_rules! rdebug {
124    ($($arg:tt)+) => {
125        log::debug!("{}", $crate::redaction::redact_text(&format!($($arg)+)))
126    }
127}
128
129#[macro_export]
130macro_rules! rtrace {
131    ($($arg:tt)+) => {
132        log::trace!("{}", $crate::redaction::redact_text(&format!($($arg)+)))
133    }
134}