Skip to main content

bitwarden_logging/
visitor.rs

1//! Visitor for extracting fields from tracing events.
2
3use std::collections::HashMap;
4
5use tracing::field::{Field, Visit};
6
7/// Extracts the message and structured fields from a tracing event.
8#[derive(Debug, Default)]
9pub struct MessageVisitor {
10    /// The extracted message, if present.
11    pub message: String,
12    /// Additional structured fields from the event.
13    pub fields: HashMap<String, String>,
14}
15
16impl Visit for MessageVisitor {
17    fn record_str(&mut self, field: &Field, value: &str) {
18        if field.name() == "message" {
19            self.message = value.to_string();
20        } else {
21            self.fields
22                .insert(field.name().to_string(), value.to_string());
23        }
24    }
25
26    fn record_debug(&mut self, field: &Field, value: &dyn std::fmt::Debug) {
27        if field.name() == "message" {
28            self.message = format!("{:?}", value);
29        } else {
30            self.fields
31                .insert(field.name().to_string(), format!("{:?}", value));
32        }
33    }
34}
35
36#[cfg(test)]
37mod tests {
38    use tracing::Level;
39
40    use super::*;
41
42    #[test]
43    fn test_visitor_extracts_message() {
44        let fields = tracing::field::FieldSet::new(
45            &["message"],
46            tracing::callsite::Identifier(&TEST_CALLSITE),
47        );
48
49        let mut visitor = MessageVisitor::default();
50        assert!(visitor.message.is_empty());
51        assert!(visitor.fields.is_empty());
52
53        let field = fields.field("message").expect("field should exist");
54        visitor.record_str(&field, "hello world");
55        assert_eq!(visitor.message, "hello world");
56    }
57
58    #[test]
59    fn test_visitor_extracts_extra_fields() {
60        let fields = tracing::field::FieldSet::new(
61            &["message", "user_id"],
62            tracing::callsite::Identifier(&TEST_CALLSITE),
63        );
64
65        let mut visitor = MessageVisitor::default();
66
67        let msg_field = fields.field("message").expect("field should exist");
68        visitor.record_str(&msg_field, "login attempt");
69
70        let id_field = fields.field("user_id").expect("field should exist");
71        visitor.record_str(&id_field, "abc-123");
72
73        assert_eq!(visitor.message, "login attempt");
74        assert_eq!(visitor.fields.get("user_id"), Some(&"abc-123".to_string()));
75    }
76
77    #[test]
78    fn test_visitor_record_debug_fallback() {
79        let fields = tracing::field::FieldSet::new(
80            &["message", "count"],
81            tracing::callsite::Identifier(&TEST_CALLSITE),
82        );
83
84        let mut visitor = MessageVisitor::default();
85
86        let count_field = fields.field("count").expect("field should exist");
87        visitor.record_debug(&count_field, &42);
88
89        assert_eq!(visitor.fields.get("count"), Some(&"42".to_string()));
90    }
91
92    // Minimal callsite for constructing FieldSet in tests
93    static TEST_CALLSITE: TestCallsite = TestCallsite;
94
95    struct TestCallsite;
96
97    impl tracing::callsite::Callsite for TestCallsite {
98        fn set_interest(&self, _interest: tracing::subscriber::Interest) {}
99        fn metadata(&self) -> &tracing::Metadata<'_> {
100            static META: std::sync::LazyLock<tracing::Metadata<'static>> =
101                std::sync::LazyLock::new(|| {
102                    tracing::metadata::Metadata::new(
103                        "test",
104                        "test_target",
105                        Level::INFO,
106                        None,
107                        None,
108                        None,
109                        tracing::field::FieldSet::new(
110                            &[],
111                            tracing::callsite::Identifier(&TEST_CALLSITE),
112                        ),
113                        tracing::metadata::Kind::EVENT,
114                    )
115                });
116            &META
117        }
118    }
119}