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}