Skip to main content

foxy/logging/
mod.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5//! Logging utilities for Foxy.
6//!
7//! This module provides centralized logging configuration and helper functions
8//! for consistent logging throughout the application.
9//!
10//! Two logging systems are supported:
11//! 1. Traditional logging via env_logger (default)
12//! 2. Structured logging via slog with JSON output support
13
14pub mod config;
15pub mod middleware;
16pub mod structured;
17pub mod test_logger;
18pub mod wrapper;
19
20#[cfg(test)]
21#[path = "../../tests/unit/logging/tests.rs"]
22mod tests;
23
24use crate::logging::config::LoggingConfig;
25use crate::logging::structured::{LoggerGuard, init_global_logger};
26use log::{LevelFilter, debug, error, info, trace, warn};
27use std::sync::Once;
28use std::sync::atomic::{AtomicBool, Ordering};
29
30static INIT: Once = Once::new();
31static USING_STRUCTURED: AtomicBool = AtomicBool::new(false);
32static mut LOGGER_GUARD: Option<LoggerGuard> = None;
33
34/// Initialize logging with the specified level and configuration.
35///
36/// This function ensures logging is only initialized once.
37pub fn init_with_config(level: LevelFilter, config: &LoggingConfig) {
38    INIT.call_once(|| {
39        log::set_max_level(level);
40
41        if config.structured {
42            let logger_config = config.to_logger_config();
43            let guard = init_global_logger(&logger_config);
44
45            // Keep the logger alive
46            unsafe {
47                LOGGER_GUARD = Some(guard);
48            }
49            USING_STRUCTURED.store(true, Ordering::SeqCst);
50        } else {
51            // Fallback to env_logger, using our determined level as the default.
52            // The RUST_LOG env var can still override this if it was set.
53            let env = env_logger::Env::default().filter_or("RUST_LOG", level.as_str());
54            env_logger::Builder::from_env(env)
55                .format_timestamp_millis()
56                .format_target(true)
57                .init();
58        }
59
60        info!("Logging initialized at level: {}", log::max_level());
61    });
62}
63
64/// Check if structured logging is enabled
65pub fn is_structured_logging() -> bool {
66    USING_STRUCTURED.load(Ordering::SeqCst)
67}
68
69/// Log an error with context and return the error.
70///
71/// This is useful for logging errors in a chain of Results.
72pub fn log_error<E: std::fmt::Display>(context: &str, err: E) -> E {
73    if is_structured_logging() {
74        slog_scope::error!("{}", err; "context" => context);
75    } else {
76        error!("{context}: {err}");
77    }
78    err
79}
80
81/// Log a warning with context.
82pub fn log_warning<E: std::fmt::Display>(context: &str, err: E) {
83    if is_structured_logging() {
84        slog_scope::warn!("{}", err; "context" => context);
85    } else {
86        warn!("{context}: {err}");
87    }
88}
89
90/// Log a debug message with context.
91pub fn log_debug<M: std::fmt::Display>(context: &str, msg: M) {
92    if is_structured_logging() {
93        slog_scope::debug!("{}", msg; "context" => context);
94    } else {
95        debug!("{context}: {msg}");
96    }
97}
98
99/// Log a trace message with context.
100pub fn log_trace<M: std::fmt::Display>(context: &str, msg: M) {
101    if is_structured_logging() {
102        slog_scope::trace!("{}", msg; "context" => context);
103    } else {
104        trace!("{context}: {msg}");
105    }
106}
107
108/// Log an info message with context.
109pub fn log_info<M: std::fmt::Display>(context: &str, msg: M) {
110    if is_structured_logging() {
111        slog_scope::info!("{}", msg; "context" => context);
112    } else {
113        info!("{context}: {msg}");
114    }
115}
116
117/// Log a message with additional context fields
118pub fn log_with_context(
119    level: log::Level,
120    message: impl std::fmt::Display,
121    context: &str,
122    fields: &[(&'static str, String)],
123) {
124    if is_structured_logging() {
125        match level {
126            log::Level::Error => {
127                let logger = slog_scope::logger();
128                let context_str = context.to_string(); // Clone to extend lifetime
129                let logger = logger.new(slog::o!("context" => context_str));
130                let logger = add_fields_to_logger(logger, fields);
131                slog::error!(logger, "{}", message);
132            }
133            log::Level::Warn => {
134                let logger = slog_scope::logger();
135                let context_str = context.to_string(); // Clone to extend lifetime
136                let logger = logger.new(slog::o!("context" => context_str));
137                let logger = add_fields_to_logger(logger, fields);
138                slog::warn!(logger, "{}", message);
139            }
140            log::Level::Info => {
141                let logger = slog_scope::logger();
142                let context_str = context.to_string(); // Clone to extend lifetime
143                let logger = logger.new(slog::o!("context" => context_str));
144                let logger = add_fields_to_logger(logger, fields);
145                slog::info!(logger, "{}", message);
146            }
147            log::Level::Debug => {
148                let logger = slog_scope::logger();
149                let context_str = context.to_string(); // Clone to extend lifetime
150                let logger = logger.new(slog::o!("context" => context_str));
151                let logger = add_fields_to_logger(logger, fields);
152                slog::debug!(logger, "{}", message);
153            }
154            log::Level::Trace => {
155                let logger = slog_scope::logger();
156                let context_str = context.to_string(); // Clone to extend lifetime
157                let logger = logger.new(slog::o!("context" => context_str));
158                let logger = add_fields_to_logger(logger, fields);
159                slog::trace!(logger, "{}", message);
160            }
161        }
162    } else {
163        // Fall back to standard logging with context
164        match level {
165            log::Level::Error => crate::error!("{}: {}", context, message),
166            log::Level::Warn => crate::warn!("{}: {}", context, message),
167            log::Level::Info => crate::info!("{}: {}", context, message),
168            log::Level::Debug => crate::debug!("{}: {}", context, message),
169            log::Level::Trace => crate::trace!("{}: {}", context, message),
170        }
171    }
172}
173
174/// Helper function to add fields to a logger
175fn add_fields_to_logger(logger: slog::Logger, fields: &[(&'static str, String)]) -> slog::Logger {
176    let mut result = logger;
177    for (k, v) in fields {
178        let v_clone = v.clone(); // Clone the value to extend its lifetime
179        result = result.new(slog::o!(*k => v_clone));
180    }
181    result
182}