nu_analytics/logger/
mod.rs1use std::fmt::Arguments;
8#[cfg(feature = "log-debug")]
9use std::sync::atomic::AtomicBool;
10use std::sync::atomic::{AtomicU8, Ordering};
11use std::sync::LazyLock;
12
13#[cfg(feature = "file-logging")]
14use std::{
15 fs::{File, OpenOptions},
16 io::Write,
17 sync::Mutex,
18};
19
20#[cfg(target_arch = "wasm32")]
21use wasm_bindgen::JsValue;
22#[cfg(target_arch = "wasm32")]
23use web_sys::console;
24
25#[derive(Copy, Clone, Debug, PartialEq, Eq)]
27pub enum Level {
28 Error = 1,
30 Warn = 2,
32 Info = 3,
34 Debug = 4,
36}
37
38const fn default_level() -> u8 {
39 if cfg!(feature = "log-debug") {
40 Level::Debug as u8
41 } else if cfg!(feature = "log-info") {
42 Level::Info as u8
43 } else {
44 Level::Warn as u8
45 }
46}
47
48static LOG_LEVEL: LazyLock<AtomicU8> = LazyLock::new(|| AtomicU8::new(default_level()));
50#[cfg(feature = "log-debug")]
51static DEBUG_ENABLED: AtomicBool = AtomicBool::new(true);
52#[cfg(feature = "verbose")]
53static VERBOSE_ENABLED: AtomicBool = AtomicBool::new(false);
54#[cfg(feature = "file-logging")]
55static LOG_FILE: LazyLock<Mutex<Option<File>>> = LazyLock::new(|| Mutex::new(None));
56
57pub fn set_level(level: Level) {
59 LOG_LEVEL.store(level as u8, Ordering::SeqCst);
60}
61
62#[must_use]
63pub fn set_level_from_str(level: &str) -> bool {
65 match level.to_ascii_lowercase().as_str() {
66 "error" | "err" => {
67 set_level(Level::Error);
68 true
69 }
70 "warn" | "warning" => {
71 set_level(Level::Warn);
72 true
73 }
74 "info" => {
75 set_level(Level::Info);
76 true
77 }
78 "debug" => {
79 set_level(Level::Debug);
80 true
81 }
82 _ => false,
83 }
84}
85
86#[cfg(feature = "log-debug")]
87pub fn enable_debug() {
89 DEBUG_ENABLED.store(true, Ordering::SeqCst);
90}
91#[cfg(not(feature = "log-debug"))]
92pub fn enable_debug() {}
94
95#[cfg(feature = "log-debug")]
96pub fn disable_debug() {
98 DEBUG_ENABLED.store(false, Ordering::SeqCst);
99}
100#[cfg(not(feature = "log-debug"))]
101pub fn disable_debug() {}
103
104#[cfg(feature = "log-debug")]
105pub fn is_debug_enabled() -> bool {
107 DEBUG_ENABLED.load(Ordering::SeqCst)
108}
109#[cfg(not(feature = "log-debug"))]
110pub fn is_debug_enabled() -> bool {
112 false
113}
114
115#[cfg(feature = "verbose")]
116pub fn enable_verbose() {
118 VERBOSE_ENABLED.store(true, Ordering::SeqCst);
119}
120#[cfg(not(feature = "verbose"))]
121pub fn enable_verbose() {}
123
124#[cfg(feature = "verbose")]
125pub fn disable_verbose() {
127 VERBOSE_ENABLED.store(false, Ordering::SeqCst);
128}
129#[cfg(not(feature = "verbose"))]
130pub fn disable_verbose() {}
132
133#[cfg(feature = "verbose")]
134pub fn is_verbose_enabled() -> bool {
136 VERBOSE_ENABLED.load(Ordering::SeqCst)
137}
138#[cfg(not(feature = "verbose"))]
139pub fn is_verbose_enabled() -> bool {
141 false
142}
143
144#[cfg(feature = "file-logging")]
145#[must_use]
146pub fn init_file_logging(path: &std::path::Path) -> bool {
148 OpenOptions::new()
149 .create(true)
150 .append(true)
151 .open(path)
152 .is_ok_and(|file| {
153 LOG_FILE.lock().is_ok_and(|mut log_file| {
154 *log_file = Some(file);
155 true
156 })
157 })
158}
159
160#[cfg(not(feature = "file-logging"))]
161pub fn init_file_logging(_path: &std::path::Path) -> bool {
163 false
164}
165
166#[cfg(feature = "file-logging")]
167fn write_to_file(message: &str) {
168 if let Ok(mut log_file) = LOG_FILE.lock() {
169 if let Some(ref mut file) = *log_file {
170 let _ = writeln!(file, "{message}");
171 let _ = file.flush();
172 }
173 }
174}
175
176#[cfg(not(feature = "file-logging"))]
177fn write_to_file(_message: &str) {}
178
179#[cfg(feature = "file-logging")]
180fn is_file_logging_active() -> bool {
181 LOG_FILE.lock().map(|lf| lf.is_some()).unwrap_or(false)
182}
183#[cfg(not(feature = "file-logging"))]
184fn is_file_logging_active() -> bool {
185 false
186}
187
188fn emit(prefix: &str, msg: &str, to_stderr: bool) {
189 #[cfg(feature = "file-logging")]
190 {
191 if is_file_logging_active() && !prefix.is_empty() {
192 let file_message = format!("{prefix} {msg}");
193 write_to_file(&file_message);
194 return;
195 }
196 }
197 #[cfg(target_arch = "wasm32")]
198 {
199 let _ = to_stderr;
200 if prefix.is_empty() {
201 console::log_1(&JsValue::from_str(msg));
202 } else {
203 let formatted = format!("%c{} {}", prefix, msg);
204 fn style_for(prefix: &str) -> &'static str {
205 match prefix {
206 "[ERROR]" => "color:#fff;background:#c0392b;font-weight:bold;padding:1px 4px;border-radius:3px",
207 "[WARN]" => "color:#000;background:#ffeb3b;font-weight:bold;padding:1px 4px;border-radius:3px",
208 "[INFO]" => "",
209 "[DEBUG]" => "color:#000;background:#bdc3c7;padding:1px 4px;border-radius:3px",
210 _ => "font-weight:bold",
211 }
212 }
213 let style = style_for(prefix);
214 let formatted_js = JsValue::from_str(&formatted);
215 let style_js = JsValue::from_str(style);
216 match prefix {
217 "[ERROR]" => console::error_2(&formatted_js, &style_js),
218 "[WARN]" => console::warn_2(&formatted_js, &style_js),
219 _ => console::log_2(&formatted_js, &style_js),
220 }
221 }
222 }
223 #[cfg(not(target_arch = "wasm32"))]
224 {
225 if to_stderr {
226 if prefix.is_empty() {
227 eprintln!("{msg}");
228 } else {
229 eprintln!("{prefix} {msg}");
230 }
231 } else if prefix.is_empty() {
232 println!("{msg}");
233 } else {
234 println!("{prefix} {msg}");
235 }
236 }
237}
238
239fn should_log(level: Level) -> bool {
240 match level {
241 Level::Info => {
242 if !cfg!(feature = "log-info") {
243 return false;
244 }
245 }
246 Level::Debug => {
247 if !cfg!(feature = "log-debug") {
248 return false;
249 }
250 }
251 _ => {}
252 }
253 let current = LOG_LEVEL.load(Ordering::SeqCst);
254 (level as u8) <= current && (level != Level::Debug || is_debug_enabled())
255}
256
257pub fn log_impl(level: Level, args: Arguments) {
259 if !should_log(level) {
260 return;
261 }
262 let msg = args.to_string();
263 match level {
264 Level::Error => emit("[ERROR]", &msg, true),
265 Level::Warn => emit("[WARN]", &msg, true),
266 Level::Info => emit("[INFO]", &msg, false),
267 Level::Debug => emit("[DEBUG]", &msg, false),
268 }
269}
270
271#[macro_export]
272macro_rules! error { ($($arg:tt)*) => { $crate::logger::log_impl($crate::logger::Level::Error, format_args!($($arg)*)) }; }
274#[macro_export]
275macro_rules! warn { ($($arg:tt)*) => { $crate::logger::log_impl($crate::logger::Level::Warn, format_args!($($arg)*)) }; }
277#[macro_export]
278macro_rules! info { ($($arg:tt)*) => { $crate::logger::log_impl($crate::logger::Level::Info, format_args!($($arg)*)) }; }
280#[macro_export]
281macro_rules! debug { ($($arg:tt)*) => { $crate::logger::log_impl($crate::logger::Level::Debug, format_args!($($arg)*)) }; }
283#[macro_export]
284macro_rules! verbose {
286 ($($arg:tt)*) => {
287 #[cfg(feature = "verbose")]
288 {
289 if $crate::logger::is_verbose_enabled() { println!($($arg)*); }
290 }
291 }
292}