shared_logging/
logger.rs

1//! Structured logger wrapper with context propagation.
2
3use crate::context::Context;
4use crate::redaction::redact_field;
5use crate::schema::{Event, Level, StandardFields};
6use tracing::{event, Level as TracingLevel};
7use tracing_subscriber::{
8    fmt::{self, time::ChronoUtc},
9    layer::SubscriberExt,
10    util::SubscriberInitExt,
11    EnvFilter, Registry,
12};
13
14/// Error type for logger initialization.
15#[derive(Debug, thiserror::Error)]
16pub enum LoggerError {
17    #[error("Failed to initialize logger: {0}")]
18    Initialization(String),
19}
20
21/// Initialize the global logger with structured JSON output.
22///
23/// # Arguments
24///
25/// * `service_name` - Name of the service (used in all log events)
26/// * `default_level` - Default log level (e.g., "info", "debug")
27///
28/// # Example
29///
30/// ```no_run
31/// use shared_logging::init_logger;
32///
33/// init_logger("my-service", "info").unwrap();
34/// ```
35pub fn init_logger(service_name: &str, default_level: &str) -> Result<(), LoggerError> {
36    let env_filter = EnvFilter::try_from_default_env()
37        .unwrap_or_else(|_| EnvFilter::new(default_level));
38
39    let fmt_layer = fmt::layer()
40        .json()
41        .with_timer(ChronoUtc::rfc_3339())
42        .with_target(false)
43        .with_current_span(false)
44        .with_span_list(false)
45        .with_file(false)
46        .with_line_number(false)
47        .with_writer(std::io::stdout);
48
49    Registry::default()
50        .with(env_filter)
51        .with(fmt_layer)
52        .try_init()
53        .map_err(|e| LoggerError::Initialization(e.to_string()))?;
54
55    // Set service name in a global context
56    std::env::set_var("LOG_SERVICE_NAME", service_name);
57
58    Ok(())
59}
60
61/// Structured logger with context propagation.
62pub struct Logger {
63    service_name: String,
64    module: Option<String>,
65    context: Context,
66}
67
68impl Logger {
69    /// Create a new logger instance.
70    ///
71    /// # Arguments
72    ///
73    /// * `module` - Module/component name (optional)
74    ///
75    /// # Example
76    ///
77    /// ```no_run
78    /// use shared_logging::Logger;
79    ///
80    /// let logger = Logger::new("auth");
81    /// logger.info("User authenticated");
82    /// ```
83    pub fn new(module: impl Into<Option<String>>) -> Self {
84        let service_name = std::env::var("LOG_SERVICE_NAME")
85            .unwrap_or_else(|_| "unknown-service".to_string());
86
87        Self {
88            service_name,
89            module: module.into(),
90            context: Context::new(),
91        }
92    }
93
94    /// Create a logger with context.
95    pub fn with_context(module: impl Into<Option<String>>, context: Context) -> Self {
96        let service_name = std::env::var("LOG_SERVICE_NAME")
97            .unwrap_or_else(|_| "unknown-service".to_string());
98
99        Self {
100            service_name,
101            module: module.into(),
102            context,
103        }
104    }
105
106    /// Set context for this logger instance.
107    pub fn set_context(&mut self, context: Context) {
108        self.context = context;
109    }
110
111    /// Merge context into this logger instance.
112    pub fn merge_context(&mut self, context: Context) {
113        self.context = self.context.clone().merge(context);
114    }
115
116    /// Log a trace-level message.
117    pub fn trace(&self, message: impl Into<String>) {
118        self.log(Level::Trace, message, None::<fn(&mut EventBuilder)>);
119    }
120
121    /// Log a debug-level message.
122    pub fn debug(&self, message: impl Into<String>) {
123        self.log(Level::Debug, message, None::<fn(&mut EventBuilder)>);
124    }
125
126    /// Log an info-level message.
127    pub fn info(&self, message: impl Into<String>) {
128        self.log(Level::Info, message, None::<fn(&mut EventBuilder)>);
129    }
130
131    /// Log a warn-level message.
132    pub fn warn(&self, message: impl Into<String>) {
133        self.log(Level::Warn, message, None::<fn(&mut EventBuilder)>);
134    }
135
136    /// Log an error-level message.
137    pub fn error(&self, message: impl Into<String>) {
138        self.log(Level::Error, message, None::<fn(&mut EventBuilder)>);
139    }
140
141    /// Log a trace-level message with fields.
142    pub fn trace_with<F>(&self, message: impl Into<String>, f: F)
143    where
144        F: FnOnce(&mut EventBuilder),
145    {
146        self.log(Level::Trace, message, Some(f));
147    }
148
149    /// Log a debug-level message with fields.
150    pub fn debug_with<F>(&self, message: impl Into<String>, f: F)
151    where
152        F: FnOnce(&mut EventBuilder),
153    {
154        self.log(Level::Debug, message, Some(f));
155    }
156
157    /// Log an info-level message with fields.
158    pub fn info_with<F>(&self, message: impl Into<String>, f: F)
159    where
160        F: FnOnce(&mut EventBuilder),
161    {
162        self.log(Level::Info, message, Some(f));
163    }
164
165    /// Log a warn-level message with fields.
166    pub fn warn_with<F>(&self, message: impl Into<String>, f: F)
167    where
168        F: FnOnce(&mut EventBuilder),
169    {
170        self.log(Level::Warn, message, Some(f));
171    }
172
173    /// Log an error-level message with fields.
174    pub fn error_with<F>(&self, message: impl Into<String>, f: F)
175    where
176        F: FnOnce(&mut EventBuilder),
177    {
178        self.log(Level::Error, message, Some(f));
179    }
180
181    /// Log an error.
182    pub fn log_error(&self, message: impl Into<String>, error: &dyn std::error::Error) {
183        self.error_with(message, |e| {
184            e.error(error);
185        });
186    }
187
188    /// Internal logging method.
189    fn log<F>(&self, level: Level, message: impl Into<String>, fields_fn: Option<F>)
190    where
191        F: FnOnce(&mut EventBuilder),
192    {
193        let message = message.into();
194        let tracing_level = level.to_tracing_level();
195
196        // Build event fields
197        let mut builder = EventBuilder::new();
198        
199        // Add standard fields
200        builder.field(StandardFields::SERVICE, &self.service_name);
201        if let Some(ref module) = self.module {
202            builder.field(StandardFields::MODULE, module);
203        }
204        builder.field(StandardFields::LEVEL, level.to_string());
205        builder.field(StandardFields::MESSAGE, &message);
206
207        // Add context fields
208        for (key, value) in self.context.to_fields() {
209            builder.field(key, &value);
210        }
211
212        // Add custom fields
213        if let Some(f) = fields_fn {
214            f(&mut builder);
215        }
216
217        // Extract fields for logging
218        let fields = builder.build();
219        let fields_map = fields.as_object().unwrap();
220
221        // Build dynamic event macro call - we'll use a helper function
222        self.log_with_fields(tracing_level, &message, fields_map);
223    }
224
225    /// Log with fields map.
226    fn log_with_fields(
227        &self,
228        level: TracingLevel,
229        message: &str,
230        fields: &serde_json::Map<String, serde_json::Value>,
231    ) {
232        // Serialize all fields as JSON for inclusion in the log event
233        // The JSON formatter will parse and include all fields in the structured output
234        let fields_json = serde_json::to_string(fields).unwrap_or_else(|_| "{}".to_string());
235        
236        // Emit the event with fields as JSON
237        // The JSON formatter will parse and include all fields in the structured output
238        // Note: We include module in the fields JSON instead of as target
239        match level {
240            TracingLevel::TRACE => {
241                event!(
242                    TracingLevel::TRACE,
243                    message = %message,
244                    service = %self.service_name,
245                    fields = %fields_json
246                );
247            }
248            TracingLevel::DEBUG => {
249                event!(
250                    TracingLevel::DEBUG,
251                    message = %message,
252                    service = %self.service_name,
253                    fields = %fields_json
254                );
255            }
256            TracingLevel::INFO => {
257                event!(
258                    TracingLevel::INFO,
259                    message = %message,
260                    service = %self.service_name,
261                    fields = %fields_json
262                );
263            }
264            TracingLevel::WARN => {
265                event!(
266                    TracingLevel::WARN,
267                    message = %message,
268                    service = %self.service_name,
269                    fields = %fields_json
270                );
271            }
272            TracingLevel::ERROR => {
273                event!(
274                    TracingLevel::ERROR,
275                    message = %message,
276                    service = %self.service_name,
277                    fields = %fields_json
278                );
279            }
280        }
281    }
282}
283
284
285/// Builder for log event fields.
286pub struct EventBuilder {
287    fields: serde_json::Map<String, serde_json::Value>,
288}
289
290impl EventBuilder {
291    /// Create a new event builder.
292    pub fn new() -> Self {
293        Self {
294            fields: serde_json::Map::new(),
295        }
296    }
297
298    /// Add a field to the event.
299    ///
300    /// Values are automatically redacted based on field name and content.
301    pub fn field(&mut self, name: &str, value: impl ToString) -> &mut Self {
302        let value_str = value.to_string();
303        let redacted = redact_field(name, &value_str);
304        self.fields.insert(name.to_string(), serde_json::Value::String(redacted));
305        self
306    }
307
308    /// Add a field without redaction (use with caution).
309    pub fn field_raw(&mut self, name: &str, value: impl Into<serde_json::Value>) -> &mut Self {
310        self.fields.insert(name.to_string(), value.into());
311        self
312    }
313
314    /// Add an error to the event.
315    pub fn error(&mut self, error: &dyn std::error::Error) -> &mut Self {
316        let error_obj = Event::format_error(error);
317        self.fields.insert(StandardFields::ERROR.to_string(), error_obj);
318        self
319    }
320
321    /// Build the fields map.
322    pub fn build(self) -> serde_json::Value {
323        serde_json::Value::Object(self.fields)
324    }
325}
326
327impl Default for EventBuilder {
328    fn default() -> Self {
329        Self::new()
330    }
331}
332
333#[cfg(test)]
334mod tests {
335    use super::*;
336
337    #[test]
338    fn test_event_builder() {
339        let mut builder = EventBuilder::new();
340        builder.field("user_id", "user123");
341        builder.field("action", "login");
342        
343        let fields = builder.build();
344        assert!(fields.is_object());
345    }
346}