celp_sdk/logger/
log.rs

1//! Provides interfaces for logging
2//!
3//! This includes functionality to log messages of various levels (Debug, Info, Warning, ...) to different backends,
4//! as well as setting the desired log level
5
6use std::io::Write;
7use std::path::Path;
8
9use chrono::Utc;
10use colored::Colorize;
11use env_logger::Env;
12use log::{Level, Record};
13
14/// SDK Log Level Enum
15#[derive(
16    Clone,
17    Copy,
18    Debug,
19    Default,
20    strum_macros::Display,
21    strum_macros::EnumString,
22    strum_macros::FromRepr,
23)]
24pub enum SDKLogLevel {
25    /// A critical error.
26    /// Maps to "Error" in log crate.
27    Critical = 1,
28    /// A standard error.
29    /// Maps to "Error" in log crate.
30    Error,
31    /// A warning.
32    /// Maps to "Warning in log crate.
33    Warning,
34    /// An informative message.
35    /// Logs to "Info" in log crate.
36    #[default]
37    Info,
38    /// A Debug message.
39    /// Maps to "Debug" in log crate.
40    Debug,
41    /// A trace message
42    /// Maps to "Trace" in log crate
43    Trace,
44}
45// The logging environment variable used to overwrite the logging levels.
46static LOG_ENV_VAR: &str = "CELP_LOG";
47
48/// SDKLogLevel implementation
49impl SDKLogLevel {
50    /// Returns max logging level available.
51    ///
52    /// # Example
53    /// ```rust,no_run
54    /// use celp_sdk::logger::SDKLogLevel;
55    ///
56    /// let max_level = SDKLogLevel::max();
57    /// ```
58    pub fn max() -> SDKLogLevel {
59        SDKLogLevel::Debug
60    }
61
62    /// Returns min logging level available.
63    ///
64    /// # Example
65    /// ```rust,no_run
66    /// use celp_sdk::logger::SDKLogLevel;
67    ///
68    /// let min_level = SDKLogLevel::min();
69    /// ```
70    pub fn min() -> SDKLogLevel {
71        SDKLogLevel::Critical
72    }
73}
74
75/// Implement 'From' and 'Into' functionality to convert the Log levels we like to the levels that the crate uses.
76/// This means we can do something like: 'let log_level: Level = SDKLogLevel::Info.into();'
77impl From<SDKLogLevel> for Level {
78    /// Converts an SDKLogLevel value to a Level value
79    ///
80    /// # Example
81    /// ```rust,no_run
82    /// use log::Level;
83    /// use celp_sdk::logger::SDKLogLevel;
84    ///
85    /// let converted_log_level: Level = SDKLogLevel::default().into();
86    /// ```
87    fn from(sdk_ll: SDKLogLevel) -> Self {
88        match sdk_ll {
89            SDKLogLevel::Critical => Level::Error,
90            SDKLogLevel::Error => Level::Error,
91            SDKLogLevel::Warning => Level::Warn,
92            SDKLogLevel::Info => Level::Info,
93            SDKLogLevel::Debug => Level::Debug,
94            SDKLogLevel::Trace => Level::Trace,
95        }
96    }
97}
98
99/// Same as above, but the other way around.
100impl From<Level> for SDKLogLevel {
101    /// Converts a Level value to an SDKLogLevel value
102    ///
103    /// # Example
104    /// ```rust,no_run
105    /// use log::Level;
106    /// use celp_sdk::logger::SDKLogLevel;
107    ///
108    /// let converted_log_level: SDKLogLevel = Level::Error.into();
109    /// ```
110    fn from(sdk_ll: Level) -> Self {
111        match sdk_ll {
112            Level::Error => SDKLogLevel::Error,
113            Level::Warn => SDKLogLevel::Warning,
114            Level::Info => SDKLogLevel::Info,
115            Level::Debug => SDKLogLevel::Debug,
116            Level::Trace => SDKLogLevel::Trace,
117        }
118    }
119}
120
121/// Initialise the logging functionality to a certain level.
122///
123/// # Arguments
124///
125/// * `module_name` - A string to denote the calling application. It can be any string. It does not have to match the exact application name.
126/// * `log_level` - The level of logging to use. If not given, will use default logging level.
127///
128/// # Example
129/// ```rust,no_run
130/// use celp_sdk::logger::{SDKLogLevel, init_logger};
131///
132/// let app_name = "test";
133/// let level = SDKLogLevel::default();
134///
135/// init_logger(app_name, Some(level));
136/// ```
137pub fn init_logger(module_name: &str, log_level: Option<SDKLogLevel>) {
138    // Set default logging level (converts to "Info" for our logging levels).
139    let mut converted_log_level: Level = SDKLogLevel::default().into();
140    // Convert the SDKLogLevel to standard Level type
141    // If a log level was specified.
142    if let Some(ll) = log_level {
143        // Convert the SDKLogLevel to Level.
144        converted_log_level = ll.into();
145    }
146
147    // Set up a new Builder instance here so that we can ignore some of the presets like the "RUST_LOG" environment variable and such.
148    let mut builder = env_logger::Builder::new();
149
150    // Convert the 'Level' type to 'LevelFilter' type with 'to_level_filter'.
151    // NOTE: After some testing, it is seen that `filter_level` is required first before setting `filter_module` or else the level filter does not get set correctly.
152    builder.filter_level(converted_log_level.to_level_filter());
153    // Then set the filter for the module.
154    builder.filter_module(module_name, converted_log_level.to_level_filter());
155
156    // Need to check if the custom environment variable is set first.
157    if std::env::var(LOG_ENV_VAR).is_ok() {
158        // Specify a custom environment variable other than the default "RUST_LOG"
159        let env = Env::new().filter(LOG_ENV_VAR);
160        // Set the environment variable to use for overwriting log levels.
161        builder.parse_env(env);
162    }
163    // Set the output as stdout (env_logger uses stderr by default).
164    builder.target(env_logger::Target::Stdout);
165
166    //Format the output message.
167    builder.format(|buf, record| writeln!(buf, "{}", format_log_str(record)));
168
169    // Initialise the builder.
170    builder.init();
171}
172
173/// Format the log string in a similar manner to C++ SDK logger.
174/// Will output in the following format:
175/// "[1970-01-01T12:34:56.012345Z] [main.rs:123] [---D---] log message"
176fn format_log_str(record: &Record) -> String {
177    // Get the current time.
178    let now = Utc::now();
179    let formatted_time = now.format("[%Y-%m-%dT%H:%M:%S%.6fZ]").to_string();
180    // Get the file name or use the default string.
181    let filename = record.file().unwrap_or("Unknown file path");
182    // Convert to Path object to get the file name at end of string.
183    let path = Path::new(filename);
184    let line = record.line().unwrap_or(0);
185    // Convert the log level to a str, iterator over the characters and grab the first letter.
186    let sdk_log_level: SDKLogLevel = record.level().into();
187    let short_log_level_char = sdk_log_level.to_string().chars().next().unwrap();
188    let mut short_log_level_str = String::from("---X---");
189    // Replace the "X" with the correct character.
190    short_log_level_str.replace_range(3..4, &short_log_level_char.to_string());
191    // Based on the log level, the string should be a different colour.
192    // TODO: Confirm color choice with C++ version.
193    let short_log_level_str_colored = match sdk_log_level {
194        SDKLogLevel::Critical => short_log_level_str.purple(),
195        SDKLogLevel::Error => short_log_level_str.red(),
196        SDKLogLevel::Warning => short_log_level_str.yellow(),
197        SDKLogLevel::Info => short_log_level_str.green(),
198        SDKLogLevel::Debug => short_log_level_str.blue(),
199        SDKLogLevel::Trace => short_log_level_str.white(),
200    };
201
202    // NOTE: Will not implement thread ID printing for now. Rust doesn't have any nice ways for this library to get the thread ID of the calling application.
203    //       We would have to pss the thread ID directly from the calling application. That would just be messy.
204    format!(
205        "{} [{:?}:{:?}] [{}] {:?}",
206        formatted_time,
207        path.file_name()
208            .unwrap_or(std::ffi::OsStr::new("<unknown>")),
209        line,
210        short_log_level_str_colored,
211        record.args()
212    )
213}
214
215/// The following macros convert the 'log' crates macros to use our own log levels.
216///
217/// | SDK      | log   |
218/// |__________|_______|
219/// | critical | error |
220/// | error    | error |
221/// | warning  | warn  |
222/// | info     | info  |
223/// | debug    | debug |
224/// | trace    | trace |
225///
226pub use log::{debug as _debug, error as _error, info as _info, trace as _trace, warn as _warn};
227
228/// # Example
229/// ```rust,no_run
230/// use celp_sdk::critical;
231///
232/// critical!("It all went terribly wrong!");
233/// ```
234///
235#[macro_export]
236macro_rules! critical {
237    ($($arg:tt)*) => {
238        // Delegate to the 'log' crates 'error!' macro
239        $crate::logger::_error!($($arg)*)
240    }
241}
242
243/// # Example
244/// ```rust,no_run
245/// use celp_sdk::error;
246///
247/// error!("Oh oh: {}", "details");
248/// ```
249#[macro_export]
250macro_rules! error {
251    ($($arg:tt)*) => {
252        // Delegate to the 'log' crates 'error!' macro
253        $crate::logger::_error!($($arg)*)
254    }
255}
256
257/// # Example
258/// ```rust,no_run
259/// use celp_sdk::warning;
260///
261/// warning!("woopsie: {}", "details");
262/// ```
263#[macro_export]
264macro_rules! warning {
265    ($($arg:tt)*) => {
266        // Delegate to the 'log' crates 'warning!' macro
267        $crate::logger::_warn!($($arg)*)
268    }
269}
270
271/// # Example
272/// ```rust,no_run
273/// use celp_sdk::info;
274///
275/// info!("I'd like to tell you about: {}", "what I'm doing now");
276/// ```
277#[macro_export]
278macro_rules! info {
279    ($($arg:tt)*) => {
280        // Delegate to the 'log' crates 'info!' macro
281        $crate::logger::_info!($($arg)*)
282    }
283}
284
285/// # Example
286/// ```rust,no_run
287/// use celp_sdk::debug;
288///
289/// debug!("I know you're probably not really interested in this: {}!", "but here it is anyway");
290/// ```
291#[macro_export]
292macro_rules! debug {
293    ($($arg:tt)*) => {
294        // Delegate to the 'log' crates 'debug!' macro
295        $crate::logger::_debug!($($arg)*)
296    }
297}
298
299/// # Example
300/// ```rust,no_run
301/// use celp_sdk::trace;
302///
303/// trace!("I'll inundate you with information!");
304/// ```
305#[macro_export]
306macro_rules! trace {
307    ($($arg:tt)*) => {
308        // Delegate to the 'log' crates 'debug!' macro
309        $crate::logger::_trace!($($arg)*)
310    }
311}