lambda_otel_lite/
logger.rs1use std::env;
26use std::sync::OnceLock;
27
28const DEFAULT_LOG_LEVEL: &str = "info";
30
31static LOG_LEVEL: OnceLock<&'static str> = OnceLock::new();
33
34fn 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#[derive(Clone)]
51pub struct Logger {
52 prefix: &'static str,
53 log_level_fn: fn() -> &'static str,
54}
55
56impl Logger {
57 pub fn new(prefix: impl Into<String>) -> Self {
59 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 pub const fn const_new(prefix: &'static str) -> Self {
70 Self {
71 prefix,
72 log_level_fn: || DEFAULT_LOG_LEVEL,
73 }
74 }
75
76 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 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 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 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 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 fn logger_with_level(level: &'static str) -> Logger {
131 match level {
132 "none" => Logger {
133 prefix: "test",
134 log_level_fn: || "none",
135 },
136 "error" => Logger {
137 prefix: "test",
138 log_level_fn: || "error",
139 },
140 "warn" => Logger {
141 prefix: "test",
142 log_level_fn: || "warn",
143 },
144 "info" => Logger {
145 prefix: "test",
146 log_level_fn: || "info",
147 },
148 "debug" => Logger {
149 prefix: "test",
150 log_level_fn: || "debug",
151 },
152 _ => unreachable!("unsupported level"),
153 }
154 }
155
156 #[test]
157 #[serial]
158 fn test_log_levels() {
159 let none = logger_with_level("none");
160 assert!(!none.should_log("error"));
161
162 let error = logger_with_level("error");
163 assert!(error.should_log("error"));
164 assert!(!error.should_log("warn"));
165
166 let warn = logger_with_level("warn");
167 assert!(warn.should_log("error"));
168 assert!(warn.should_log("warn"));
169 assert!(!warn.should_log("info"));
170
171 let info = logger_with_level("info");
172 assert!(info.should_log("error"));
173 assert!(info.should_log("warn"));
174 assert!(info.should_log("info"));
175 assert!(!info.should_log("debug"));
176 assert!(!info.should_log("invalid"));
177
178 let debug = logger_with_level("debug");
179 assert!(debug.should_log("error"));
180 assert!(debug.should_log("warn"));
181 assert!(debug.should_log("info"));
182 assert!(debug.should_log("debug"));
183 }
184
185 #[test]
186 #[serial]
187 fn test_format_message() {
188 let logger = Logger::new("test");
189
190 assert_eq!(logger.format_message("hello"), "[test] hello");
191 }
192
193 #[test]
194 fn test_const_logger_uses_default_info_level() {
195 static LOGGER: Logger = Logger::const_new("const-test");
196
197 assert_eq!(LOGGER.log_level(), "info");
198 assert!(LOGGER.should_log("info"));
199 assert!(!LOGGER.should_log("debug"));
200 assert_eq!(LOGGER.format_message("hello"), "[const-test] hello");
201 }
202}