lightstreamer_rs/utils/
logger.rs

1//! # Utils Module
2//!
3//! This module provides a collection of utility functions, structures, and tools designed to simplify and support
4//! common tasks across the library. These utilities range from logging, time frame management, testing helpers,
5//! and other general-purpose helpers.
6//!
7//! ## Key Components
8//!
9//! ### Logger (`logger`)
10//!
11//! Handles application logging with configurable log levels. It includes safe and idempotent initialization to avoid
12//! redundant setups. Useful for debugging, tracing, and monitoring program behavior.
13//!
14//! **Log Levels:**
15//! - `DEBUG`: Detailed debugging information.
16//! - `INFO`: General application status information.
17//! - `WARN`: Non-critical issues that require attention.
18//! - `ERROR`: Significant problems causing failures.
19//! - `TRACE`: Fine-grained application execution details.
20//!
21//! ### Time Frames (`time`)
22//!
23//! Provides robust structures for managing common time frames used in financial or other periodic systems.
24//! This includes predefined constants for standard periods and support for custom periods.
25//!
26//! **Supported Time Frames:**
27//! - Microsecond
28//! - Millisecond
29//! - Second
30//! - Minute
31//! - Hour
32//! - Day
33//! - Week
34//! - Month
35//! - Quarter
36//! - Year
37//! - Custom periods (customizable to specific needs)
38//!
39//! ### Testing Utilities (`tests`)
40//!
41//! A set of functions and macros to simplify testing. These include utilities for relative equality comparisons
42//! and other common test-case behaviors.
43//!
44//!
45//! ### Miscellaneous Utilities (`others`)
46//!
47//! General-purpose functions for common operations, such as approximate equality checks, random selection from a
48//! collection, and iterating over combinations.
49//!
50//! ## Performance Characteristics
51//!
52//! - **Logger:** Initialization is thread-safe and happens only once, ensuring minimal performance impact.
53//! - **Time Frames:** All operations on time structures are constant-time.
54//! - **Random Selection:** Complexity is O(n), where `n` is the size of the collection.
55//! - **Combination Processing:** Complexity depends on the size of each combination and the number of combinations processed.
56//!
57//! ## Design Notes
58//!
59//! - **Logger:** Leverages the `tracing` crate, enabling structured and asynchronous logging.
60//! - **Time Frames:** Focuses on reusable constants while supporting flexible customizations.
61//! - **Testing Utilities:** Targets precise and consistent floating-point comparisons to prevent test inaccuracies.
62//! - **General Utilities:** Built with error handling, edge case scenarios, and performance in mind.
63//!
64
65use std::sync::Once;
66
67use tracing_subscriber::FmtSubscriber;
68
69use {std::env, tracing::Level};
70
71static INIT: Once = Once::new();
72
73/// Sets up a logger for the application
74///
75/// The logger level is determined by the `LOGLEVEL` environment variable.
76/// Supported log levels are:
77/// - `DEBUG`: Captures detailed debug information.
78/// - `ERROR`: Captures error messages.
79/// - `WARN`: Captures warnings.
80/// - `TRACE`: Captures detailed trace logs.
81/// - All other values default to `INFO`, which captures general information.
82///
83/// **Behavior:**
84/// - Concurrent calls to this function result in the logger being initialized only once.
85///
86/// # Panics
87/// This function panics if setting the default subscriber fails.
88pub fn setup_logger() {
89    INIT.call_once(|| {
90        let log_level = env::var("LOGLEVEL")
91            .unwrap_or_else(|_| "INFO".to_string())
92            .to_uppercase();
93
94        let level = match log_level.as_str() {
95            "DEBUG" => Level::DEBUG,
96            "ERROR" => Level::ERROR,
97            "WARN" => Level::WARN,
98            "TRACE" => Level::TRACE,
99            _ => Level::INFO,
100        };
101
102        let subscriber = FmtSubscriber::builder().with_max_level(level).finish();
103
104        tracing::subscriber::set_global_default(subscriber)
105            .expect("Error setting default subscriber");
106
107        tracing::debug!("Log level set to: {}", level);
108    });
109}
110
111/// Sets up a logger with a user-specified log level for platforms
112///
113/// **Parameters:**
114/// - `log_level`: The desired log level as a string. Supported levels are the same as for `setup_logger`.
115///
116/// **Behavior:**
117/// - Concurrent calls to this function result in the logger being initialized only once.
118///
119/// # Panics
120/// This function panics if setting the default subscriber fails.
121#[allow(unused_variables)]
122pub fn setup_logger_with_level(log_level: &str) {
123    INIT.call_once(|| {
124        let log_level = log_level.to_uppercase();
125
126        let level = match log_level.as_str() {
127            "DEBUG" => Level::DEBUG,
128            "ERROR" => Level::ERROR,
129            "WARN" => Level::WARN,
130            "TRACE" => Level::TRACE,
131            _ => Level::INFO,
132        };
133
134        let subscriber = FmtSubscriber::builder().with_max_level(level).finish();
135
136        tracing::subscriber::set_global_default(subscriber)
137            .expect("Error setting default subscriber");
138
139        tracing::debug!("Log level set to: {}", level);
140    });
141}
142
143#[cfg(test)]
144mod tests_setup_logger {
145    use super::setup_logger;
146    use std::env;
147    use tracing::subscriber::set_global_default;
148    use tracing_subscriber::FmtSubscriber;
149
150    #[test]
151    fn test_logger_initialization_info() {
152        unsafe {
153            env::set_var("LOGLEVEL", "INFO");
154        }
155        setup_logger();
156
157        assert!(
158            set_global_default(FmtSubscriber::builder().finish()).is_err(),
159            "Logger should already be set"
160        );
161    }
162
163    #[test]
164    fn test_logger_initialization_debug() {
165        unsafe {
166            env::set_var("LOGLEVEL", "DEBUG");
167        }
168        setup_logger();
169
170        assert!(
171            set_global_default(FmtSubscriber::builder().finish()).is_err(),
172            "Logger should already be set"
173        );
174    }
175
176    #[test]
177    fn test_logger_initialization_default() {
178        unsafe {
179            env::remove_var("LOGLEVEL");
180        }
181        setup_logger();
182
183        assert!(
184            set_global_default(FmtSubscriber::builder().finish()).is_err(),
185            "Logger should already be set"
186        );
187    }
188
189    #[test]
190    fn test_logger_called_once() {
191        unsafe {
192            env::set_var("LOGLEVEL", "INFO");
193        }
194
195        setup_logger(); // First call should set up the logger
196        setup_logger(); // Second call should not re-initialize
197
198        assert!(
199            set_global_default(FmtSubscriber::builder().finish()).is_err(),
200            "Logger should already be set and should not be reset"
201        );
202    }
203}
204
205#[cfg(test)]
206mod tests_setup_logger_bis {
207    use super::*;
208    use std::sync::Mutex;
209    use tracing::subscriber::with_default;
210    use tracing_subscriber::Layer;
211    use tracing_subscriber::layer::{Context, SubscriberExt};
212    use tracing_subscriber::registry;
213
214    static TEST_MUTEX: Mutex<()> = Mutex::new(());
215
216    #[derive(Clone)]
217    struct TestLayer {
218        level: std::sync::Arc<Mutex<Option<Level>>>,
219    }
220
221    impl<S> Layer<S> for TestLayer
222    where
223        S: tracing::Subscriber,
224    {
225        fn on_event(&self, event: &tracing::Event<'_>, _ctx: Context<'_, S>) {
226            let mut level = self.level.lock().unwrap();
227            *level = Some(*event.metadata().level());
228        }
229    }
230
231    fn create_test_layer() -> (TestLayer, std::sync::Arc<Mutex<Option<Level>>>) {
232        let level = std::sync::Arc::new(Mutex::new(None));
233        (
234            TestLayer {
235                level: level.clone(),
236            },
237            level,
238        )
239    }
240
241    #[test]
242    fn test_default_log_level() {
243        let _lock = TEST_MUTEX.lock().unwrap();
244        unsafe {
245            env::remove_var("LOGLEVEL");
246        }
247
248        let (layer, level) = create_test_layer();
249        let subscriber = registry().with(layer);
250
251        with_default(subscriber, || {
252            setup_logger();
253            tracing::info!("Test log");
254        });
255
256        assert_eq!(*level.lock().unwrap(), Some(Level::INFO));
257    }
258
259    #[test]
260    fn test_debug_log_level() {
261        let _lock = TEST_MUTEX.lock().unwrap();
262        unsafe {
263            env::set_var("LOGLEVEL", "DEBUG");
264        }
265
266        let (layer, level) = create_test_layer();
267        let subscriber = registry().with(layer);
268
269        with_default(subscriber, || {
270            setup_logger();
271            tracing::debug!("Test log");
272        });
273
274        assert_eq!(*level.lock().unwrap(), Some(Level::DEBUG));
275        unsafe {
276            env::remove_var("LOGLEVEL");
277        }
278    }
279
280    #[test]
281    fn test_error_log_level() {
282        let _lock = TEST_MUTEX.lock().unwrap();
283        unsafe {
284            env::set_var("LOGLEVEL", "ERROR");
285        }
286
287        let (layer, level) = create_test_layer();
288        let subscriber = registry().with(layer);
289
290        with_default(subscriber, || {
291            setup_logger();
292            tracing::error!("Test log");
293        });
294
295        assert_eq!(*level.lock().unwrap(), Some(Level::ERROR));
296        unsafe {
297            env::remove_var("LOGLEVEL");
298        }
299    }
300
301    #[test]
302    fn test_warn_log_level() {
303        let _lock = TEST_MUTEX.lock().unwrap();
304        unsafe {
305            env::set_var("LOGLEVEL", "WARN");
306        }
307        let (layer, level) = create_test_layer();
308        let subscriber = registry().with(layer);
309
310        with_default(subscriber, || {
311            setup_logger();
312            tracing::warn!("Test log");
313        });
314
315        assert_eq!(*level.lock().unwrap(), Some(Level::WARN));
316        unsafe {
317            env::remove_var("LOGLEVEL");
318        }
319    }
320
321    #[test]
322    fn test_trace_log_level() {
323        let _lock = TEST_MUTEX.lock().unwrap();
324        unsafe {
325            env::set_var("LOGLEVEL", "TRACE");
326        }
327
328        let (layer, level) = create_test_layer();
329        let subscriber = registry().with(layer);
330
331        with_default(subscriber, || {
332            setup_logger();
333            tracing::trace!("Test log");
334        });
335
336        assert_eq!(*level.lock().unwrap(), Some(Level::TRACE));
337
338        unsafe {
339            env::remove_var("LOGLEVEL");
340        }
341    }
342
343    #[test]
344    fn test_invalid_log_level() {
345        let _lock = TEST_MUTEX.lock().unwrap();
346        unsafe {
347            env::set_var("LOGLEVEL", "INVALID");
348        }
349
350        let (layer, level) = create_test_layer();
351        let subscriber = registry().with(layer);
352
353        with_default(subscriber, || {
354            setup_logger();
355            tracing::info!("Test log");
356        });
357
358        assert_eq!(*level.lock().unwrap(), Some(Level::INFO));
359        unsafe {
360            env::remove_var("LOGLEVEL");
361        }
362    }
363}