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 structured;
15pub mod config;
16pub mod wrapper;
17pub mod middleware;
18
19use log::{debug, error, info, trace, warn, LevelFilter};
20use std::sync::Once;
21use std::sync::atomic::{AtomicBool, Ordering};
22use crate::logging::config::LoggingConfig;
23use crate::logging::structured::{LoggerGuard, init_global_logger};
24
25static INIT: Once = Once::new();
26static USING_STRUCTURED: AtomicBool = AtomicBool::new(false);
27static mut LOGGER_GUARD: Option<LoggerGuard> = None;
28
29/// Initialize logging with the specified level and configuration.
30///
31/// This function ensures logging is only initialized once.
32pub fn init_with_config(level: LevelFilter, config: &LoggingConfig) {
33    INIT.call_once(|| {
34        log::set_max_level(level);
35
36        if config.structured {
37            let logger_config = config.to_logger_config();
38            let guard = init_global_logger(&logger_config);
39
40            // Keep the logger alive
41            unsafe { LOGGER_GUARD = Some(guard); }
42            USING_STRUCTURED.store(true, Ordering::SeqCst);
43        } else {
44            // Fallback to env_logger, using our determined level as the default.
45            // The RUST_LOG env var can still override this if it was set.
46            let env = env_logger::Env::default().filter_or("RUST_LOG", level.as_str());
47            env_logger::Builder::from_env(env)
48                .format_timestamp_millis()
49                .format_target(true)
50                .init();
51        }
52
53        info!("Logging initialized at level: {}", log::max_level());
54    });
55}
56
57/// Check if structured logging is enabled
58pub fn is_structured_logging() -> bool {
59    USING_STRUCTURED.load(Ordering::SeqCst)
60}
61
62/// Log an error with context and return the error.
63///
64/// This is useful for logging errors in a chain of Results.
65pub fn log_error<E: std::fmt::Display>(context: &str, err: E) -> E {
66    if is_structured_logging() {
67        slog_scope::error!("{}", err; "context" => context);
68    } else {
69        error!("{}: {}", context, err);
70    }
71    err
72}
73
74/// Log a warning with context.
75pub fn log_warning<E: std::fmt::Display>(context: &str, err: E) {
76    if is_structured_logging() {
77        slog_scope::warn!("{}", err; "context" => context);
78    } else {
79        warn!("{}: {}", context, err);
80    }
81}
82
83/// Log a debug message with context.
84pub fn log_debug<M: std::fmt::Display>(context: &str, msg: M) {
85    if is_structured_logging() {
86        slog_scope::debug!("{}", msg; "context" => context);
87    } else {
88        debug!("{}: {}", context, msg);
89    }
90}
91
92/// Log a trace message with context.
93pub fn log_trace<M: std::fmt::Display>(context: &str, msg: M) {
94    if is_structured_logging() {
95        slog_scope::trace!("{}", msg; "context" => context);
96    } else {
97        trace!("{}: {}", context, msg);
98    }
99}
100
101/// Log an info message with context.
102pub fn log_info<M: std::fmt::Display>(context: &str, msg: M) {
103    if is_structured_logging() {
104        slog_scope::info!("{}", msg; "context" => context);
105    } else {
106        info!("{}: {}", context, msg);
107    }
108}
109
110/// Log a message with additional context fields
111pub fn log_with_context(
112    level: log::Level,
113    message: impl std::fmt::Display,
114    context: &str,
115    fields: &[(&'static str, String)]
116) {
117    if is_structured_logging() {
118        match level {
119            log::Level::Error => {
120                let logger = slog_scope::logger();
121                let context_str = context.to_string(); // Clone to extend lifetime
122                let logger = logger.new(slog::o!("context" => context_str));
123                let logger = add_fields_to_logger(logger, fields);
124                slog::error!(logger, "{}", message);
125            },
126            log::Level::Warn => {
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::warn!(logger, "{}", message);
132            },
133            log::Level::Info => {
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::info!(logger, "{}", message);
139            },
140            log::Level::Debug => {
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::debug!(logger, "{}", message);
146            },
147            log::Level::Trace => {
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::trace!(logger, "{}", message);
153            },
154        }
155    } else {
156        // Fall back to standard logging with context
157        match level {
158            log::Level::Error => crate::error!("{}: {}", context, message),
159            log::Level::Warn => crate::warn!("{}: {}", context, message),
160            log::Level::Info => crate::info!("{}: {}", context, message),
161            log::Level::Debug => crate::debug!("{}: {}", context, message),
162            log::Level::Trace => crate::trace!("{}: {}", context, message),
163        }
164    }
165}
166
167/// Helper function to add fields to a logger
168fn add_fields_to_logger(logger: slog::Logger, fields: &[(&'static str, String)]) -> slog::Logger {
169    let mut result = logger;
170    for (k, v) in fields {
171        let v_clone = v.clone(); // Clone the value to extend its lifetime
172        result = result.new(slog::o!(*k => v_clone));
173    }
174    result
175}