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::{TempDir, 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 log_path =
93            TempDir::create("common_logging", "logger_extension_new_with_component_name")
94                .join("test.log");
95        {
96            let root_logger = TestLogger::file(&log_path);
97            let child_logger = root_logger.new_with_component_name::<TestStruct>();
98            info!(child_logger, "Child log");
99        }
100
101        let logs = std::fs::read_to_string(&log_path).unwrap();
102        assert!(
103            logs.contains("src") && logs.contains("TestStruct"),
104            "log should contain `src` key for `TestStruct` as component name was provided, logs:\n{logs}"
105        );
106    }
107
108    #[test]
109    fn logger_extension_new_with_name() {
110        let expected_name = "my name";
111        let log_path =
112            TempDir::create("common_logging", "logger_extension_new_with_name").join("test.log");
113        {
114            let root_logger = TestLogger::file(&log_path);
115            let child_logger = root_logger.new_with_name(expected_name);
116            info!(child_logger, "Child log");
117        }
118
119        let logs = std::fs::read_to_string(&log_path).unwrap();
120        assert!(
121            logs.contains("src") && logs.contains(expected_name),
122            "log should contain `src` key for `{expected_name}` as a name was provided, logs:\n{logs}"
123        );
124    }
125}