Skip to main content

context_logger/
fields.rs

1use std::{borrow::Cow, collections::HashMap};
2
3use crate::LogValue;
4
5pub type LogFieldsIter<'a> = std::collections::hash_map::Iter<'a, Cow<'static, str>, LogValue>;
6pub type LogFieldsIntoIter = std::collections::hash_map::IntoIter<Cow<'static, str>, LogValue>;
7pub type LogField = (Cow<'static, str>, LogValue);
8pub type LogFieldRef<'a> = (&'a Cow<'static, str>, &'a LogValue);
9
10/// A set of key-value fields that can be attached to a logging scope.
11///
12/// [`LogFields`] stores structured attributes that extend
13/// the key-values source of a [`log::Record`]. When a record flows through
14/// [`crate::ContextLogger`], these fields are merged into the record's own
15/// [`log::kv::Source`] so they appear in the output alongside the
16/// user-provided key-values.
17///
18/// # Ordering
19///
20/// The order in which fields appear is **not guaranteed**. Do not rely on any
21/// specific ordering of keys.
22#[derive(Debug, Clone, Default)]
23pub struct LogFields(pub(crate) HashMap<Cow<'static, str>, LogValue>);
24
25impl LogFields {
26    /// Creates a new, empty set of fields.
27    #[must_use]
28    pub fn new() -> Self {
29        Self::default()
30    }
31
32    /// Inserts a key-value pair into this collection, returning the collection
33    /// for chained calls.
34    ///
35    /// This method takes ownership of `self`, so it can be used as part of a
36    /// builder-style chain:
37    ///
38    /// # Examples
39    ///
40    /// ```
41    /// use context_logger::LogFields;
42    ///
43    /// let fields = LogFields::new()
44    ///       .with("user_id", "user-123")
45    ///       .with("request_id", 42);
46    /// ```
47    #[must_use]
48    pub fn with(mut self, key: impl Into<Cow<'static, str>>, value: impl Into<LogValue>) -> Self {
49        self.insert(key, value);
50        self
51    }
52
53    /// Inserts a key-value pair into this collection.
54    ///
55    /// Unlike [`Self::with`], this method borrows `self` and
56    /// returns a mutable reference, allowing it to be used when chaining with
57    /// other methods that require borrowing.
58    ///
59    /// # Examples
60    ///
61    /// ```
62    /// use context_logger::LogFields;
63    ///
64    /// let mut fields = LogFields::new();
65    /// fields
66    ///     .insert("user_id", "user-123")
67    ///     .insert("request_id", 42);
68    /// ```
69    pub fn insert(
70        &mut self,
71        key: impl Into<Cow<'static, str>>,
72        value: impl Into<LogValue>,
73    ) -> &mut Self {
74        self.0.insert(key.into(), value.into());
75        self
76    }
77
78    /// Merges this collection with the fields from another one.
79    ///
80    /// This method borrows `self` and returns a mutable reference, allowing it
81    /// to be used when chaining with other methods that require borrowing.
82    ///
83    /// # Merging policy
84    ///
85    /// Keys in this collection with duplicate names will be overwritten by keys
86    /// from the provided collection. The order of keys in the resulting
87    /// collection is undefined.
88    ///
89    /// # Examples
90    ///
91    /// ```
92    /// use context_logger::LogFields;
93    ///
94    /// # let other_fields = LogFields::new();
95    /// let mut fields = LogFields::new();
96    /// fields
97    ///     .insert("user_id", "Alice")
98    ///     .merge_with(other_fields)
99    ///     .insert("request_id", 42);
100    /// ```
101    pub fn merge_with(&mut self, other: impl IntoIterator<Item = LogField>) -> &mut Self {
102        self.0.extend(other);
103        self
104    }
105
106    /// Returns an iterator over the fields in this collection.
107    #[must_use]
108    pub fn iter(&self) -> LogFieldsIter<'_> {
109        self.0.iter()
110    }
111
112    /// Returns `true` if this collection contains no fields.
113    #[must_use]
114    pub fn is_empty(&self) -> bool {
115        self.0.is_empty()
116    }
117}
118
119impl<'a> IntoIterator for &'a LogFields {
120    type Item = LogFieldRef<'a>;
121    type IntoIter = LogFieldsIter<'a>;
122
123    fn into_iter(self) -> Self::IntoIter {
124        self.iter()
125    }
126}
127
128impl IntoIterator for LogFields {
129    type Item = LogField;
130    type IntoIter = LogFieldsIntoIter;
131
132    fn into_iter(self) -> Self::IntoIter {
133        self.0.into_iter()
134    }
135}
136
137impl Extend<LogField> for LogFields {
138    fn extend<I: IntoIterator<Item = LogField>>(&mut self, iter: I) {
139        self.0.extend(iter);
140    }
141}
142
143impl FromIterator<LogField> for LogFields {
144    fn from_iter<T: IntoIterator<Item = LogField>>(iter: T) -> Self {
145        Self(HashMap::from_iter(iter))
146    }
147}
148
149#[cfg(test)]
150impl LogFields {
151    /// Returns a reference to the value associated with the given key, if it
152    /// exists.
153    pub(crate) fn find(&self, key: impl AsRef<str>) -> Option<&LogValue> {
154        self.0.get(&Cow::Owned(key.as_ref().to_owned()))
155    }
156}
157
158#[cfg(test)]
159impl std::ops::Index<&str> for LogFields {
160    type Output = LogValue;
161
162    fn index(&self, index: &str) -> &Self::Output {
163        self.0
164            .get(&Cow::Owned(index.to_owned()))
165            .expect("No value found for the given key")
166    }
167}