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}