spdlog/
kv.rs

1//! Structured logging.
2//!
3//! Structured logging enhances traditional text-based log records with
4//! user-defined attributes. Structured logs can be analyzed using a variety of
5//! data processing techniques, without needing to find and parse attributes
6//! from unstructured text first.
7//!
8//! [`Key`]s are strings and [`Value`]s are a datum of any type that can be
9//! formatted or serialized. Simple types like strings, booleans, and numbers
10//! are supported, as well as arbitrarily complex structures involving nested
11//! objects and sequences.
12//!
13//! [`Value`] uses [_value-bag_ crate] as the backend, which is an alias of
14//! [`value_bag::ValueBag`].
15//!
16//! KVs will be passed into a [`Record`] to be processed by [`Formatter`]s via
17//! [`Record::key_values`] method.
18//!
19//! ## Examples
20//!
21//! #### Basic syntax
22//!
23//! In logging macros, an optional named parameter `kv` (like `logger`) is used
24//! to add key-values to a log.
25//!
26//! ```
27//! # use spdlog::prelude::*;
28//! info!("program started", kv: { pid = std::process::id() });
29//!
30//! # let telemetry = spdlog::default_logger();
31//! trace!(logger: telemetry, "user logged in", kv: { username = "John" });
32//!
33//! let ip = "1.1.1.1";
34//! trace!("DNS setup", kv: { ip });
35//! //                        ^^ Shorthand syntax, equivalent to `ip = ip`
36//! ```
37//!
38//! #### Modifier
39//!
40//! A value is stored directly with its type by default (after erasure, of
41//! course), using _modifier_ if you want it to be stored in another format.
42//!
43//! | Modifier | Description                                                                           |
44//! |----------|---------------------------------------------------------------------------------------|
45//! |          | No modifier, capture the value directly                                               |
46//! | `:`      | Capture the value using [`Display`] trait                                             |
47//! | `:?`     | Capture the value using [`Debug`] trait                                               |
48//! | `:sval`  | Capture the value using [`sval::Value`] trait, crate feature `sval` is required       |
49//! | `:serde` | Capture the value using [`serde::Serialize`] trait, crate feature `serde` is required |
50//!
51//! ```
52//! # use spdlog::prelude::*;
53//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
54//! # struct Url;
55//! # impl Url { fn parse(_: &str) -> Result<Self, Box<dyn std::error::Error>> { Ok(Self) } }
56//! # impl std::fmt::Display for Url {
57//! #     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
58//! #         write!(f, "")
59//! #     }
60//! # }
61//! let url = Url::parse("https://example.com")?;
62//! trace!("user browsed website", kv: { url: });
63//! //                                      ^ Capture the value using `Display` trait
64//! //                                   ^^^^ Shorthand syntax, equivalent to `url: = url`
65//!
66//! let orders = vec!["coffee", "pizza", "soup"];
67//! info!("order received", kv: { orders:? });
68//! //                                  ^^ Capture the value using `Debug` trait
69//! //                            ^^^^^^^^ Shorthand syntax, equivalent to `orders:? = orders`
70//!
71//! #[derive(sval_derive::Value)]
72//! struct Point { x: f32, y: f32 }
73//!
74//! let pos = Point { x: 11.4, y: 5.14 };
75//! # #[cfg(feature = "sval")]
76//! trace!("user clicked", kv: { pos:sval });
77//! //                              ^^^^^ Capture the value using `sval::Value` trait
78//! //                           ^^^^^^^^ Shorthand syntax, equivalent to `pos:sval = pos`
79//! # Ok(()) }
80//! ```
81//! [_value-bag_ crate]: https://crates.io/crates/value-bag
82//! [`Record`]: crate::Record
83//! [`Formatter`]: crate::formatter::Formatter
84//! [`Display`]: std::fmt::Display
85//! [`Record::key_values`]: crate::Record::key_values
86//!
87//! [`sval::Value`]: https://docs.rs/sval/latest/sval/trait.Value.html
88// TODO: This above link annotation is unnecessary, but Rustdoc has bug:
89//       https://github.com/rust-lang/cargo/issues/3475
90//       Remove it when the bug is fixed.
91use std::{borrow::Cow, fmt, slice};
92
93use value_bag::{OwnedValueBag, ValueBag};
94
95use crate::utils::RefStr;
96
97/// Represents a key in a key-value pair.
98#[derive(Debug, Clone)]
99pub struct Key<'a>(RefStr<'a>);
100
101impl Key<'_> {
102    /// Gets the key string.
103    pub fn as_str(&self) -> &str {
104        self.0.get()
105    }
106}
107
108impl<'a> Key<'a> {
109    #[doc(hidden)]
110    pub fn __from_static_str(key: &'static str) -> Self {
111        Key(RefStr::new_static(key))
112    }
113
114    pub(crate) fn from_str(key: &'a str) -> Self {
115        Key(RefStr::new_ref(key))
116    }
117
118    pub(crate) fn to_owned(&self) -> KeyOwned {
119        KeyOwned(self.0.to_cow_static())
120    }
121
122    #[cfg(test)]
123    pub(crate) fn inner(&self) -> RefStr<'a> {
124        self.0
125    }
126}
127
128impl PartialEq for Key<'_> {
129    fn eq(&self, other: &Self) -> bool {
130        self.as_str() == other.as_str()
131    }
132}
133
134#[derive(Debug, Clone)]
135pub(crate) struct KeyOwned(Cow<'static, str>);
136
137impl KeyOwned {
138    pub(crate) fn as_ref(&self) -> Key<'_> {
139        Key(RefStr::new_ref(&self.0))
140    }
141}
142
143/// Represents a value in a key-value pair.
144pub type Value<'a> = ValueBag<'a>;
145pub(crate) type ValueOwned = OwnedValueBag;
146
147enum KeyValuesInner<'a> {
148    Borrowed(&'a [Pair<'a>]),
149    Owned(&'a [(KeyOwned, ValueOwned)]),
150}
151enum KeyValuesIterInner<'a> {
152    Borrowed(slice::Iter<'a, Pair<'a>>),
153    Owned(slice::Iter<'a, (KeyOwned, ValueOwned)>),
154}
155
156/// Represents a collection of key-value pairs.
157///
158/// ## Examples
159///
160/// ```
161/// use std::fmt::Write;
162/// use spdlog::{
163///     formatter::{Formatter, FormatterContext},
164///     Record, StringBuf,
165/// };
166///
167/// #[derive(Clone)]
168/// struct MyFormatter;
169///
170/// impl Formatter for MyFormatter {
171///     fn format(
172///         &self,
173///         record: &Record,
174///         dest: &mut StringBuf,
175///         ctx: &mut FormatterContext,
176///     ) -> spdlog::Result<()> {
177///         dest.write_str(record.payload())
178///             .map_err(spdlog::Error::FormatRecord)?;
179///         for (key, value) in record.key_values() {
180///             write!(dest, " {}={}", key.as_str(), value).map_err(spdlog::Error::FormatRecord)?;
181///         }
182///         Ok(())
183///     }
184/// }
185/// ```
186pub struct KeyValues<'a>(KeyValuesInner<'a>);
187
188impl<'a> KeyValues<'a> {
189    /// Gets the number of key-value pairs.
190    pub fn len(&self) -> usize {
191        match self.0 {
192            KeyValuesInner::Borrowed(p) => p.len(),
193            KeyValuesInner::Owned(p) => p.len(),
194        }
195    }
196
197    /// Checks if there are no key-value pairs.
198    pub fn is_empty(&self) -> bool {
199        match self.0 {
200            KeyValuesInner::Borrowed(p) => p.is_empty(),
201            KeyValuesInner::Owned(p) => p.is_empty(),
202        }
203    }
204
205    /// Gets the value of the specified key.
206    pub fn get(&self, key: Key) -> Option<Value<'a>> {
207        match self.0 {
208            KeyValuesInner::Borrowed(p) => {
209                p.iter()
210                    .find_map(|(k, v)| if k == &key { Some(v.clone()) } else { None })
211            }
212            KeyValuesInner::Owned(p) => p.iter().find_map(|(k, v)| {
213                if k.as_ref() == key {
214                    Some(v.by_ref())
215                } else {
216                    None
217                }
218            }),
219        }
220    }
221
222    /// Gets an iterator over the key-value pairs.
223    pub fn iter(&self) -> KeyValuesIter<'a> {
224        match &self.0 {
225            KeyValuesInner::Borrowed(p) => KeyValuesIter(KeyValuesIterInner::Borrowed(p.iter())),
226            KeyValuesInner::Owned(p) => KeyValuesIter(KeyValuesIterInner::Owned(p.iter())),
227        }
228    }
229
230    pub(crate) fn with_borrowed(pairs: &'a [Pair<'a>]) -> Self {
231        Self(KeyValuesInner::Borrowed(pairs))
232    }
233
234    pub(crate) fn with_owned(pairs: &'a [(KeyOwned, ValueOwned)]) -> Self {
235        Self(KeyValuesInner::Owned(pairs))
236    }
237
238    pub(crate) fn write_to(&self, dest: &mut impl fmt::Write, brackets: bool) -> fmt::Result {
239        let mut iter = self.iter();
240        let first = iter.next();
241        if let Some((key, value)) = first {
242            if brackets {
243                dest.write_str(" { ")?;
244            }
245
246            // Reduce branch prediction misses for performance
247            // So we manually process the first KV pair
248            dest.write_str(key.as_str())?;
249            dest.write_str("=")?;
250            write!(dest, "{value}")?;
251
252            for (key, value) in iter {
253                dest.write_str(" ")?;
254                dest.write_str(key.as_str())?;
255                dest.write_str("=")?;
256                write!(dest, "{value}")?;
257            }
258
259            if brackets {
260                dest.write_str(" }")?;
261            }
262        }
263        Ok(())
264    }
265}
266
267impl<'a> IntoIterator for KeyValues<'a> {
268    type Item = Pair<'a>;
269    type IntoIter = KeyValuesIter<'a>;
270
271    fn into_iter(self) -> Self::IntoIter {
272        self.iter()
273    }
274}
275
276/// Represents an iterator over key-value pairs.
277pub struct KeyValuesIter<'a>(KeyValuesIterInner<'a>);
278
279impl<'a> Iterator for KeyValuesIter<'a> {
280    type Item = Pair<'a>;
281
282    fn next(&mut self) -> Option<Self::Item> {
283        match &mut self.0 {
284            // The 2 clones should be cheap
285            KeyValuesIterInner::Borrowed(iter) => iter.next().map(|(k, v)| (k.clone(), v.clone())),
286            KeyValuesIterInner::Owned(iter) => iter.next().map(|(k, v)| (k.as_ref(), v.by_ref())),
287        }
288    }
289
290    fn size_hint(&self) -> (usize, Option<usize>) {
291        match &self.0 {
292            KeyValuesIterInner::Borrowed(iter) => iter.size_hint(),
293            KeyValuesIterInner::Owned(iter) => iter.size_hint(),
294        }
295    }
296}
297
298pub(crate) type Pair<'a> = (Key<'a>, Value<'a>);
299
300#[cfg(feature = "log")]
301pub(crate) struct LogCrateConverter<'a>(Vec<(log::kv::Key<'a>, ValueOwned)>);
302
303#[cfg(feature = "log")]
304impl<'a> LogCrateConverter<'a> {
305    pub(crate) fn new(capacity: usize) -> Self {
306        Self(Vec::with_capacity(capacity))
307    }
308
309    pub(crate) fn finalize(self) -> Vec<(log::kv::Key<'a>, ValueOwned)> {
310        self.0
311    }
312}
313
314#[cfg(feature = "log")]
315impl<'a> log::kv::VisitSource<'a> for LogCrateConverter<'a> {
316    fn visit_pair(
317        &mut self,
318        key: log::kv::Key<'a>,
319        value: log::kv::Value<'a>,
320    ) -> Result<(), log::kv::Error> {
321        struct Visitor(Option<ValueOwned>);
322
323        macro_rules! visit_fn {
324            ( $($fn:ident: $ty:ty => $from:ident),+$(,)? ) => {
325                $(fn $fn(&mut self, value: $ty) -> Result<(), log::kv::Error> {
326                    self.0 = Some(Value::$from(value).to_owned());
327                    Ok(())
328                })+
329            };
330        }
331
332        impl log::kv::VisitValue<'_> for Visitor {
333            fn visit_any(&mut self, value: log::kv::Value) -> Result<(), log::kv::Error> {
334                // Since we have no way to extract the underlying `&dyn Display`, we have to
335                // `to_owned()` here
336                self.0 = Some(Value::from_display(&value).to_owned());
337                Ok(())
338            }
339
340            fn visit_null(&mut self) -> Result<(), log::kv::Error> {
341                self.0 = Some(Value::empty().to_owned());
342                Ok(())
343            }
344
345            visit_fn! {
346                visit_u64: u64 => from_u64,
347                visit_i64: i64 => from_i64,
348                visit_u128: u128 => from_u128,
349                visit_i128: i128 => from_i128,
350                visit_f64: f64 => from_f64,
351                visit_bool: bool => from_bool,
352                visit_str: &str => from_str,
353                visit_borrowed_str: &str => from_str,
354                visit_char: char => from_char,
355            }
356        }
357
358        let mut visitor = Visitor(None);
359        value.visit(&mut visitor)?;
360        self.0.push((key, visitor.0.unwrap()));
361        Ok(())
362    }
363}
364
365#[cfg(test)]
366mod tests {
367    use super::*;
368
369    #[test]
370    fn key_partial_eq() {
371        assert_eq!(Key::from_str("a"), Key::__from_static_str("a"));
372        assert_ne!(Key::from_str("a"), Key::__from_static_str("b"));
373    }
374}