Skip to main content

dcontext_tracing/
field_mapping.rs

1use std::collections::HashMap;
2use std::fmt;
3
4use tracing_core::field::{Field, Visit};
5
6/// A mapping from a tracing span field to a dcontext key.
7///
8/// When a span with the configured field is entered, the field value
9/// is extracted and set as a dcontext context value.
10pub(crate) struct FieldMapping {
11    pub field_name: &'static str,
12    pub context_key: &'static str,
13    pub setter: Box<dyn FieldSetter>,
14}
15
16/// Trait for setting a dcontext value from a tracing field value.
17pub(crate) trait FieldSetter: Send + Sync {
18    /// Try to set the context value from a string representation.
19    fn set_from_str(&self, key: &'static str, value: &str);
20    /// Try to set the context value from a u64.
21    fn set_from_u64(&self, key: &'static str, value: u64);
22    /// Try to set the context value from an i64.
23    fn set_from_i64(&self, key: &'static str, value: i64);
24    /// Try to set the context value from a bool.
25    fn set_from_bool(&self, key: &'static str, value: bool);
26}
27
28/// Trait for types that can be constructed from tracing field values.
29///
30/// Implement this for your context types to enable automatic
31/// field-to-context mapping.
32///
33/// # Example
34///
35/// ```rust
36/// use dcontext_tracing::FromFieldValue;
37///
38/// #[derive(Clone, Default, Debug, serde::Serialize, serde::Deserialize)]
39/// struct RequestId(String);
40///
41/// impl FromFieldValue for RequestId {
42///     fn from_str_value(s: &str) -> Option<Self> {
43///         Some(RequestId(s.to_string()))
44///     }
45/// }
46/// ```
47pub trait FromFieldValue: Clone + Default + Send + Sync + serde::Serialize + serde::de::DeserializeOwned + 'static {
48    /// Construct from a string value. Returns `None` if conversion fails.
49    fn from_str_value(s: &str) -> Option<Self> {
50        let _ = s;
51        None
52    }
53
54    /// Construct from a u64 value. Returns `None` if conversion fails.
55    fn from_u64_value(v: u64) -> Option<Self> {
56        let _ = v;
57        None
58    }
59
60    /// Construct from an i64 value. Returns `None` if conversion fails.
61    fn from_i64_value(v: i64) -> Option<Self> {
62        let _ = v;
63        None
64    }
65
66    /// Construct from a bool value. Returns `None` if conversion fails.
67    fn from_bool_value(v: bool) -> Option<Self> {
68        let _ = v;
69        None
70    }
71}
72
73/// A concrete FieldSetter for a specific type T.
74pub(crate) struct TypedFieldSetter<T> {
75    _marker: std::marker::PhantomData<T>,
76}
77
78impl<T> TypedFieldSetter<T> {
79    pub fn new() -> Self {
80        Self {
81            _marker: std::marker::PhantomData,
82        }
83    }
84}
85
86impl<T> FieldSetter for TypedFieldSetter<T>
87where
88    T: FromFieldValue,
89{
90    fn set_from_str(&self, key: &'static str, value: &str) {
91        if let Some(v) = T::from_str_value(value) {
92            dcontext::set_context(key, v);
93        }
94    }
95
96    fn set_from_u64(&self, key: &'static str, value: u64) {
97        if let Some(v) = T::from_u64_value(value) {
98            dcontext::set_context(key, v);
99        }
100    }
101
102    fn set_from_i64(&self, key: &'static str, value: i64) {
103        if let Some(v) = T::from_i64_value(value) {
104            dcontext::set_context(key, v);
105        }
106    }
107
108    fn set_from_bool(&self, key: &'static str, value: bool) {
109        if let Some(v) = T::from_bool_value(value) {
110            dcontext::set_context(key, v);
111        }
112    }
113}
114
115/// Extracted field values from a span, stored in span extensions.
116///
117/// Must be `Send + Sync` to satisfy span extension requirements.
118pub(crate) struct ExtractedFields {
119    pub string_values: HashMap<&'static str, String>,
120    pub u64_values: HashMap<&'static str, u64>,
121    pub i64_values: HashMap<&'static str, i64>,
122    pub bool_values: HashMap<&'static str, bool>,
123}
124
125impl ExtractedFields {
126    pub fn new() -> Self {
127        Self {
128            string_values: HashMap::new(),
129            u64_values: HashMap::new(),
130            i64_values: HashMap::new(),
131            bool_values: HashMap::new(),
132        }
133    }
134
135    pub fn is_empty(&self) -> bool {
136        self.string_values.is_empty()
137            && self.u64_values.is_empty()
138            && self.i64_values.is_empty()
139            && self.bool_values.is_empty()
140    }
141}
142
143/// Visitor that extracts field values from span attributes.
144pub(crate) struct FieldExtractor<'a> {
145    /// Set of field names we're interested in.
146    pub target_fields: &'a [&'static str],
147    pub extracted: ExtractedFields,
148}
149
150impl<'a> FieldExtractor<'a> {
151    pub fn new(target_fields: &'a [&'static str]) -> Self {
152        Self {
153            target_fields,
154            extracted: ExtractedFields::new(),
155        }
156    }
157
158    fn is_target(&self, field: &Field) -> Option<&'static str> {
159        self.target_fields.iter().find(|&&f| f == field.name()).copied()
160    }
161}
162
163impl<'a> Visit for FieldExtractor<'a> {
164    fn record_str(&mut self, field: &Field, value: &str) {
165        if let Some(key) = self.is_target(field) {
166            self.extracted.string_values.insert(key, value.to_string());
167        }
168    }
169
170    fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) {
171        if let Some(key) = self.is_target(field) {
172            // Fall back to Debug formatting for non-string types
173            if !self.extracted.string_values.contains_key(key)
174                && !self.extracted.u64_values.contains_key(key)
175                && !self.extracted.i64_values.contains_key(key)
176                && !self.extracted.bool_values.contains_key(key)
177            {
178                self.extracted
179                    .string_values
180                    .insert(key, format!("{:?}", value));
181            }
182        }
183    }
184
185    fn record_u64(&mut self, field: &Field, value: u64) {
186        if let Some(key) = self.is_target(field) {
187            self.extracted.u64_values.insert(key, value);
188        }
189    }
190
191    fn record_i64(&mut self, field: &Field, value: i64) {
192        if let Some(key) = self.is_target(field) {
193            self.extracted.i64_values.insert(key, value);
194        }
195    }
196
197    fn record_bool(&mut self, field: &Field, value: bool) {
198        if let Some(key) = self.is_target(field) {
199            self.extracted.bool_values.insert(key, value);
200        }
201    }
202}