mithril_common/
logging.rs1use slog::Logger;
4
5pub trait LoggerExtensions {
7 fn new_with_component_name<T>(&self) -> Self;
9
10 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 #[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}