sdre_rust_logging/
lib.rs

1// Copyright (C) 2023-2024 Fred Clausen
2
3// This program is free software; you can redistribute it and/or
4// modify it under the terms of the GNU General Public License
5// as published by the Free Software Foundation; either version 2
6// of the License, or (at your option) any later version.
7
8// This program is distributed in the hope that it will be useful,
9// but WITHOUT ANY WARRANTY; without even the implied warranty of
10// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11// GNU General Public License for more details.
12
13// You should have received a copy of the GNU General Public License
14// along with this program; if not, write to the Free Software
15// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
16
17extern crate chrono;
18extern crate env_logger;
19extern crate log;
20
21use anstyle::Color;
22use chrono::Local;
23use env_logger::{fmt::style::AnsiColor, Builder};
24use log::LevelFilter;
25use std::io::Write;
26
27/// Trait to setup logging
28/// To initialize logging, call `enable_logging` on a u8
29pub trait SetupLogging {
30    /// Set logging level. The logging levels match up to the
31    /// log crate's levels. If the user does not specify
32    /// one of these, it defaults to info. <br><br>
33    /// to set the logging level, call `set_logging_level` on a u8<br><br>
34    /// Once set, users will be shown messages at the current level and lower only
35    /// High levels are suppressed. For example, if the level is set to info, users will see
36    /// info, warn, and error messages. They will not see debug or trace messages.
37    fn set_logging_level(self) -> LevelFilter;
38    /// Enable logging<br><br>
39    /// The output is colored and looks like this:<br>
40    /// \[INFO \]\[2021-08-22T15:49:01\]This is an info message<br>
41    /// \[DEBUG\]\[2021-08-22T15:49:01\]This is a debug message<br>
42    /// \[TRACE\]\[2021-08-22T15:49:01\]This is a trace message<br>
43    /// \[ERROR\]\[2021-08-22T15:49:01\]This is an error message<br>
44    /// \[WARN \]\[2021-08-22T15:49:01\]This is a warning message<br>
45    /// \[OTHER\]\[2021-08-22T15:49:01\]This is a message with a different log level<br><br>
46    /// The level field is colored and bold if the terminal supports it.<br>
47    fn enable_logging(&self);
48}
49
50fn set_builder(loglevel: LevelFilter) {
51    let _ = Builder::new()
52        .format(|buf, record| {
53            let time_style = anstyle::Style::new()
54                .fg_color(Some((159, 80, 1).into()))
55                .bold();
56
57            let level_style = match record.level() {
58                log::Level::Info => anstyle::Style::new()
59                    .fg_color(Some(Color::from(AnsiColor::Green)))
60                    .bold(),
61                log::Level::Debug => anstyle::Style::new()
62                    .fg_color(Some(Color::from(AnsiColor::Cyan)))
63                    .bold(),
64                log::Level::Trace => anstyle::Style::new()
65                    .fg_color(Some(Color::from(AnsiColor::Magenta)))
66                    .bold(),
67                log::Level::Error => anstyle::Style::new()
68                    .fg_color(Some(Color::from(AnsiColor::Red)))
69                    .bold(),
70                log::Level::Warn => anstyle::Style::new()
71                    .fg_color(Some(Color::from(AnsiColor::Yellow)))
72                    .bold(),
73            };
74
75            let level = format!("{level_style}{: <5}{level_style:#}", record.level());
76            let time = format!(
77                "{time_style}{}{time_style:#}",
78                Local::now().format("%Y-%m-%dT%H:%M:%S")
79            );
80            writeln!(buf, "[{}][{}]{}", level, time, record.args())
81        })
82        .filter(None, loglevel)
83        .try_init();
84}
85
86impl SetupLogging for &str {
87    fn set_logging_level(self) -> LevelFilter {
88        match self.to_lowercase().as_str() {
89            "error" => LevelFilter::Error,
90            "warn" => LevelFilter::Warn,
91            "info" => LevelFilter::Info,
92            "debug" => LevelFilter::Debug,
93            "trace" => LevelFilter::Trace,
94            _ => LevelFilter::Info,
95        }
96    }
97
98    fn enable_logging(&self) {
99        let loglevel = self.set_logging_level();
100        set_builder(loglevel);
101    }
102}
103
104impl SetupLogging for String {
105    fn set_logging_level(self) -> LevelFilter {
106        match self.to_lowercase().as_str() {
107            "error" => LevelFilter::Error,
108            "warn" => LevelFilter::Warn,
109            "info" => LevelFilter::Info,
110            "debug" => LevelFilter::Debug,
111            "trace" => LevelFilter::Trace,
112            _ => LevelFilter::Info,
113        }
114    }
115
116    fn enable_logging(&self) {
117        // FIXME: this clone seems unnecessary
118        let loglevel = self.clone().set_logging_level();
119        set_builder(loglevel);
120    }
121}
122
123impl SetupLogging for usize {
124    fn set_logging_level(self) -> LevelFilter {
125        match self {
126            1 => LevelFilter::Error,
127            2 => LevelFilter::Warn,
128            3 => LevelFilter::Info,
129            4 => LevelFilter::Debug,
130            5 => LevelFilter::Trace,
131            _ => LevelFilter::Info,
132        }
133    }
134
135    fn enable_logging(&self) {
136        let loglevel = self.set_logging_level();
137        set_builder(loglevel);
138    }
139}
140
141impl SetupLogging for u8 {
142    fn set_logging_level(self) -> LevelFilter {
143        match self {
144            1 => LevelFilter::Error,
145            2 => LevelFilter::Warn,
146            3 => LevelFilter::Info,
147            4 => LevelFilter::Debug,
148            5 => LevelFilter::Trace,
149            _ => LevelFilter::Info,
150        }
151    }
152
153    fn enable_logging(&self) {
154        let loglevel = self.set_logging_level();
155        set_builder(loglevel);
156    }
157}
158
159#[cfg(test)]
160mod test {
161    use super::*;
162
163    #[test]
164    fn test_set_logging_level() {
165        let info_level: u8 = 0;
166        let debug_level: u8 = 1;
167        let trace_level: u8 = 2;
168        let stupid_levels: u8 = 255;
169        let info_level_logging: LevelFilter = info_level.set_logging_level();
170        let debug_level_logging: LevelFilter = debug_level.set_logging_level();
171        let trace_level_logging: LevelFilter = trace_level.set_logging_level();
172        let stupid_levels_logging: LevelFilter = stupid_levels.set_logging_level();
173        assert_eq!(info_level_logging, LevelFilter::Info);
174        assert_eq!(debug_level_logging, LevelFilter::Debug);
175        assert_eq!(trace_level_logging, LevelFilter::Trace);
176        assert_eq!(stupid_levels_logging, LevelFilter::Trace);
177    }
178}