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}