hyperion_framework/logging/
logging_service.rs1use chrono::Utc;
25use colored::*;
26use log::{Level, LevelFilter, Metadata, Record, SetLoggerError};
27
28pub struct LoggingService {
30 pub min_log_level: LevelFilter,
31}
32
33impl log::Log for LoggingService {
34 fn enabled(&self, metadata: &Metadata) -> bool {
35 metadata.level().to_level_filter() <= self.min_log_level
36 }
37
38 fn log(&self, record: &Record) {
39 if self.enabled(record.metadata()) {
40 let level = match record.level() {
41 Level::Error => format!("{:<5}", "ERROR").red(),
42 Level::Warn => format!("{:<5}", "WARN").yellow(),
43 Level::Info => format!("{:<5}", "INFO").green(),
44 Level::Debug => format!("{:<5}", "DEBUG").bright_cyan(),
45 Level::Trace => format!("{:<5}", "TRACE").blue(),
46 };
47
48 let timestamp = Utc::now().format("%Y-%m-%d %H:%M:%S");
49
50 let module = record
51 .module_path()
52 .and_then(|m| m.split("::").last())
53 .unwrap_or("unknown");
54
55 println!("[{timestamp}] {level} {module:<20} - {}", record.args());
56 }
57 }
58
59 fn flush(&self) {}
60}
61
62pub fn initialise_logger(log_level: LevelFilter) -> Result<(), SetLoggerError> {
64 let logger = LoggingService {
65 min_log_level: log_level,
66 };
67
68 log::set_max_level(logger.min_log_level);
70 log::set_boxed_logger(Box::new(logger))?;
71 Ok(())
72}
73
74#[cfg(test)]
75mod tests {
76 use super::*;
77 use log::{Level, Log};
78
79 fn make_service(level: LevelFilter) -> LoggingService {
80 LoggingService {
81 min_log_level: level,
82 }
83 }
84
85 #[test]
86 fn enabled_passes_at_or_above_min_level() {
87 let svc = make_service(LevelFilter::Info);
88 assert!(
89 svc.enabled(
90 &log::Metadata::builder()
91 .level(Level::Error)
92 .target("t")
93 .build()
94 )
95 );
96 assert!(
97 svc.enabled(
98 &log::Metadata::builder()
99 .level(Level::Warn)
100 .target("t")
101 .build()
102 )
103 );
104 assert!(
105 svc.enabled(
106 &log::Metadata::builder()
107 .level(Level::Info)
108 .target("t")
109 .build()
110 )
111 );
112 }
113
114 #[test]
115 fn enabled_filters_below_min_level() {
116 let svc = make_service(LevelFilter::Info);
117 assert!(
118 !svc.enabled(
119 &log::Metadata::builder()
120 .level(Level::Debug)
121 .target("t")
122 .build()
123 )
124 );
125 assert!(
126 !svc.enabled(
127 &log::Metadata::builder()
128 .level(Level::Trace)
129 .target("t")
130 .build()
131 )
132 );
133 }
134
135 #[test]
136 fn log_does_not_panic_for_all_levels() {
137 let svc = make_service(LevelFilter::Trace);
138 svc.log(
140 &log::Record::builder()
141 .level(Level::Error)
142 .target("t")
143 .module_path(Some("test::module"))
144 .args(format_args!("error message"))
145 .build(),
146 );
147 svc.log(
148 &log::Record::builder()
149 .level(Level::Warn)
150 .target("t")
151 .module_path(Some("test::module"))
152 .args(format_args!("warn message"))
153 .build(),
154 );
155 svc.log(
156 &log::Record::builder()
157 .level(Level::Info)
158 .target("t")
159 .module_path(Some("test::module"))
160 .args(format_args!("info message"))
161 .build(),
162 );
163 svc.log(
164 &log::Record::builder()
165 .level(Level::Debug)
166 .target("t")
167 .module_path(Some("test::module"))
168 .args(format_args!("debug message"))
169 .build(),
170 );
171 svc.log(
172 &log::Record::builder()
173 .level(Level::Trace)
174 .target("t")
175 .module_path(Some("test::module"))
176 .args(format_args!("trace message"))
177 .build(),
178 );
179 }
180
181 #[test]
182 fn log_silently_skips_filtered_levels() {
183 let svc = make_service(LevelFilter::Error);
184 svc.log(
185 &log::Record::builder()
186 .level(Level::Trace)
187 .target("t")
188 .args(format_args!("should be skipped"))
189 .build(),
190 ); }
192
193 #[test]
194 fn log_handles_missing_module_path() {
195 let svc = make_service(LevelFilter::Trace);
196 svc.log(
197 &log::Record::builder()
198 .level(Level::Info)
199 .target("t")
200 .module_path(None)
201 .args(format_args!("no module path"))
202 .build(),
203 ); }
205}