flogging/logger/
mod.rs

1//
2// File Name:    mod.rs
3// Project Name: flogging
4//
5// Copyright (C) 2025 Bradley Willcott
6//
7// SPDX-License-Identifier: GPL-3.0-or-later
8//
9// This library (crate) is free software: you can redistribute it and/or modify
10// it under the terms of the GNU General Public License as published by
11// the Free Software Foundation, either version 3 of the License, or
12// (at your option) any later version.
13//
14// This library (crate) is distributed in the hope that it will be useful,
15// but WITHOUT ANY WARRANTY; without even the implied warranty of
16// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17// GNU General Public License for more details.
18//
19// You should have received a copy of the GNU General Public License
20// along with this library (crate).  If not, see <https://www.gnu.org/licenses/>.
21//
22
23//!
24//! # Logger
25//!
26//! `Logger` is the work-horse of the crate.
27//!
28//! It contains the primary functions to both create a logger instance, and has
29//! the methods to add log messages at various log levels.
30//!
31
32#![allow(clippy::needless_doctest_main)]
33
34mod builder;
35mod level;
36mod log_entry;
37
38use anyhow::{Context, Error, Result};
39use std::cell::{LazyCell, RefCell};
40use std::collections::hash_map::IterMut;
41use std::collections::{HashMap, HashSet};
42use std::f32::consts;
43use std::fmt;
44use std::fs::{File, exists};
45use std::io::Write;
46use std::marker::PhantomData;
47use std::module_path;
48use std::ops::DerefMut;
49use std::path::Path;
50use std::sync::mpsc::Sender;
51use std::sync::{Arc, MutexGuard, PoisonError, mpsc};
52use std::thread;
53
54use crate::handlers::{
55    handler::{self, Handler, HandlerTrait},
56    mock_handler::MockHandler,
57};
58pub use crate::logger::builder::*;
59pub use crate::logger::level::Level;
60pub use crate::logger::log_entry::LogEntry;
61
62///
63/// This is the work-horse, providing the primary methods of the crate.
64///
65pub struct Logger {
66    ///
67    /// Identify the source of log messages passed to this logger.
68    ///
69    /// This would ideally be the **mod** path.
70    ///
71    mod_path: String,
72
73    ///
74    /// The name of the function/method inside which the log message
75    /// is generated.
76    ///
77    fn_name: String,
78
79    ///
80    /// Default level used by `log(msg)`.
81    ///
82    level: Level,
83
84    ///
85    /// Holds the handlers associated with this logger.
86    ///
87    handlers: RefCell<HashMap<Handler, Box<dyn HandlerTrait>>>,
88}
89
90impl Logger {
91    ///
92    /// Create a new Logger instance.
93    ///
94    /// Logging level is set to it's default setting (INFO).
95    ///
96    /// No `handlers` are set. Use the various methods of
97    /// [`LoggerBuilder`] to configure the new `Logger`.
98    ///
99    /// ## Parameters
100    /// - `mod_path` - The module path. Can be set with: [`module_path!()`]
101    ///
102    /// Returns a `LoggerBuilder` for further configuring.
103    ///
104    /// ## Examples
105    /// ```
106    /// extern crate flogging;
107    /// use flogging::*;
108    ///
109    /// let mut log = Logger::builder(module_path!())
110    ///     .add_console_handler()
111    ///     .build();
112    /// ```
113    ///
114    pub fn builder(mod_path: &str) -> LoggerBuilder {
115        LoggerBuilder::create(mod_path.to_string())
116    }
117
118    ///
119    /// Log a CONFIG message.
120    ///
121    /// If the logger is currently enabled for the CONFIG message level
122    /// then the given message is forwarded to all the registered output
123    /// Handler objects.
124    ///
125    /// ## Parameters
126    /// - `msg` - The string message.
127    ///
128    /// ## Examples
129    /// ```
130    /// extern crate flogging;
131    /// use flogging::*;
132    ///
133    /// let mut log = Logger::console_logger(module_path!());
134    /// log.set_level(Level::CONFIG);
135    /// log.config("Some text to store.");
136    /// ```
137    ///
138    pub fn config(&mut self, msg: &str) {
139        self.log(Level::CONFIG, &self.fn_name(), msg);
140    }
141
142    ///
143    /// Create new Logger instance, with a `ConsoleHandler`.
144    ///
145    /// Logging level is set to it's default setting (INFO).
146    ///
147    /// ## Parameters
148    /// - `mod_path`- The module path. Suggest using [`module_path!()`].
149    ///
150    /// Returns a configured `Logger`.
151    ///
152    /// ## Examples
153    /// ```
154    /// extern crate flogging;
155    /// use flogging::*;
156    ///
157    /// let mut log = Logger::console_logger(module_path!());
158    /// log.set_fn_name("main");
159    ///
160    /// log.warning("Don't over do it.");
161    /// ```
162    /// Output:
163    /// ```text
164    /// |flogging->main| [WARNING] Don't over do it.
165    /// ```
166    ///
167    pub fn console_logger(mod_path: &str) -> Logger {
168        Logger::builder(mod_path).add_console_handler().build()
169    }
170
171    ///
172    /// Create new Logger instance, with a custom handler.
173    ///
174    /// ## Parameters
175    /// - `mod_path`- The module path. Suggest using [`module_path!()`].
176    /// - `label` - Unique label for this custom handler.
177    /// - `custom` - The boxed custom handler.
178    ///
179    /// Returns a configured `Logger`.
180    ///
181    /// ## Examples
182    /// ```
183    /// extern crate flogging;
184    /// use flogging::*;
185    ///
186    /// let mut log = Logger::custom_logger(
187    ///     module_path!(),
188    ///     "MockHandler",
189    ///     Box::new(MockHandler::create("Some text").unwrap()),
190    /// );
191    /// log.set_fn_name("main");
192    ///
193    /// log.warning("Don't over do it.");
194    /// ```
195    /// [`MockHandler`] doesn't publish anything, as [`publish()`][MockHandler::publish()] is a **NoOp** method.
196    /// It is used here to make the example work.
197    ///
198    /// However, it is expected that _your_ custom handler will do a little more.
199    ///
200    pub fn custom_logger(mod_path: &str, label: &str, custom: Box<dyn HandlerTrait>) -> Logger {
201        Logger::builder(mod_path)
202            .add_custom_handler(label, custom)
203            .build()
204    }
205
206    ///
207    /// Log a method entry.
208    ///
209    /// This is a convenience method that can be used to log entry to a method.
210    /// A `LogEntry` with message "Entry" and log level FINER, is logged.
211    ///
212    /// ## Examples
213    /// ```
214    /// mod my_mod {
215    ///     extern crate flogging;
216    ///     use flogging::*;
217    ///     use std::cell::{LazyCell, RefCell};
218    ///
219    ///     // Setting up the module level logger.
220    ///     const LOGGER: LazyCell<RefCell<Logger>> = LazyCell::new(|| {
221    ///         RefCell::new({
222    ///             Logger::builder(module_path!())
223    ///                 .add_console_handler()
224    ///                 .set_level(Level::FINEST)
225    ///                 .build()
226    ///         })
227    ///     });
228    ///
229    ///     pub fn my_func(data: &str) {
230    ///         let binding = LOGGER;
231    ///         let mut log = binding.borrow_mut();
232    ///         log.set_fn_name("my_func");
233    ///
234    ///         log.entering();
235    ///     }
236    /// }
237    ///
238    /// fn main() {
239    ///     let data = "Some data";
240    ///     my_mod::my_func(data);
241    /// }
242    /// ```
243    /// Output:
244    /// ```text
245    /// |flogging::my_mod->my_func| [FINER  ] Entry
246    /// ```
247    ///
248    pub fn entering(&mut self) {
249        self.log(Level::FINER, &self.fn_name(), "Entry");
250    }
251
252    ///
253    /// Log a method entry.
254    ///
255    /// This is a convenience method that can be used to log entry to a method.
256    /// A `LogEntry` with message "Entry" and log level FINER, is logged.
257    ///
258    /// ## Parameters
259    /// - `msg` - The string message.
260    ///
261    /// ## Examples
262    /// ```
263    /// mod my_mod {
264    ///     extern crate flogging;
265    ///     use flogging::*;
266    ///     use std::cell::{LazyCell, RefCell};
267    ///
268    ///     // Setting up the module level logger.
269    ///     const LOGGER: LazyCell<RefCell<Logger>> = LazyCell::new(|| {
270    ///         RefCell::new({
271    ///             Logger::builder(module_path!())
272    ///                 .add_console_handler()
273    ///                 .set_level(Level::FINEST)
274    ///                 .build()
275    ///         })
276    ///     });
277    ///
278    ///     pub fn my_func(data: &str) {
279    ///         let binding = LOGGER;
280    ///         let mut log = binding.borrow_mut();
281    ///         log.set_fn_name("my_func");
282    ///
283    ///         log.entering_with(&format!("data: \"{data}\""));
284    ///     }
285    /// }
286    ///
287    /// fn main() {
288    ///     let data = "Some data";
289    ///     my_mod::my_func(data);
290    /// }
291    /// ```
292    /// Output:
293    /// ```text
294    /// |flogging::my_mod->my_func| [FINER  ] Entry: (data: "Some data")
295    /// ```
296    ///
297    pub fn entering_with(&mut self, msg: &str) {
298        self.log(
299            Level::FINER,
300            &self.fn_name(),
301            &("Entry: (".to_string() + msg + ")"),
302        );
303    }
304
305    ///
306    /// Log a method return.
307    ///
308    /// This is a convenience method that can be used to log returning from a method.
309    /// A `LogEntry` with message "Return" and log level FINER, is logged.
310    ///
311    /// ## Examples
312    /// ```
313    /// mod my_mod {
314    ///     extern crate flogging;
315    ///     use flogging::*;
316    ///     use std::cell::{LazyCell, RefCell};
317    ///
318    ///     // Setting up the module level logger.
319    ///     const LOGGER: LazyCell<RefCell<Logger>> = LazyCell::new(|| {
320    ///         RefCell::new({
321    ///             Logger::builder(module_path!())
322    ///                 .add_console_handler()
323    ///                 .set_level(Level::FINEST)
324    ///                 .build()
325    ///         })
326    ///     });
327    ///
328    ///     pub fn my_func(data: &str) {
329    ///         let binding = LOGGER;
330    ///         let mut log = binding.borrow_mut();
331    ///         log.set_fn_name("my_func");
332    ///
333    ///         log.exiting();
334    ///     }
335    /// }
336    ///
337    /// fn main() {
338    ///     let data = "Some data";
339    ///     my_mod::my_func(data);
340    /// }
341    /// ```
342    /// Output:
343    /// ```text
344    /// |flogging::my_mod->my_func| [FINER  ] Return
345    /// ```
346    ///
347    pub fn exiting(&mut self) {
348        self.log(Level::FINER, &self.fn_name(), "Return");
349    }
350
351    ///
352    /// Log a method return.
353    ///
354    /// This is a convenience method that can be used to log returning from a method.
355    /// A `LogEntry` with message "Return" and log level FINER, is logged.
356    ///
357    /// ## Parameters
358    /// - `msg` - The string message.
359    ///
360    /// ## Examples
361    /// ```
362    /// mod my_mod {
363    ///     extern crate flogging;
364    ///     use flogging::*;
365    ///     use std::cell::{LazyCell, RefCell};
366    ///
367    ///     // Setting up the module level logger.
368    ///     const LOGGER: LazyCell<RefCell<Logger>> = LazyCell::new(|| {
369    ///         RefCell::new({
370    ///             Logger::builder(module_path!())
371    ///                 .add_console_handler()
372    ///                 .set_level(Level::FINEST)
373    ///                 .build()
374    ///         })
375    ///     });
376    ///
377    ///     pub fn my_func(data: &str) -> bool {
378    ///         let binding = LOGGER;
379    ///         let mut log = binding.borrow_mut();
380    ///         log.set_fn_name("my_func");
381    ///
382    ///         let rtn = true;
383    ///         log.exiting_with(&format!("rtn: {rtn}"));
384    ///         rtn
385    ///     }
386    /// }
387    ///
388    /// fn main() {
389    ///     let data = "Some data";
390    ///     my_mod::my_func(data);
391    /// }
392    /// ```
393    /// Output:
394    /// ```text
395    /// |flogging::my_mod->my_func| [FINER  ] Return: (rtn: true)
396    /// ```
397    ///
398    pub fn exiting_with(&mut self, msg: &str) {
399        self.log(
400            Level::FINER,
401            &self.fn_name(),
402            &("Return: (".to_string() + msg + ")"),
403        );
404    }
405
406    ///
407    /// Create new Logger instance, with a `FileHandler`.
408    ///
409    /// Logging level is set to it's default setting (INFO).
410    ///
411    /// ## Parameters
412    /// - `mod_path`- The module path. Suggest using [`std::module_path`][mp].
413    /// - `filename` - The name of the log file to use. Will be created
414    ///   if it doesn't exist.
415    ///
416    /// Returns a configured `Logger`.
417    ///
418    /// ## Examples
419    /// ```
420    /// extern crate flogging;
421    /// use flogging::Logger;
422    ///
423    /// let mut log = Logger::file_logger(module_path!(), "test.log");
424    /// log.set_fn_name("main");
425    ///
426    /// log.info("Some text to store.");
427    /// ```
428    /// Output:
429    /// ```text
430    /// 2025-07-18T12:14:47.322720683+08:00 |flogging->main| [INFO   ] Some text to store.
431    /// ```
432    ///
433    /// [mp]: https://doc.rust-lang.org/std/macro.module_path.html
434    pub fn file_logger(mod_path: &str, filename: &str) -> Logger {
435        Logger::builder(mod_path).add_file_handler(filename).build()
436    }
437
438    ///
439    /// Log a FINE message.
440    ///
441    /// If the logger is currently enabled for the FINE message level
442    /// then the given message is forwarded to all the registered output
443    /// Handler objects.
444    ///
445    /// ## Parameters
446    /// - `msg` - The string message.
447    ///
448    /// ## Examples
449    /// ```
450    /// extern crate flogging;
451    /// use flogging::*;
452    ///
453    /// let mut log = Logger::console_logger(module_path!());
454    /// log.set_level(Level::FINEST);
455    /// log.set_fn_name("main");
456    ///
457    /// log.fine("Some text to store.");
458    /// ```
459    /// Output:
460    /// ```text
461    /// |flogging->main| [FINE   ] Some text to store.
462    /// ```
463    ///
464    pub fn fine(&mut self, msg: &str) {
465        self.log(Level::FINE, &self.fn_name(), msg);
466    }
467
468    ///
469    /// Log a FINER message.
470    ///
471    /// If the logger is currently enabled for the FINER message level
472    /// then the given message is forwarded to all the registered output
473    /// Handler objects.
474    ///
475    /// ## Parameters
476    /// - `msg` - The string message.
477    ///
478    /// ## Examples
479    /// ```
480    /// extern crate flogging;
481    /// use flogging::*;
482    ///
483    /// let mut log = Logger::console_logger(module_path!());
484    /// log.set_level(Level::FINEST);
485    /// log.set_fn_name("main");
486    ///
487    /// log.finer("Some text to store.");
488    /// ```
489    /// Output:
490    /// ```text
491    /// |flogging->main| [FINER  ] Some text to store.
492    /// ```
493    ///
494    pub fn finer(&mut self, msg: &str) {
495        self.log(Level::FINER, &self.fn_name(), msg);
496    }
497
498    ///
499    /// Log a FINEST message.
500    ///
501    /// If the logger is currently enabled for the FINEST message level
502    /// then the given message is forwarded to all the registered output
503    /// Handler objects.
504    ///
505    /// ## Parameters
506    /// - `msg` - The string message.
507    ///
508    /// ## Examples
509    /// ```
510    /// extern crate flogging;
511    /// use flogging::*;
512    ///
513    /// let mut log = Logger::console_logger(module_path!());
514    /// log.set_level(Level::FINEST);
515    /// log.set_fn_name("main");
516    ///
517    /// log.finest("Some text to store.");
518    /// ```
519    /// Output:
520    /// ```text
521    /// |flogging->main| [FINEST ] Some text to store.
522    /// ```
523    ///
524    pub fn finest(&mut self, msg: &str) {
525        self.log(Level::FINEST, &self.fn_name(), msg);
526    }
527
528    ///
529    /// Get the current function/method name.
530    ///
531    pub fn fn_name(&self) -> String {
532        self.fn_name.clone()
533    }
534
535    ///
536    /// Get required `Handler`.
537    ///
538    /// ## Parameters
539    /// - `handler` - The enum of the required handler.
540    ///
541    /// Returns Some boxed handler, or None.
542    ///
543    /// ## Examples
544    /// ```
545    /// extern crate flogging;
546    /// use flogging::*;
547    ///
548    /// let mut log = Logger::string_logger(module_path!());
549    /// log.set_fn_name("get_handler");
550    ///
551    /// log.info("Some text to store.");
552    ///
553    /// let h = log.get_handler(Handler::String).unwrap();
554    /// println!("{h}");
555    /// ```
556    pub fn get_handler(&mut self, handler: Handler) -> Option<Box<&mut dyn HandlerTrait>> {
557        match self.handlers.get_mut().get_mut(&handler) {
558            Some(val) => Some(Box::new(&mut **val)),
559            None => None,
560        }
561    }
562
563    ///
564    /// Check if the required `Handler` has been added to this `Logger`.
565    ///
566    /// ## Parameters
567    /// - `handler` - The enum of the required handler.
568    ///
569    /// Returns `true` if it exists, `false` otherwise.
570    ///
571    /// ## Examples
572    /// ```
573    /// extern crate flogging;
574    /// use flogging::*;
575    ///
576    /// let mut log = Logger::string_logger(module_path!());
577    /// log.info("Some text to store.");
578    ///
579    /// println!("This logger has a 'StringHandler': {}", log.has_handler(Handler::String));
580    /// ```
581    pub fn has_handler(&self, handler: Handler) -> bool {
582        self.handlers.borrow().contains_key(&handler)
583    }
584    ///
585    /// Log a INFO message.
586    ///
587    /// If the logger is currently enabled for the INFO message level
588    /// then the given message is forwarded to all the registered output
589    /// Handler objects.
590    ///
591    /// ## Parameters
592    /// - `msg` - The string message.
593    ///
594    /// ## Examples
595    /// ```
596    /// extern crate flogging;
597    /// use flogging::*;
598    ///
599    /// let mut log = Logger::console_logger(module_path!());
600    /// log.set_level(Level::FINEST);
601    /// log.set_fn_name("main");
602    ///
603    /// log.info("Some text to store.");
604    /// ```
605    /// Output:
606    /// ```text
607    /// |flogging->main| [INFO   ] Some text to store.
608    /// ```
609    ///
610    pub fn info(&mut self, msg: &str) {
611        self.log(Level::INFO, &self.fn_name(), msg);
612    }
613
614    ///
615    /// Check if a message of the given level would actually be logged by this logger.
616    ///
617    /// ## Parameters
618    /// - `level` - The level to compare with.
619    ///
620    /// Returns `true` if it is loggable, `false` otherwise.
621    ///
622    fn is_loggable(&self, level: &Level) -> bool {
623        *level >= self.level
624    }
625
626    ///
627    /// Obtain the current default logging level for this Log instance.
628    ///
629    pub fn level(&self) -> &Level {
630        &self.level
631    }
632
633    ///
634    /// Log a `LogEntry`.
635    ///
636    /// All the other logging methods in this class call through this method to actually
637    /// perform any logging.
638    ///
639    /// ## Parameters
640    /// - `entry` - The `LogEntry` to be published.
641    ///
642    fn _log(&mut self, entry: &mut LogEntry) {
643        entry.set_mod_path(self.mod_path.clone());
644
645        for handler in self.handlers.get_mut() {
646            handler.1.publish(entry);
647        }
648    }
649
650    ///
651    /// Log a message, with no arguments.
652    ///
653    /// If the logger is currently enabled for the given message level then the given
654    /// message is forwarded to all the registered output `Handler` objects.
655    ///
656    /// ## Parameters
657    /// - `level` - One of the message level identifiers, e.g., SEVERE.
658    /// - `fn_name` - The name of the function/method from-which this method
659    ///   was called.
660    /// - `msg` - The string message.
661    ///
662    fn log(&mut self, level: Level, fn_name: &str, msg: &str) {
663        if !self.is_loggable(&level) {
664            return;
665        }
666
667        // build LogEntry
668        let mut log_entry = LogEntry::create(level, fn_name.to_string(), msg.to_string());
669        // Send LogEntry
670        self._log(&mut log_entry);
671    }
672
673    ///
674    /// Reset this `Logger` instance's default logging level.
675    ///
676    /// Returns itself for chaining purposes.
677    ///
678    /// See [Level]
679    ///
680    pub fn reset_level(&mut self) -> &mut Self {
681        self.level = Level::default();
682        self
683    }
684
685    ///
686    /// Set the current function/method name.
687    ///
688    /// ## Parameters
689    /// - `fn_name` - The name of the function/method in which you are
690    ///   logging.
691    ///
692    /// Returns itself for chaining purposes.
693    ///
694    pub fn set_fn_name(&mut self, fn_name: &str) -> &mut Self {
695        self.fn_name = fn_name.to_string();
696        self
697    }
698
699    ///
700    /// Set default logging level for this Log instance.
701    ///
702    /// ## Parameters
703    /// - `level` - The new logging level to set.
704    ///
705    /// Returns itself for chaining purposes.
706    ///
707    pub fn set_level(&mut self, level: Level) -> &mut Self {
708        self.level = level;
709        self
710    }
711
712    ///
713    /// Log a SEVERE message.
714    ///
715    /// If the logger is currently enabled for the SEVERE message level
716    /// then the given message is forwarded to all the registered output
717    /// Handler objects.
718    ///
719    /// ## Parameters
720    /// - `msg` - The string message.
721    ///
722    /// ## Examples
723    /// ```
724    /// extern crate flogging;
725    /// use flogging::*;
726    ///
727    /// let mut log = Logger::console_logger(module_path!());
728    /// log.set_level(Level::FINEST);
729    /// log.set_fn_name("main");
730    ///
731    /// log.severe("Some text to store.");
732    /// ```
733    /// Output:
734    /// ```text
735    /// |flogging->main| [SEVERE ] Some text to store.
736    /// ```
737    ///
738    pub fn severe(&mut self, msg: &str) {
739        self.log(Level::SEVERE, &self.fn_name(), msg);
740    }
741
742    ///
743    /// Create new Logger instance, with a `ConsoleHandler`.
744    ///
745    /// Logging level is set to it's default setting (INFO).
746    ///
747    /// I expect this will be primarily used during unit testing of
748    /// the logging output. Though, any requirement to pass-on the log entry,
749    /// perhaps for further processing, would also be a valid use case.
750    ///
751    /// ## Parameters
752    /// - `mod_path`- The module path. Suggest using [`module_path!()`].
753    ///
754    /// Returns a configured `Logger`.
755    ///
756    /// ## Examples
757    /// ```
758    /// extern crate flogging;
759    /// use flogging::*;
760    ///
761    /// let mut log = Logger::string_logger(module_path!());
762    /// log.set_fn_name("main");
763    ///
764    /// log.warning("Don't over do it.");
765    ///
766    /// let log_str = log.get_handler(Handler::String).unwrap().get_log();
767    ///
768    /// println!("{log_str}");
769    /// ```
770    /// Output:
771    /// ```text
772    /// |flogging->main| [WARNING] Don't over do it.
773    /// ```
774    ///
775    pub fn string_logger(mod_path: &str) -> Logger {
776        Logger::builder(mod_path).add_string_handler().build()
777    }
778
779    ///
780    /// Log a WARNING message.
781    ///
782    /// If the logger is currently enabled for the WARNING message level
783    /// then the given message is forwarded to all the registered output
784    /// Handler objects.
785    ///
786    /// ## Parameters
787    /// - `msg` - The string message.
788    ///
789    /// ## Examples
790    /// ```
791    /// extern crate flogging;
792    /// use flogging::*;
793    ///
794    /// let mut log = Logger::console_logger(module_path!());
795    /// log.set_level(Level::FINEST);
796    /// log.set_fn_name("main");
797    ///
798    /// log.warning("Some text to store.");
799    /// ```
800    /// Output:
801    /// ```text
802    /// |flogging->main| [WARNING] Some text to store.
803    /// ```
804    ///
805    pub fn warning(&mut self, msg: &str) {
806        self.log(Level::WARNING, &self.fn_name(), msg);
807    }
808}
809
810impl fmt::Display for Logger {
811    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
812        let mut buf = String::new();
813
814        for elem in self.handlers.borrow().iter() {
815            let s = format!("{}: {}\n", elem.0, elem.1);
816            buf.push_str(&s);
817        }
818
819        writeln!(
820            f,
821            "{}::{} - [{}]\n\n{}",
822            self.mod_path, self.fn_name, self.level, buf
823        )
824    }
825}
826
827#[cfg(test)]
828mod tests;