Skip to main content

sh_layer1/observability/
logging.rs

1//! Structured logging utilities.
2
3use crate::observability::config::LogFormat;
4use tracing::Level;
5
6/// Log level wrapper for structured logging.
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum LogLevel {
9    Trace,
10    Debug,
11    Info,
12    Warn,
13    Error,
14}
15
16impl From<LogLevel> for Level {
17    fn from(level: LogLevel) -> Self {
18        match level {
19            LogLevel::Trace => Level::TRACE,
20            LogLevel::Debug => Level::DEBUG,
21            LogLevel::Info => Level::INFO,
22            LogLevel::Warn => Level::WARN,
23            LogLevel::Error => Level::ERROR,
24        }
25    }
26}
27
28impl From<Level> for LogLevel {
29    fn from(level: Level) -> Self {
30        match level {
31            Level::TRACE => LogLevel::Trace,
32            Level::DEBUG => LogLevel::Debug,
33            Level::INFO => LogLevel::Info,
34            Level::WARN => LogLevel::Warn,
35            Level::ERROR => LogLevel::Error,
36        }
37    }
38}
39
40/// Log a structured message with attributes.
41pub fn log(level: LogLevel, message: &str, attributes: &[(&str, &str)]) {
42    let level: Level = level.into();
43
44    match level {
45        Level::TRACE => tracing::trace!(message, ?attributes),
46        Level::DEBUG => tracing::debug!(message, ?attributes),
47        Level::INFO => tracing::info!(message, ?attributes),
48        Level::WARN => tracing::warn!(message, ?attributes),
49        Level::ERROR => tracing::error!(message, ?attributes),
50    }
51}
52
53/// Initialize the tracing subscriber with the given format.
54pub fn init_subscriber(log_format: LogFormat) -> Result<(), String> {
55    use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};
56
57    let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info"));
58
59    match log_format {
60        LogFormat::Pretty => {
61            tracing_subscriber::registry()
62                .with(env_filter)
63                .with(tracing_subscriber::fmt::layer().pretty())
64                .try_init()
65                .map_err(|e| format!("Failed to init subscriber: {}", e))?;
66        }
67        LogFormat::Json => {
68            tracing_subscriber::registry()
69                .with(env_filter)
70                .with(tracing_subscriber::fmt::layer().json())
71                .try_init()
72                .map_err(|e| format!("Failed to init subscriber: {}", e))?;
73        }
74    }
75
76    Ok(())
77}
78
79#[cfg(test)]
80mod tests {
81    use super::*;
82
83    #[test]
84    fn test_log_level_conversion() {
85        assert_eq!(Level::from(LogLevel::Trace), Level::TRACE);
86        assert_eq!(Level::from(LogLevel::Debug), Level::DEBUG);
87        assert_eq!(Level::from(LogLevel::Info), Level::INFO);
88        assert_eq!(Level::from(LogLevel::Warn), Level::WARN);
89        assert_eq!(Level::from(LogLevel::Error), Level::ERROR);
90    }
91
92    #[test]
93    fn test_log_level_reverse_conversion() {
94        assert_eq!(LogLevel::from(Level::TRACE), LogLevel::Trace);
95        assert_eq!(LogLevel::from(Level::DEBUG), LogLevel::Debug);
96        assert_eq!(LogLevel::from(Level::INFO), LogLevel::Info);
97        assert_eq!(LogLevel::from(Level::WARN), LogLevel::Warn);
98        assert_eq!(LogLevel::from(Level::ERROR), LogLevel::Error);
99    }
100
101    #[test]
102    fn test_log_function() {
103        // Should not panic
104        log(LogLevel::Info, "test message", &[("key", "value")]);
105        log(LogLevel::Debug, "another message", &[]);
106    }
107}