mithril_common/
logging.rs

1//! Logging utilities for the Mithril project.
2
3use slog::Logger;
4
5/// Extension trait for `slog::Logger`
6pub trait LoggerExtensions {
7    /// Create a new child logger with a `src` key containing the component name.
8    fn new_with_component_name<T>(&self) -> Self;
9
10    /// Create a new child logger with a `src` key containing the provided name.
11    fn new_with_name(&self, name: &str) -> Self;
12}
13
14impl LoggerExtensions for Logger {
15    fn new_with_component_name<T>(&self) -> Self {
16        self.new_with_name(component_name::<T>())
17    }
18
19    fn new_with_name(&self, name: &str) -> Self {
20        self.new(slog::o!("src" => name.to_owned()))
21    }
22}
23
24fn component_name<T>() -> &'static str {
25    let complete_name = std::any::type_name::<T>();
26    let without_generic = {
27        if complete_name.contains('<') {
28            complete_name.split('<').next().unwrap_or("")
29        } else {
30            complete_name
31        }
32    };
33    without_generic.split("::").last().unwrap_or(complete_name)
34}
35
36#[cfg(test)]
37mod tests {
38    use super::*;
39    use crate::test_utils::TestLogger;
40    use slog::info;
41
42    struct TestStruct;
43    // The `allow(dead_code)` is used because a field is needed to add the lifetime but is unused.
44    #[allow(dead_code)]
45    struct TestStructWithLifetime<'a>(&'a str);
46    enum TestEnum {}
47
48    struct TestStructWithGeneric<T> {
49        _phantom: std::marker::PhantomData<T>,
50    }
51
52    mod test_mod {
53        pub struct ScopedTestStruct;
54        pub enum ScopedTestEnum {}
55    }
56
57    impl TestStruct {
58        fn self_component_name() -> &'static str {
59            component_name::<Self>()
60        }
61    }
62
63    #[test]
64    fn extract_component_name_remove_namespaces() {
65        assert_eq!(component_name::<TestStruct>(), "TestStruct");
66        assert_eq!(component_name::<TestEnum>(), "TestEnum");
67        assert_eq!(
68            component_name::<test_mod::ScopedTestStruct>(),
69            "ScopedTestStruct"
70        );
71        assert_eq!(
72            component_name::<test_mod::ScopedTestEnum>(),
73            "ScopedTestEnum"
74        );
75        assert_eq!(TestStruct::self_component_name(), "TestStruct");
76        assert_eq!(
77            component_name::<TestStructWithLifetime>(),
78            "TestStructWithLifetime"
79        );
80        assert_eq!(
81            component_name::<TestStructWithGeneric<test_mod::ScopedTestStruct>>(),
82            "TestStructWithGeneric"
83        );
84        assert_eq!(
85            component_name::<TestStructWithGeneric<&str>>(),
86            "TestStructWithGeneric"
87        );
88    }
89
90    #[test]
91    fn logger_extension_new_with_component_name() {
92        let (root_logger, log_inspector) = TestLogger::memory();
93        let child_logger = root_logger.new_with_component_name::<TestStruct>();
94        info!(child_logger, "Child log");
95
96        assert!(
97            log_inspector.contains_log("src") && log_inspector.contains_log("TestStruct"),
98            "log should contain `src` key for `TestStruct` as component name was provided, logs:\n{log_inspector}"
99        );
100    }
101
102    #[test]
103    fn logger_extension_new_with_name() {
104        let expected_name = "my name";
105        let (root_logger, log_inspector) = TestLogger::memory();
106        let child_logger = root_logger.new_with_name(expected_name);
107        info!(child_logger, "Child log");
108
109        assert!(
110            log_inspector.contains_log("src") && log_inspector.contains_log(expected_name),
111            "log should contain `src` key for `{expected_name}` as a name was provided, logs:\n{log_inspector}"
112        );
113    }
114}