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