1use ansi_term::{Color, Style};
2use anyhow::Result;
3use flexi_logger::writers::LogWriter;
4use flexi_logger::{Age, Duplicate, FlexiLoggerError, LogSpecification};
5use flexi_logger::{Cleanup, Criterion, FileSpec, Naming};
6use flexi_logger::{
7 DeferredNow, FormatFunction, LevelFilter, LogSpecBuilder, Logger, LoggerHandle, Record,
8 WriteMode,
9};
10use std::path::Path;
11use std::path::PathBuf;
12use std::str::FromStr;
13use std::thread;
14const TS_DASHES_BLANK_COLONS_DOT_BLANK: &str = "%m-%d %H:%M:%S%.3f";
16
17const HOUR_MINUTER_SECONDE: &str = "%H:%M:%S%.3f";
18
19#[allow(dead_code)]
20fn with_thread(
21 w: &mut dyn std::io::Write,
22 now: &mut DeferredNow,
23 record: &Record,
24) -> Result<(), std::io::Error> {
25 let level = record.level();
26 write!(
27 w,
28 "[{}][{}][{:5}][{}:{}] {}",
29 now.format(TS_DASHES_BLANK_COLONS_DOT_BLANK),
30 thread::current().name().unwrap_or("<unnamed>"),
31 level.to_string(),
32 record.target(),
33 record.line().unwrap_or(0),
34 &record.args()
35 )
36}
37#[allow(dead_code)]
38pub fn colored_with_thread(
39 w: &mut dyn std::io::Write,
40 now: &mut DeferredNow,
41 record: &Record,
42) -> Result<(), std::io::Error> {
43 let level = record.level();
44 write!(
45 w,
46 "{}",
47 format_args!(
48 "[{}][{}][{:5}][{}:{}] {}",
49 now.format(TS_DASHES_BLANK_COLONS_DOT_BLANK),
50 thread::current().name().unwrap_or("<unnamed>"),
51 style(level).paint(format_args!("{:6}", level.to_string()).to_string()),
52 record.target(),
53 record.line().unwrap_or(0),
54 &record.args()
55 )
56 )
57}
58
59#[allow(dead_code)]
60pub fn colored_with_thread_target(
61 w: &mut dyn std::io::Write,
62 now: &mut DeferredNow,
63 record: &Record,
64) -> Result<(), std::io::Error> {
65 let level = record.level();
66 write!(
67 w,
68 "{}",
69 format_args!(
70 "[{}][{}][{:5}][{}:{}] {}",
71 now.format(TS_DASHES_BLANK_COLONS_DOT_BLANK),
72 thread::current().name().unwrap_or("<unnamed>"),
73 style(level).paint(format_args!("{:6}", level.to_string()).to_string()),
74 record.target(),
75 record.line().unwrap_or(0),
76 &record.args()
77 )
78 )
79}
80
81#[allow(dead_code)]
82pub fn simple_colored_with_thread_target(
83 w: &mut dyn std::io::Write,
84 now: &mut DeferredNow,
85 record: &Record,
86) -> Result<(), std::io::Error> {
87 let level = record.level();
88 write!(
89 w,
90 "{}",
91 format_args!(
92 "[{}][{}][{:5}][{}:{}] {}",
93 now.format(HOUR_MINUTER_SECONDE),
94 thread::current().name().unwrap_or("<unnamed>"),
95 style(level).paint(format_args!("{:6}", level.to_string()).to_string()),
96 record.target(),
97 record.line().unwrap_or(0),
98 &record.args()
99 )
100 )
101}
102
103pub struct LoggerBuilder {
104 display_target: bool,
105 log_spec_builder: LogSpecBuilder,
106}
107impl LoggerBuilder {
108 pub fn default(level: LevelFilter) -> Self {
109 let mut log_spec_builder = LogSpecBuilder::new();
110 log_spec_builder.default(level);
111 Self {
112 log_spec_builder,
113 display_target: false,
114 }
115 }
116 pub fn module<M: AsRef<str>>(mut self, module_name: M, lf: LevelFilter) -> Self {
117 self.log_spec_builder.module(module_name, lf);
118 self
119 }
120 pub fn build_default(self) -> LoggerBuilder2 {
121 LoggerBuilder2 {
122 logger: Logger::with(self.log_spec_builder.build())
123 .format(if self.display_target {
124 colored_with_thread_target
125 } else {
126 colored_with_thread
127 })
128 .write_mode(WriteMode::Direct),
129 }
130 }
131
132 pub fn log_to_stdout(self) {
133 Logger::with(self.log_spec_builder.build())
134 .format(if self.display_target {
135 colored_with_thread_target
136 } else {
137 colored_with_thread
138 })
139 .write_mode(WriteMode::Direct)
140 .log_to_stdout()
141 .start()
142 .unwrap();
143 }
144
145 pub fn build_with(self, format: FormatFunction, write_mode: WriteMode) -> LoggerBuilder2 {
146 LoggerBuilder2 {
147 logger: Logger::with(self.log_spec_builder.build())
148 .format(format)
149 .write_mode(write_mode),
150 }
151 }
152}
153pub struct LoggerBuilder2 {
154 logger: Logger,
155}
156pub struct LoggerBuilder3 {
157 logger: Logger,
158}
159impl LoggerBuilder3 {
160 #[must_use]
161 pub fn start(self) -> LoggerHandle {
162 self.logger.start().unwrap()
163 }
164 pub fn _start(self) -> Result<LoggerHandle> {
165 Ok(self.logger.start()?)
166 }
167
168 #[must_use]
169 pub fn start_with_specfile(self, p: impl AsRef<Path>) -> LoggerHandle {
170 self.logger.start_with_specfile(p).unwrap()
171 }
172 #[must_use]
173 pub fn start_with_specfile_default(self, app: &str) -> LoggerHandle {
174 let path = PathBuf::from_str("/var/local/etc/")
175 .unwrap()
176 .join(app)
177 .join("logspecification.toml");
178 self.logger.start_with_specfile(path).unwrap()
179 }
180}
181impl LoggerBuilder2 {
182 pub fn log_to_stdout(self) -> LoggerBuilder3 {
183 LoggerBuilder3 {
184 logger: self.logger.log_to_stdout(),
185 }
186 }
187 pub fn log_to_file_default(self, app: &str) -> LoggerBuilder3 {
188 let fs_path = PathBuf::from_str("/var/local/log").unwrap().join(app);
189 let fs = FileSpec::default()
190 .directory(fs_path)
191 .basename(app)
192 .suffix("log");
193 self.log_to_file(
195 fs,
196 Criterion::AgeOrSize(Age::Day, 10_000_000),
197 Naming::Numbers,
198 Cleanup::KeepLogFiles(10),
199 true,
200 )
201 }
202 pub fn log_to_writer(self, w: Box<dyn LogWriter>) -> LoggerBuilder3 {
203 LoggerBuilder3 {
204 logger: self.logger.log_to_writer(w),
205 }
206 }
207 pub fn log_to_file(
208 self,
209 fs: FileSpec,
210 criterion: Criterion,
211 naming: Naming,
212 cleanup: Cleanup,
213 append: bool,
214 ) -> LoggerBuilder3 {
215 LoggerBuilder3 {
216 logger: self
217 .logger
218 .log_to_file(fs)
219 .o_append(append)
220 .rotate(criterion, naming, cleanup),
221 }
222 }
223}
224#[allow(dead_code)]
225pub struct LoggerFeatureBuilder {
226 _app: String,
227 _debug_level: DebugLevel,
228 _prod_level: LevelFilter,
229 fs: FileSpec,
230 criterion: Criterion,
231 naming: Naming,
232 cleanup: Cleanup,
233 append: bool,
234 modules: Vec<(String, LevelFilter)>,
235 writer: Option<Box<dyn LogWriter>>,
236 log_etc_path: PathBuf,
237}
238impl LoggerFeatureBuilder {
239 pub fn default(
240 app: &str,
241 _debug_level: DebugLevel,
242 prod_level: LevelFilter,
243 log_etc_path: PathBuf,
244 log_path: PathBuf,
245 ) -> Self {
246 let fs = FileSpec::default()
248 .directory(log_path)
249 .basename(app)
250 .suffix("log");
251 let criterion = Criterion::AgeOrSize(Age::Day, 10_000_000);
253 let naming = Naming::Numbers;
254 let cleanup = Cleanup::KeepLogFiles(10);
255 let append = true;
256 Self {
257 _app: app.to_string(),
258 _debug_level,
259 _prod_level: prod_level,
260 fs,
261 criterion,
262 naming,
263 cleanup,
264 append,
265 modules: Vec::new(),
266 writer: None,
267 log_etc_path,
268 }
269 }
270 pub fn module<M: AsRef<str>>(mut self, module_name: M, lf: LevelFilter) -> Self {
271 self.modules.push((module_name.as_ref().to_owned(), lf));
272 self
273 }
274 pub fn log_to_write(mut self, w: Box<dyn LogWriter>) -> Self {
275 self.writer = Some(w);
276 self
277 }
278 pub fn config(
279 mut self,
280 fs: FileSpec,
281 criterion: Criterion,
282 naming: Naming,
283 cleanup: Cleanup,
284 append: bool,
285 ) -> Self {
286 self.fs = fs;
287 self.criterion = criterion;
288 self.naming = naming;
289 self.cleanup = cleanup;
290 self.append = append;
291 self
292 }
293 #[cfg(feature = "prod")]
294 #[must_use]
295 pub fn build(self) -> LoggerHandle {
296 let mut log_spec_builder = LogSpecBuilder::new();
297 log_spec_builder.default(self._prod_level);
298 for (module, level) in self.modules {
299 log_spec_builder.module(module, level);
300 }
301 let path = self.log_etc_path.join(format!("{}.toml", self._app));
302 if let Some(w) = self.writer {
303 Logger::with(log_spec_builder.build())
304 .format(with_thread)
305 .write_mode(WriteMode::Direct)
306 .log_to_file_and_writer(self.fs, w)
307 .o_append(self.append)
308 .rotate(self.criterion, self.naming, self.cleanup)
309 .start_with_specfile(path)
310 .unwrap()
311 } else {
312 Logger::with(log_spec_builder.build())
313 .format(with_thread)
314 .write_mode(WriteMode::Direct)
315 .log_to_file(self.fs)
316 .o_append(self.append)
317 .rotate(self.criterion, self.naming, self.cleanup)
318 .start_with_specfile(path)
319 .unwrap()
320 }
321 }
322 #[cfg(not(feature = "prod"))]
323 #[must_use]
324 pub fn build(self) -> LoggerHandle {
325 let specification = match self._debug_level {
326 DebugLevel::Filter(debug_level) => {
327 let mut log_spec_builder = LogSpecBuilder::new();
328 log_spec_builder.default(debug_level);
329 for (module, level) in self.modules {
330 log_spec_builder.module(module, level);
331 }
332 log_spec_builder.build()
333 }
334 DebugLevel::Env(default) => {
335 if let Some(env_val) =
336 std::env::vars().find_map(|x| if x.0 == self._app { Some(x.1) } else { None })
337 {
338 match LogSpecification::env_or_parse(env_val) {
339 Ok(rs) => rs,
340 Err(_) => LogSpecification::env_or_parse(default).unwrap(),
341 }
342 } else {
343 LogSpecification::env_or_parse(default).unwrap()
344 }
345 }
346 };
347 if let Some(w) = self.writer {
348 LoggerBuilder2 {
349 logger: Logger::with(specification)
350 .format(colored_with_thread)
351 .write_mode(WriteMode::Direct)
352 .duplicate_to_stdout(Duplicate::All),
353 }
354 .log_to_writer(w)
355 .start()
356 } else {
357 LoggerBuilder2 {
358 logger: Logger::with(specification)
359 .format(colored_with_thread)
360 .write_mode(WriteMode::Direct),
361 }
362 .log_to_stdout()
363 .start()
364 }
365 }
366}
367
368lazy_static::lazy_static! {
369 static ref MY_PALETTE: std::sync::RwLock<Palette> = std::sync::RwLock::new(Palette::default());
370}
371pub fn style(level: log::Level) -> Style {
372 let palette = &*(MY_PALETTE.read().unwrap());
373 match level {
374 log::Level::Error => palette.error,
375 log::Level::Warn => palette.warn,
376 log::Level::Info => palette.info,
377 log::Level::Debug => palette.debug,
378 log::Level::Trace => palette.trace,
379 }
380}
381
382#[derive(Debug)]
383struct Palette {
384 pub error: Style,
385 pub warn: Style,
386 pub info: Style,
387 pub debug: Style,
388 pub trace: Style,
389}
390impl Palette {
391 fn default() -> Palette {
392 Palette {
393 error: Style::default().fg(Color::Red).bold(),
394 warn: Style::default().fg(Color::Yellow).bold(),
395 info: Style::default(),
396 debug: Style::default().fg(Color::Fixed(28)),
397 trace: Style::default().fg(Color::Fixed(8)),
398 }
399 }
400}
401
402pub enum DebugLevel {
403 Filter(LevelFilter),
404 Env(String),
405}
406
407impl From<LevelFilter> for DebugLevel {
408 fn from(value: LevelFilter) -> Self {
409 Self::Filter(value)
410 }
411}
412impl From<&str> for DebugLevel {
413 fn from(value: &str) -> Self {
414 Self::Env(value.to_string())
415 }
416}