logging_rs/
lib.rs

1#![doc = include_str!("../.github/README.md")]
2// Logging-rs
3// Version: 1.1.0
4
5// Copyright (c) 2023-present I Language Development.
6
7// Permission is hereby granted, free of charge, to any person obtaining a
8// copy of this software and associated documentation files (the 'Software'),
9// to deal in the Software without restriction, including without limitation
10// the rights to use, copy, modify, merge, publish, distribute, sublicense,
11// and/or sell copies of the Software, and to permit persons to whom the
12// Software is furnished to do so, subject to the following conditions:
13
14// The above copyright notice and this permission notice shall be included in
15// all copies or substantial portions of the Software.
16
17// THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS
18// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
23// DEALINGS IN THE SOFTWARE.
24
25/////////////
26// EXPORTS //
27/////////////
28
29pub mod errors;
30
31
32/////////////
33// IMPORTS //
34/////////////
35
36use std;
37use std::io::Write;
38
39use chrono;
40
41
42////////////////
43// LOG LEVELS //
44////////////////
45
46/// Log levels
47#[derive(Copy, Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
48pub enum Level {
49    /// Debug log level. The default value
50    #[default]
51    DEBUG,
52    /// Info log level
53    INFO,
54    /// Warn log level
55    WARN,
56    /// Error log level
57    ERROR,
58    /// Fatal log level
59    FATAL,
60    /// Message log level
61    MESSAGE
62}
63
64
65/////////////////
66// OUTPUT TYPE //
67/////////////////
68
69/// Output types
70#[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
71pub enum Output {
72    /// Stdout. The default value
73    #[default]
74    STDOUT,
75    /// Stderr
76    STDERR,
77    /// File
78    FILE {
79        /// File path
80        path: String
81    }
82}
83
84
85///////////////
86// FORMATTER //
87///////////////
88
89/// Logging formatter object.
90///
91/// Use [`Formatter::new()`] to create formatter objects instead of using this struct.
92///
93/// # Parameters
94///
95/// - `color_format_string`: Format string supporting special ASCII control characters
96/// - `format_string`: Format string *NOT* supporting special ASCII control characters
97/// - `timestamp_format`: Timestamp format string in strftime format
98///
99/// # Returns
100///
101/// A new `Formatter` object with the specified format strings.
102///
103/// # Examples
104///
105/// ```rust
106/// # use logging_rs;
107/// logging_rs::Formatter {
108///     color_format_string: "format string with color support".to_owned(),
109///     format_string: "format string".to_owned(),
110///     timestamp_format: "timestamp format".to_owned()
111/// };
112/// ```
113#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
114pub struct Formatter {
115    /// Format string supporting special ASCII control characters
116    pub color_format_string: String,
117    /// Format string *NOT* supporting special ASCII control characters
118    pub format_string: String,
119    /// Timestamp format string in strftime format
120    pub timestamp_format: String,
121}
122
123impl Default for Formatter {
124    fn default() -> Formatter {
125        return Formatter::new("[{{color.bright_blue}}{{timestamp}}{{end}}] [{{level}}] {{path}}: {{message}}", "[{{timestamp}}] [{{level}}] {{path}}: {{message}}", "%Y-%m-%d %H:%M:%S");
126    }
127}
128
129impl Formatter {
130    /// Creates a new formatter object.
131    ///
132    /// # Parameters
133    ///
134    /// - `color_format_string`: Format string supporting special ASCII control characters
135    /// - `format_string`: Format string *NOT* supporting special ASCII control characters
136    /// - `timestamp_format`: Timestamp format string in strftime format
137    ///
138    /// # Returns
139    ///
140    /// A new `Formatter` object with the specified format strings.
141    ///
142    /// # Examples
143    ///
144    /// ```rust
145    /// # use logging_rs;
146    /// logging_rs::Formatter::new(
147    ///     "[{{color.bright_blue}}{{timestamp}}{{end}}] [{{level}}] {{path}}: {{message}}",
148    ///     "[{{timestamp}}] [{{level}}] {{path}}: {{message}}",
149    ///     "%Y-%m-%d %H:%M:%S"
150    /// );
151    /// ```
152    ///
153    /// # See also
154    ///
155    /// - [`Formatter`]
156    pub fn new(color_format_string: &str, format_string: &str, timestamp_format: &str) -> Formatter {
157        Formatter {
158            color_format_string: color_format_string.to_owned(),
159            format_string: format_string.to_owned(),
160            timestamp_format: timestamp_format.to_owned()
161        }
162    }
163
164    /// Formats the given message.
165    ///
166    /// # Parameters
167    ///
168    /// - `self`: The formatter object
169    /// - `output`: The [`Output`] to write to
170    /// - `level`: The log [`Level`] to use for formatting
171    /// - `message`: The message to log
172    /// - `arguments`: A vector of additional formatting arguments
173    ///
174    /// # Returns
175    ///
176    /// A `String` containing the formatted message.
177    ///
178    /// # Examples
179    ///
180    /// ```rust
181    /// # use logging_rs;
182    /// # let formatter: logging_rs::Formatter = logging_rs::Formatter::default();
183    /// formatter.format(
184    ///     logging_rs::Output::default(),
185    ///     logging_rs::Level::default(),
186    ///     "Some message with an {{argument}}",
187    ///     vec![("argument", "replaced value".to_string())]
188    /// );
189    /// ```
190    ///
191    /// # See also
192    ///
193    /// - [`Formatter`]
194    /// - [`Output`]
195    /// - [`Level`]
196    #[doc = include_str!("../.github/formatting_codes.md")]
197    pub fn format<'a>(&self, output: Output, level: Level, message: &'a str, mut extra_arguments: Vec<(&str, String)>) -> String {
198        let mut arguments: Vec<(&str, String)> = vec![];
199        let mut colors: Vec<(&str, String)> = vec![
200            // Formatting codes
201            ("end", "\x1b[0m".to_string()),
202            ("bold", "\x1b[1m".to_string()),
203            ("italic", "\x1b[3m".to_string()),
204            ("underline", "\x1b[4m".to_string()),
205            ("overline", "\x1b[53m".to_string()),
206
207            // Foreground colors
208            ("color.black", "\x1b[30m".to_string()),
209            ("color.red", "\x1b[31m".to_string()),
210            ("color.green", "\x1b[32m".to_string()),
211            ("color.yellow", "\x1b[33m".to_string()),
212            ("color.blue", "\x1b[34m".to_string()),
213            ("color.magenta", "\x1b[35m".to_string()),
214            ("color.cyan", "\x1b[36m".to_string()),
215            ("color.white", "\x1b[37m".to_string()),
216
217            // Bright foreground colors
218            ("color.bright_black", "\x1b[90m".to_string()),
219            ("color.bright_red", "\x1b[91m".to_string()),
220            ("color.bright_green", "\x1b[92m".to_string()),
221            ("color.bright_yellow", "\x1b[93m".to_string()),
222            ("color.bright_blue", "\x1b[94m".to_string()),
223            ("color.bright_magenta", "\x1b[95m".to_string()),
224            ("color.bright_cyan", "\x1b[96m".to_string()),
225            ("color.bright_white", "\x1b[97m".to_string()),
226
227            // Background colors
228            ("back.black", "\x1b[40m".to_string()),
229            ("back.red", "\x1b[41m".to_string()),
230            ("back.green", "\x1b[42m".to_string()),
231            ("back.yellow", "\x1b[43m".to_string()),
232            ("back.blue", "\x1b[44m".to_string()),
233            ("back.magenta", "\x1b[45m".to_string()),
234            ("back.cyan", "\x1b[46m".to_string()),
235            ("back.white", "\x1b[47m".to_string()),
236
237            // Bright background colors
238            ("back.bright_black", "\x1b[100m".to_string()),
239            ("back.bright_red", "\x1b[101m".to_string()),
240            ("back.bright_green", "\x1b[102m".to_string()),
241            ("back.bright_yellow", "\x1b[103m".to_string()),
242            ("back.bright_blue", "\x1b[104m".to_string()),
243            ("back.bright_magenta", "\x1b[105m".to_string()),
244            ("back.bright_cyan", "\x1b[106m".to_string()),
245            ("back.bright_white", "\x1b[107m".to_string()),
246        ];
247
248        let level_string: (&str, String) = ("level", match level {
249            Level::DEBUG => "DEBUG",
250            Level::INFO => "INFO",
251            Level::WARN => "WARNING",
252            Level::ERROR => "ERROR",
253            Level::FATAL => "FATAL",
254            Level::MESSAGE => "MESSAGE"
255        }.to_string());
256        let colored_level_string: (&str, String) = ("level", match level {
257            Level::DEBUG => "DEBUG",
258            Level::INFO => "{{color.blue}}INFO{{end}}",
259            Level::WARN => "{{color.yellow}}WARNING{{end}}",
260            Level::ERROR => "{{color.red}}ERROR{{end}}",
261            Level::FATAL => "{{color.red}}FATAL{{end}}",
262            Level::MESSAGE => "{{color.blue}}MESSAGE{{end}}"
263        }.to_string());
264
265        arguments.push(("message", message.to_string()));
266        arguments.push(("timestamp", chrono::Utc::now().format(&self.timestamp_format).to_string()));
267        arguments.append(&mut extra_arguments);
268
269        let mut result: String = match output {
270            Output::STDOUT | Output::STDERR => {
271                arguments.push(colored_level_string);
272                self.color_format_string.to_owned()
273            },
274            _ => {
275                arguments.push(level_string);
276                self.format_string.to_owned()
277            }
278        };
279
280        arguments.append(&mut colors);
281
282        for (key, value) in arguments {
283            result = result.replace(("{{".to_owned() + key + "}}").as_str(), &value);
284        }
285
286        return result.clone();
287    }
288}
289
290
291///////////////////
292// LOGGER STRUCT //
293///////////////////
294
295/// Logger object.
296///
297/// Use [`Logger::new()`] to create logger objects instead of using this struct.
298///
299/// # Parameters
300///
301/// - `formatter`: The [`Formatter`] to use for formatting messages
302/// - `writable_list`: A vector of [`Output`]s to write to
303///
304/// # Returns
305///
306/// A new `Logger` object with the specified formatter and writables.
307///
308/// # Examples
309///
310/// ```rust
311/// # use logging_rs;
312/// logging_rs::Logger {
313///     formatter: logging_rs::Formatter::default(),
314///     writable_list: vec![logging_rs::Output::default()]
315/// };
316/// ```
317#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
318pub struct Logger {
319    pub formatter: Formatter,
320    pub writable_list: Vec<Output>
321}
322
323impl Default for Logger {
324    fn default() -> Logger {
325        return Logger::new(Formatter::default(), vec![Output::STDOUT]);
326    }
327}
328
329impl Logger {
330    /// Creates a new logger object.
331    ///
332    /// # Parameters
333    ///
334    /// - `formatter`: The [`Formatter`] to use for formatting messages
335    /// - `writable_list`: A vector of [`Output`]s to write to
336    ///
337    /// # Returns
338    ///
339    /// A new `Logger` object with the specified formatter and writables.
340    ///
341    /// # Examples
342    ///
343    /// ```rust
344    /// # use logging_rs;
345    /// logging_rs::Logger::new(logging_rs::Formatter::default(), vec![logging_rs::Output::default()]);
346    /// ```
347    ///
348    /// # See also
349    ///
350    /// - [`Logger`]
351    pub fn new(formatter: Formatter, writable_list: Vec<Output>) -> Logger {
352        Logger {
353            formatter: formatter,
354            writable_list: writable_list
355        }
356    }
357
358    /// Logs the given message.
359    ///
360    /// # Parameters
361    ///
362    /// - `self`: The logger object
363    /// - `message`: The message to log
364    /// - `level`: The log [`Level`] to use for logging
365    /// - `path`: The path of the calling file
366    /// - `arguments`: A list of arguments to use when formatting the message
367    ///
368    /// # Returns
369    ///
370    /// A `String` containing the formatted message.
371    ///
372    /// # Examples
373    ///
374    /// ```rust
375    /// # use logging_rs;
376    /// # let logger: logging_rs::Logger = logging_rs::Logger::default();
377    /// logger.log(
378    ///     "Some message",
379    ///     logging_rs::Level::default(),
380    ///     "src/lib.rs",
381    ///     vec![]
382    /// );
383    /// ```
384    ///
385    /// # See also
386    ///
387    /// - [`debug!()`]
388    /// - [`info!()`]
389    /// - [`warn!()`]
390    /// - [`error!()`]
391    /// - [`fatal!()`]
392    /// - [`log!()`]
393    /// - [`Logger`]
394    /// - [`Level`]
395    pub fn log(&self, message: &str, level: Level, path: &str, mut arguments: Vec<(&str, String)>) {
396        arguments.push(("path", path.to_string()));
397        for writable in self.writable_list.clone() {
398            let formatted: String = self.formatter.format(writable.clone(), level, message, arguments.clone());
399
400            match writable {
401                Output::STDOUT => println!("{}", formatted),
402                Output::STDERR => eprintln!("{}", formatted),
403                Output::FILE { ref path } => {
404                    let file: Result<std::fs::File, std::io::Error> = std::fs::OpenOptions::new().create(true).append(true).write(true).open(path);
405                    let write: Result<_, std::io::Error> = write!(file.as_ref().unwrap(), "{}", formatted);
406
407                    if let Err(error) = file {
408                        errors::Error::new("File error", "The file could not be opened", 1).raise(format!("Path: {}\nError: {}", path, error).as_str());
409                    }
410
411                    if let Err(error) = write {
412                        errors::Error::new("Writing error", "The file could not be edited", 2).raise(format!("File: {}\nText: {}\nError: {}", path, formatted, error).as_str());
413                    }
414                }
415            }
416        }
417    }
418}
419
420
421////////////
422// MACROS //
423////////////
424
425/// Logs the given message with logging level [`Level::DEBUG`].
426///
427/// # Parameters
428///
429/// - `logger`: The logger object to log with
430/// - `message`: The message to log
431///
432/// # Examples
433///
434/// ```rust
435/// # use logging_rs;
436/// # let logger: logging_rs::Logger = logging_rs::Logger::default();
437/// logging_rs::debug!(logger, "A message");
438/// logging_rs::debug!(logger, "A message with more {{details}}", "details" = "stuff");
439/// ```
440///
441/// # See also
442///
443/// - [`info!()`]
444/// - [`warn!()`]
445/// - [`error!()`]
446/// - [`fatal!()`]
447/// - [`log!()`]
448/// - [`Logger`]
449#[macro_export]
450macro_rules! debug {
451    ($logger:expr, $message:expr) => {
452        {
453            $logger.log($message, $crate::Level::DEBUG, std::panic::Location::caller().file(), vec![]);
454        }
455    };
456
457    ($logger:expr, $message:expr, $($argument_name:literal = $argument_value:literal),* $(,)?) => {
458        {
459            let mut arguments: Vec<(&str, String)> = vec![];
460
461            $(
462                arguments.push(($argument_name, $argument_value.to_string()));
463            )*
464
465            $logger.log($message, $crate::Level::DEBUG, std::panic::Location::caller().file(), arguments);
466        }
467    };
468}
469
470/// Logs the given message with logging level [`Level::INFO`].
471///
472/// # Parameters
473///
474/// - `logger`: The logger object to log with
475/// - `message`: The message to log
476///
477/// # Examples
478///
479/// ```rust
480/// # use logging_rs;
481/// # let logger: logging_rs::Logger = logging_rs::Logger::default();
482/// logging_rs::info!(logger, "A message");
483/// logging_rs::info!(logger, "A message with more {{details}}", "details" = "stuff");
484/// ```
485///
486/// # See also
487///
488/// - [`debug!()`]
489/// - [`warn!()`]
490/// - [`error!()`]
491/// - [`fatal!()`]
492/// - [`log!()`]
493/// - [`Logger`]
494#[macro_export]
495macro_rules! info {
496    ($logger:expr, $message:expr) => {
497        {
498            $logger.log($message, $crate::Level::INFO, std::panic::Location::caller().file(), vec![]);
499        }
500    };
501
502    ($logger:expr, $message:expr, $($argument_name:literal = $argument_value:literal),* $(,)?) => {
503        {
504            let mut arguments: Vec<(&str, String)> = vec![];
505
506            $(
507                arguments.push(($argument_name, $argument_value.to_string()));
508            )*
509
510            $logger.log($message, $crate::Level::INFO, std::panic::Location::caller().file(), arguments);
511        }
512    }
513}
514
515/// Logs the given message with logging level [`Level::WARN`].
516///
517/// # Parameters
518///
519/// - `logger`: The logger object to log with
520/// - `message`: The message to log
521///
522/// # Examples
523///
524/// ```rust
525/// # use logging_rs;
526/// # let logger: logging_rs::Logger = logging_rs::Logger::default();
527/// logging_rs::warn!(logger, "A message");
528/// logging_rs::warn!(logger, "A message with more {{details}}", "details" = "stuff");
529/// ```
530///
531/// # See also
532///
533/// - [`debug!()`]
534/// - [`info!()`]
535/// - [`error!()`]
536/// - [`fatal!()`]
537/// - [`log!()`]
538/// - [`Logger`]
539#[macro_export]
540macro_rules! warn {
541    ($logger:expr, $message:expr) => {
542        {
543            $logger.log($message, $crate::Level::WARN, std::panic::Location::caller().file(), vec![]);
544        }
545    };
546
547    ($logger:expr, $message:expr, $($argument_name:literal = $argument_value:literal),* $(,)?) => {
548        {
549            let mut arguments: Vec<(&str, String)> = vec![];
550
551            $(
552                arguments.push(($argument_name, $argument_value.to_string()));
553            )*
554
555            $logger.log($message, $crate::Level::WARN, std::panic::Location::caller().file(), arguments);
556        }
557    }
558}
559
560/// Logs the given message with logging level [`Level::ERROR`].
561///
562/// # Parameters
563///
564/// - `logger`: The logger object to log with
565/// - `message`: The message to log
566///
567/// # Examples
568///
569/// ```rust
570/// # use logging_rs;
571/// # let logger: logging_rs::Logger = logging_rs::Logger::default();
572/// logging_rs::error!(logger, "A message");
573/// logging_rs::error!(logger, "A message with more {{details}}", "details" = "stuff");
574/// ```
575///
576/// # See also
577///
578/// - [`debug!()`]
579/// - [`info!()`]
580/// - [`warn!()`]
581/// - [`fatal!()`]
582/// - [`log!()`]
583/// - [`Logger`]
584#[macro_export]
585macro_rules! error {
586    ($logger:expr, $message:expr) => {
587        {
588            $logger.log($message, $crate::Level::ERROR, std::panic::Location::caller().file(), vec![]);
589        }
590    };
591
592    ($logger:expr, $message:expr, $($argument_name:literal = $argument_value:literal),* $(,)?) => {
593        {
594            let mut arguments: Vec<(&str, String)> = vec![];
595
596            $(
597                arguments.push(($argument_name, $argument_value.to_string()));
598            )*
599
600            $logger.log($message, $crate::Level::ERROR, std::panic::Location::caller().file(), arguments);
601        }
602    }
603}
604
605/// Logs the given message with logging level [`Level::FATAL`].
606///
607/// # Parameters
608///
609/// - `logger`: The logger object to log with
610/// - `message`: The message to log
611///
612/// # Examples
613///
614/// ```rust
615/// # use logging_rs;
616/// # let logger: logging_rs::Logger = logging_rs::Logger::default();
617/// logging_rs::fatal!(logger, "A message");
618/// logging_rs::fatal!(logger, "A message with more {{details}}", "details" = "stuff");
619/// ```
620///
621/// # See also
622///
623/// - [`debug!()`]
624/// - [`info!()`]
625/// - [`warn!()`]
626/// - [`error!()`]
627/// - [`log!()`]
628/// - [`Logger`]
629#[macro_export]
630macro_rules! fatal {
631    ($logger:expr, $message:expr) => {
632        {
633            $logger.log($message, $crate::Level::FATAL, std::panic::Location::caller().file(), vec![]);
634        }
635    };
636
637    ($logger:expr, $message:expr, $($argument_name:literal = $argument_value:literal),* $(,)?) => {
638        {
639            let mut arguments: Vec<(&str, String)> = vec![];
640
641            $(
642                arguments.push(($argument_name, $argument_value.to_string()));
643            )*
644
645            $logger.log($message, $crate::Level::FATAL, std::panic::Location::caller().file(), arguments);
646        }
647    }
648}
649
650/// Logs the given message with logging level [`Level::MESSAGE`].
651///
652/// # Parameters
653///
654/// - `logger`: The logger object to log with
655/// - `message`: The message to log
656///
657/// # Examples
658///
659/// ```rust
660/// # use logging_rs;
661/// # let logger: logging_rs::Logger = logging_rs::Logger::default();
662/// logging_rs::log!(logger, "A message");
663/// logging_rs::log!(logger, "A message with more {{details}}", "details" = "stuff");
664/// ```
665///
666/// # See also
667///
668/// - [`debug!()`]
669/// - [`info!()`]
670/// - [`warn!()`]
671/// - [`error!()`]
672/// - [`fatal!()`]
673/// - [`Logger`]
674#[macro_export]
675macro_rules! log {
676    ($logger:expr, $message:expr) => {
677        {
678            $logger.log($message, $crate::Level::MESSAGE, std::panic::Location::caller().file(), vec![]);
679        }
680    };
681
682    ($logger:expr, $message:expr, $($argument_name:literal = $argument_value:literal),* $(,)?) => {
683        {
684            let mut arguments: Vec<(&str, String)> = vec![];
685
686            $(
687                arguments.push(($argument_name, $argument_value.to_string()));
688            )*
689
690            $logger.log($message, $crate::Level::MESSAGE, std::panic::Location::caller().file(), arguments);
691        }
692    }
693}