lambda_otel_lite/
logger.rs

1//! Logging utilities for lambda-otel-lite.
2//!
3//! This module provides a simple logging interface with level filtering and prefixing.
4//!
5//! # Example
6//! ```
7//! use lambda_otel_lite::logger::Logger;
8//!
9//! // Create a logger for your module
10//! let logger = Logger::new("my_module");
11//! logger.info("Starting module");
12//! ```
13//!
14//! # Static Logger Example
15//! ```
16//! use lambda_otel_lite::logger::Logger;
17//!
18//! // Define a static logger for your module
19//! static LOGGER: Logger = Logger::const_new("my_module");
20//!
21//! // Use it directly
22//! LOGGER.info("Starting module");
23//! ```
24
25use std::env;
26use std::sync::OnceLock;
27
28// Default log level used for const initialization
29const DEFAULT_LOG_LEVEL: &str = "info";
30
31// Global log level cache
32static LOG_LEVEL: OnceLock<&'static str> = OnceLock::new();
33
34/// Get the log level from environment variables
35fn get_log_level() -> &'static str {
36    LOG_LEVEL.get_or_init(|| {
37        let level = env::var("AWS_LAMBDA_LOG_LEVEL")
38            .or_else(|_| env::var("LOG_LEVEL"))
39            .unwrap_or_else(|_| "info".to_string())
40            .to_lowercase();
41
42        match level.as_str() {
43            "none" | "error" | "warn" | "info" | "debug" => Box::leak(level.into_boxed_str()),
44            _ => "info",
45        }
46    })
47}
48
49/// Logger with level filtering and consistent prefixing
50#[derive(Clone)]
51pub struct Logger {
52    prefix: &'static str,
53    log_level_fn: fn() -> &'static str,
54}
55
56impl Logger {
57    /// Create a new logger with the given prefix
58    pub fn new(prefix: impl Into<String>) -> Self {
59        // Convert the prefix to a &'static str
60        let static_prefix = Box::leak(prefix.into().into_boxed_str());
61
62        Self {
63            prefix: static_prefix,
64            log_level_fn: get_log_level,
65        }
66    }
67
68    /// Create a new logger with the given prefix that can be used in const contexts
69    pub const fn const_new(prefix: &'static str) -> Self {
70        Self {
71            prefix,
72            log_level_fn: || DEFAULT_LOG_LEVEL,
73        }
74    }
75
76    // Get the current log level
77    fn log_level(&self) -> &'static str {
78        (self.log_level_fn)()
79    }
80
81    fn should_log(&self, level: &str) -> bool {
82        match self.log_level() {
83            "none" => false,
84            "error" => level == "error",
85            "warn" => matches!(level, "error" | "warn"),
86            "info" => matches!(level, "error" | "warn" | "info"),
87            "debug" => matches!(level, "error" | "warn" | "info" | "debug"),
88            _ => matches!(level, "error" | "warn" | "info"),
89        }
90    }
91
92    fn format_message(&self, message: &str) -> String {
93        format!("[{}] {}", self.prefix, message)
94    }
95
96    /// Log a debug message
97    pub fn debug(&self, message: impl AsRef<str>) {
98        if self.should_log("debug") {
99            println!("{}", self.format_message(message.as_ref()));
100        }
101    }
102
103    /// Log an info message
104    pub fn info(&self, message: impl AsRef<str>) {
105        if self.should_log("info") {
106            println!("{}", self.format_message(message.as_ref()));
107        }
108    }
109
110    /// Log a warning message
111    pub fn warn(&self, message: impl AsRef<str>) {
112        if self.should_log("warn") {
113            eprintln!("{}", self.format_message(message.as_ref()));
114        }
115    }
116
117    /// Log an error message
118    pub fn error(&self, message: impl AsRef<str>) {
119        if self.should_log("error") {
120            eprintln!("{}", self.format_message(message.as_ref()));
121        }
122    }
123}
124
125#[cfg(test)]
126mod tests {
127    use super::*;
128    use serial_test::serial;
129
130    #[test]
131    #[serial]
132    fn test_log_levels() {
133        let logger = Logger::new("test");
134
135        assert!(logger.should_log("error"));
136        assert!(logger.should_log("warn"));
137        assert!(logger.should_log("info"));
138        assert!(!logger.should_log("invalid"));
139    }
140
141    #[test]
142    #[serial]
143    fn test_format_message() {
144        let logger = Logger::new("test");
145
146        assert_eq!(logger.format_message("hello"), "[test] hello");
147    }
148}