Skip to main content

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, pass the value directly, and type for primitives will be captured     |
46//! | `:`      | Pass the value using [`Display`] trait                                             |
47//! | `:?`     | Pass the value using [`Debug`] trait                                               |
48//! | `:sval`  | Pass the value using [`sval::Value`] trait, crate feature `sval` is required       |
49//! | `:serde` | Pass 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//! //                                      ^ Pass 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//! //                                  ^^ Pass 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//! //                              ^^^^^ Pass 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    #[must_use]
104    pub fn as_str(&self) -> &str {
105        self.0.get()
106    }
107}
108
109impl<'a> Key<'a> {
110    #[doc(hidden)]
111    #[must_use]
112    pub fn __from_static_str(key: &'static str) -> Self {
113        Key(RefStr::new_static(key))
114    }
115
116    pub(crate) fn from_str(key: &'a str) -> Self {
117        Key(RefStr::new_ref(key))
118    }
119
120    pub(crate) fn to_owned(&self) -> KeyOwned {
121        KeyOwned(self.0.to_cow_static())
122    }
123
124    #[cfg(test)]
125    pub(crate) fn inner(&self) -> RefStr<'a> {
126        self.0
127    }
128}
129
130impl PartialEq for Key<'_> {
131    fn eq(&self, other: &Self) -> bool {
132        self.as_str() == other.as_str()
133    }
134}
135
136#[derive(Debug, Clone)]
137pub(crate) struct KeyOwned(Cow<'static, str>);
138
139impl KeyOwned {
140    pub(crate) fn as_ref(&self) -> Key<'_> {
141        Key(RefStr::new_ref(&self.0))
142    }
143}
144
145/// Represents a value in a key-value pair.
146pub type Value<'a> = ValueBag<'a>;
147pub(crate) type ValueOwned = OwnedValueBag;
148
149enum KeyValuesInner<'a> {
150    Borrowed(&'a [Pair<'a>]),
151    Owned(&'a [(KeyOwned, ValueOwned)]),
152}
153enum KeyValuesIterInner<'a> {
154    Borrowed(slice::Iter<'a, Pair<'a>>),
155    Owned(slice::Iter<'a, (KeyOwned, ValueOwned)>),
156}
157
158/// Represents a collection of key-value pairs.
159///
160/// ## Examples
161///
162/// ```
163/// use std::fmt::Write;
164///
165/// use spdlog::{
166///     formatter::{Formatter, FormatterContext},
167///     Record, StringBuf,
168/// };
169///
170/// #[derive(Clone)]
171/// struct MyFormatter;
172///
173/// impl Formatter for MyFormatter {
174///     fn format(
175///         &self,
176///         record: &Record,
177///         dest: &mut StringBuf,
178///         ctx: &mut FormatterContext,
179///     ) -> spdlog::Result<()> {
180///         dest.write_str(record.payload())
181///             .map_err(spdlog::Error::FormatRecord)?;
182///         for (key, value) in record.key_values() {
183///             write!(dest, " {}={}", key.as_str(), value).map_err(spdlog::Error::FormatRecord)?;
184///         }
185///         Ok(())
186///     }
187/// }
188/// ```
189pub struct KeyValues<'a>(KeyValuesInner<'a>);
190
191impl<'a> KeyValues<'a> {
192    /// Gets the number of key-value pairs.
193    #[must_use]
194    pub fn len(&self) -> usize {
195        match self.0 {
196            KeyValuesInner::Borrowed(p) => p.len(),
197            KeyValuesInner::Owned(p) => p.len(),
198        }
199    }
200
201    /// Checks if there are no key-value pairs.
202    #[must_use]
203    pub fn is_empty(&self) -> bool {
204        match self.0 {
205            KeyValuesInner::Borrowed(p) => p.is_empty(),
206            KeyValuesInner::Owned(p) => p.is_empty(),
207        }
208    }
209
210    /// Gets the value of the specified key.
211    #[must_use]
212    pub fn get(&self, key: Key) -> Option<Value<'a>> {
213        match self.0 {
214            KeyValuesInner::Borrowed(p) => {
215                p.iter()
216                    .find_map(|(k, v)| if k == &key { Some(v.clone()) } else { None })
217            }
218            KeyValuesInner::Owned(p) => p.iter().find_map(|(k, v)| {
219                if k.as_ref() == key {
220                    Some(v.by_ref())
221                } else {
222                    None
223                }
224            }),
225        }
226    }
227
228    /// Gets an iterator over the key-value pairs.
229    pub fn iter(&self) -> KeyValuesIter<'a> {
230        match &self.0 {
231            KeyValuesInner::Borrowed(p) => KeyValuesIter(KeyValuesIterInner::Borrowed(p.iter())),
232            KeyValuesInner::Owned(p) => KeyValuesIter(KeyValuesIterInner::Owned(p.iter())),
233        }
234    }
235
236    pub(crate) fn with_borrowed(pairs: &'a [Pair<'a>]) -> Self {
237        Self(KeyValuesInner::Borrowed(pairs))
238    }
239
240    pub(crate) fn with_owned(pairs: &'a [(KeyOwned, ValueOwned)]) -> Self {
241        Self(KeyValuesInner::Owned(pairs))
242    }
243
244    pub(crate) fn write_to(&self, dest: &mut impl fmt::Write, brackets: bool) -> fmt::Result {
245        let mut iter = self.iter();
246        let first = iter.next();
247        if let Some((key, value)) = first {
248            if brackets {
249                dest.write_str(" { ")?;
250            }
251
252            // Reduce branch prediction misses for performance
253            // So we manually process the first KV pair
254            dest.write_str(key.as_str())?;
255            dest.write_str("=")?;
256            write!(dest, "{value}")?;
257
258            for (key, value) in iter {
259                dest.write_str(" ")?;
260                dest.write_str(key.as_str())?;
261                dest.write_str("=")?;
262                write!(dest, "{value}")?;
263            }
264
265            if brackets {
266                dest.write_str(" }")?;
267            }
268        }
269        Ok(())
270    }
271}
272
273impl<'a> IntoIterator for KeyValues<'a> {
274    type Item = Pair<'a>;
275    type IntoIter = KeyValuesIter<'a>;
276
277    fn into_iter(self) -> Self::IntoIter {
278        self.iter()
279    }
280}
281
282/// Represents an iterator over key-value pairs.
283#[must_use]
284pub struct KeyValuesIter<'a>(KeyValuesIterInner<'a>);
285
286impl<'a> Iterator for KeyValuesIter<'a> {
287    type Item = Pair<'a>;
288
289    fn next(&mut self) -> Option<Self::Item> {
290        match &mut self.0 {
291            // The 2 clones should be cheap
292            KeyValuesIterInner::Borrowed(iter) => iter.next().map(|(k, v)| (k.clone(), v.clone())),
293            KeyValuesIterInner::Owned(iter) => iter.next().map(|(k, v)| (k.as_ref(), v.by_ref())),
294        }
295    }
296
297    fn size_hint(&self) -> (usize, Option<usize>) {
298        match &self.0 {
299            KeyValuesIterInner::Borrowed(iter) => iter.size_hint(),
300            KeyValuesIterInner::Owned(iter) => iter.size_hint(),
301        }
302    }
303}
304
305pub(crate) type Pair<'a> = (Key<'a>, Value<'a>);
306
307#[cfg(feature = "log")]
308pub(crate) struct LogCrateConverter<'a>(Vec<(log::kv::Key<'a>, ValueOwned)>);
309
310#[cfg(feature = "log")]
311impl<'a> LogCrateConverter<'a> {
312    pub(crate) fn new(capacity: usize) -> Self {
313        Self(Vec::with_capacity(capacity))
314    }
315
316    pub(crate) fn finalize(self) -> Vec<(log::kv::Key<'a>, ValueOwned)> {
317        self.0
318    }
319}
320
321#[cfg(feature = "log")]
322impl<'a> log::kv::VisitSource<'a> for LogCrateConverter<'a> {
323    fn visit_pair(
324        &mut self,
325        key: log::kv::Key<'a>,
326        value: log::kv::Value<'a>,
327    ) -> Result<(), log::kv::Error> {
328        struct Visitor(Option<ValueOwned>);
329
330        macro_rules! visit_fn {
331            ( $($fn:ident: $ty:ty => $from:ident),+$(,)? ) => {
332                $(fn $fn(&mut self, value: $ty) -> Result<(), log::kv::Error> {
333                    self.0 = Some(Value::$from(value).to_owned());
334                    Ok(())
335                })+
336            };
337        }
338
339        impl log::kv::VisitValue<'_> for Visitor {
340            fn visit_any(&mut self, value: log::kv::Value) -> Result<(), log::kv::Error> {
341                // Since we have no way to extract the underlying `&dyn Display`, we have to
342                // `to_owned()` here
343                self.0 = Some(Value::from_display(&value).to_owned());
344                Ok(())
345            }
346
347            fn visit_null(&mut self) -> Result<(), log::kv::Error> {
348                self.0 = Some(Value::empty().to_owned());
349                Ok(())
350            }
351
352            visit_fn! {
353                visit_u64: u64 => from_u64,
354                visit_i64: i64 => from_i64,
355                visit_u128: u128 => from_u128,
356                visit_i128: i128 => from_i128,
357                visit_f64: f64 => from_f64,
358                visit_bool: bool => from_bool,
359                visit_str: &str => from_str,
360                visit_borrowed_str: &str => from_str,
361                visit_char: char => from_char,
362            }
363        }
364
365        let mut visitor = Visitor(None);
366        value.visit(&mut visitor)?;
367        self.0.push((key, visitor.0.unwrap()));
368        Ok(())
369    }
370}
371
372#[cfg(test)]
373mod tests {
374    use super::*;
375
376    #[test]
377    fn key_partial_eq() {
378        assert_eq!(Key::from_str("a"), Key::__from_static_str("a"));
379        assert_ne!(Key::from_str("a"), Key::__from_static_str("b"));
380    }
381}