1- Feature Name: `structured_logging`
2- Start Date: 2019-03-11
3- RFC PR: [log/rfcs#0296](https://github.com/rust-lang-nursery/log/pull/296)
4
5# Summary
6[summary]: #summary
7
8Add support for structured logging to the `log` crate in both `std` and `no_std` environments, allowing log records to carry typed data beyond a textual message. This document serves as an introduction to what structured logging is all about, and as an RFC for an implementation in the `log` crate.
9
10`log` will provide an API for capturing structured data that offloads complex serialization to de-facto standards in the ecosystem, but avoids integrating them too tightly, or forcing any specific framework on consumers.
11
12The API is heavily inspired by `slog` and `tokio-trace`.
13
14> NOTE: Code in this RFC uses recent language features like `impl Trait`, but can be implemented without them.
15
16# Contents
17- [Motivation](#motivation)
18  - [What is structured logging?](#what-is-structured-logging)
19  - [Why do we need structured logging in `log`?](#why-do-we-need-structured-logging-in-log)
20- [Guide-level explanation](#guide-level-explanation)
21  - [Logging structured key-value pairs](#logging-structured-key-value-pairs)
22  - [Supporting key-value pairs in `Log` implementations](#supporting-key-value-pairs-in-log-implementations)
23  - [Integrating log frameworks with `log`](#integrating-log-frameworks-with-log)
24  - [How producers and consumers of structured values interact](#how-producers-and-consumers-of-structured-values-interact)
25- [Reference-level explanation](#reference-level-explanation)
26  - [Design considerations](#design-considerations)
27  - [Cargo features](#cargo-features)
28  - [A complete key-values API](#a-complete-key-values-api)
29    - [`Error`](#error)
30    - [`Value`](#value)
31    - [`ToValue`](#tovalue)
32    - [`Key`](#key)
33    - [`ToKey`](#tokey)
34    - [`Source`](#source)
35    - [`Visitor`](#visitor)
36    - [`Record` and `RecordBuilder`](#record-and-recordbuilder)
37  - [A minimal key-values API](#a-minimal-key-values-api)
38  - [The `log!` macros](#the-log-macros)
39- [Drawbacks, rationale, and alternatives](#drawbacks-rationale-and-alternatives)
40- [Prior art](#prior-art)
41- [Unresolved questions](#unresolved-questions)
42
43# Motivation
44[motivation]: #motivation
45
46## What is structured logging?
47
48Information in log records can be traditionally captured as a blob of text, including a level, a message, and maybe a few other pieces of metadata. There's a lot of potentially valuable information we throw away when we format log records this way. Arbitrary textual representations often result in output that is neither easy for humans to read, nor for machines to parse.
49
50Structured logs can retain their original structure in a machine-readable format. They can be changed programmatically within a logging pipeline before reaching their destination. Once there, they can be analyzed using common database tools.
51
52As an example of structured logging, a textual record like this:
53
54```
55[INF 2018-09-27T09:32:03Z basic] [service: database, correlation: 123] Operation completed successfully in 18ms
56```
57
58could be represented as a structured record like this:
59
60```json
61{
62    "ts": 1538040723000,
63    "lvl": "INFO",
64    "msg": "Operation completed successfully in 18ms",
65    "module": "basic",
66    "service": "database",
67    "correlation": 123,
68    "took": 18
69}
70```
71
72When log records are kept in a structured format like this, potentially interesting queries like _what are all records where the correlation is 123?_, or _how many errors were there in the last hour?_ can be computed efficiently.
73
74Even when logging to a console for immediate consumption, the human-readable message can be presented better when it's not trying to include ambient metadata inline:
75
76```
77[INF 2018-09-27T09:32:03Z] Operation completed successfully in 18ms
78module: "basic"
79service: "database"
80correlation: 123
81took: 18
82```
83
84Having a way to capture additional metadata is good for human-centric formats. Having a way to retain the structure of that metadata is good for machine-centric formats.
85
86## Why do we need structured logging in `log`?
87
88Why add structured logging support to the `log` crate when libraries like `slog` already exist and support it? `log` needs to support structured logging to make the experience of using `slog` and other logging tools in the Rust ecosystem more compatible.
89
90On the surface there doesn't seem to be a lot of difference between `log` and `slog`, so why not just deprecate one in favor of the other? Conceptually, `log` and `slog` are different libraries that fill different roles, even if there's some overlap.
91
92`slog` is a logging _framework_. It offers all the fundamental tools needed out-of-the-box to capture log records, define and implement the pieces of a logging pipeline, and pass them through that pipeline to an eventual destination. It has conventions and trade-offs baked into the design of its API. Loggers are treated explicitly as values in data structures and as arguments, and callers can control whether to pass owned or borrowed data.
93
94`log` is a logging _facade_. It's only concerned with a standard, minimal API for capturing log records, and surfacing those records to some consumer. The tools provided by `log` are only those that are fundamental to the operation of the `log!` macro. From `log`'s point of view, a logging framework like `slog` is a black-box implementation of the `Log` trait. In this role, the `Log` trait can act as a common entry-point for capturing log records. That means the `Record` type can act as a common container for describing a log record. `log` has its own set of trade-offs baked into the design of its API. The `log!` macro assumes a single, global entry-point, and all data in a log record is borrowed from the call-site.
95
96A healthy logging ecosystem needs both `log` and frameworks like `slog`. As a standard API, `log` can support a diverse but cohesive ecosystem of logging tools in Rust by acting as the glue between libraries, frameworks, and applications. A lot of libraries already depend on it. In order to really fulfill this role though, `log` needs to support structured logging so that libraries and their consumers can take advantage of it in a framework-agnostic way.
97
98# Guide-level explanation
99[guide-level-explanation]: #guide-level-explanation
100
101This section introduces `log`'s structured logging API through a tour of how structured records can be captured and consumed.
102
103## Logging structured key-value pairs
104
105Structured logging is supported in `log` by allowing typed key-value pairs to be associated with a log record. This support isn't surfaced in the `log!` macros initially, so you'll need to use a framework like `slog` or `tokio-trace` with `log` integration, or an alternative implementation of the `log!` macros to capture structured key-value pairs.
106
107### What can be captured as a structured value?
108
109A value can be captured as a structured value in a log record if it implements the `ToValue` trait:
110
111```rust
112pub trait ToValue {
113    fn to_value<'v>(&'v self) -> Value<'v>;
114}
115```
116
117where `Value` is a special container for structured data:
118
119```rust
120pub struct Value<'v>(_);
121
122// A value can always be debugged
123impl<'v> Debug for Value<'v> {
124    ..
125}
126```
127
128We'll look at `Value` in more detail later. For now, we can think of it as a container that normalizes capturing and emitting the structure of values.
129
130Initially, that means a fixed set of primitive types from the standard library:
131
132- Standard formats: `Arguments`
133- Primitives: `bool`, `char`
134- Unsigned integers: `u8`, `u16`, `u32`, `u64`, `u128`
135- Signed integers: `i8`, `i16`, `i32`, `i64`, `i128`
136- Strings: `&str`, `String`
137- Paths: `&Path`, `PathBuf`
138- Special types: `Option<T>`, `&T`, and `()`.
139
140Each of these types implements `ToValue` in a way that opaquely retains some notion of their underlying structure. Using `u8` as an example:
141
142```rust
143impl ToValue for u8 {
144    fn to_value(&self) -> Value {
145        Value::from_any(self, |from, v| from.u64(*v as u64))
146    }
147}
148```
149
150The `Value::from_any` method accepts any type, `&T`, and an ad-hoc function that tells the `Value` what its structure is:
151
152```rust
153impl<'v> Value<'v> {
154    pub fn from_any<T>(v: &'v T, from: fn(FromAny, &T) -> Result<(), Error>) -> Self {
155        ..
156    }
157}
158
159pub struct FromAny(_);
160
161impl FromAny {
162    pub fn debug(v: impl Debug) -> Result<(), Error> {
163        ..
164    }
165
166    // Not publicly exposed. Used by internal implementations
167    fn u64(v: u64) -> Result<(), Error> {
168        ..
169    }
170}
171```
172
173Only being able to log primitive types from the standard library is a bit limiting though. What if `correlation_id` is a `uuid::Uuid`, and `user` is a struct, `User`, with its own fields?
174
175#### Implementing `ToValue` for a simple value
176
177A newtype structure like `uuid::Uuid` could implement the `ToValue` trait directly in terms of some underlying value that already implements `ToValue`:
178
179```rust
180impl ToValue for Uuid {
181    fn to_value(&self) -> Value {
182        self.as_u128().to_value()
183    }
184}
185```
186
187Alternatively, `uuid::Uuid` could provide a nicer implementation using its `Debug` implementation:
188
189```rust
190impl ToValue for Uuid {
191    fn to_value(&self) -> Value {
192        Value::from_debug(self)
193    }
194}
195```
196
197Finally, it could use the `Debug` implementation of its hyphenated format:
198
199```rust
200impl ToValue for Uuid {
201    fn to_value(&self) -> Value {
202        Value::from_any(self, |from, uuid| from.debug(uuid.to_hyphenated()))
203    }
204}
205```
206
207There's some subtlety in this last implementation. The actual value whose structure is captured is not the `&'v Uuid`, it's the owned `ToHyphenated<'v>`. This is why `Value::from_any` uses a separate function for capturing the structure of its values that doesn't depend on the lifetime of the given `&'v T`. It lets us capture a borrowed `Uuid` with the right lifetime `'v`, but materialize an owned `ToHyphenated` with the structure we want.
208
209#### Implementing `ToValue` for a complex value
210
211A structure like `User` is a bit different from a newtype like `uuid::Uuid`. It could be represented using `Debug`, but then the contents of its fields would be lost in an opaque and unstructured string. It would be better represented as a map of key-value pairs. However, complex values like maps and sequences aren't directly supported in `log`. They're offloaded to serialization frameworks like `serde` and `sval` that are capable of handling them effectively.
212
213`serde` and `sval` have direct two-way integration with `log`'s `Value` type through optional Cargo features. Let's use `sval` as an example. It's a serialization framework that's built specifically for structured logging. Adding the `kv_sval` feature to `log` will enable it:
214
215```toml
216[dependencies.log]
217features = ["kv_sval"]
218```
219
220Complex structures that derive `sval`'s `Value` trait can then implement `log`'s `ToValue` trait in terms of `sval`:
221
222```rust
223#[derive(Debug, Value)]
224struct User {
225    name: String,
226}
227
228impl ToValue for User {
229    fn to_value(&self) -> Value {
230        Value::from_sval(self)
231    }
232}
233```
234
235In this way, the underlying structure of a `User`, described as a map by `sval`, is retained when converting it into a `Value`. Using `serde` instead of `sval` is a similar story:
236
237```toml
238[dependencies.log]
239features = ["kv_serde"]
240```
241
242```rust
243#[derive(Debug, Serialize)]
244struct User {
245    name: String,
246}
247
248impl ToValue for User {
249    fn to_value(&self) -> Value {
250        Value::from_serde(self)
251    }
252}
253```
254
255## Supporting key-value pairs in `Log` implementations
256
257Capturing structured logs is only half the story. Implementors of the `Log` trait also need to be able to work with any key-value pairs associated with a log record. Key-value pairs are accessible on a log record through the `Record::key_values` method:
258
259```rust
260impl Record {
261    pub fn key_values(&self) -> impl Source;
262}
263```
264
265where `Source` is a trait for iterating over the individual key-value pairs:
266
267```rust
268pub trait Source {
269    // Get the value for a given key
270    fn get<'kvs, Q>(&'kvs self, key: Q) -> Option<Value<'kvs>>
271    where
272        Q: ToKey,
273    {
274        ..
275    }
276
277    // Run a function for each key-value pair
278    fn for_each<F>(self, f: F) -> Result<(), Error>
279    where
280        Self: Sized,
281        F: FnMut(Key, Value),
282    {
283        ..
284    }
285
286    // Run a function for each key-value pair
287    fn try_for_each<F, E>(self, f: F) -> Result<(), Error>
288    where
289        Self: Sized,
290        F: FnMut(Key, Value) -> Result<(), E>,
291        E: Into<Error>,
292    {
293        ..
294    }
295
296    // Serialize the source as a map of key-value pairs
297    fn as_map(self) -> AsMap<Self>
298    where
299        Self: Sized,
300    {
301        ..
302    }
303
304    // Serialize the source as a sequence of key-value tuples
305    fn as_seq(self) -> AsSeq<Self>
306    where
307        Self: Sized,
308    {
309        ..
310    }
311
312    // Other methods we'll look at later
313}
314```
315
316### Writing key-value pairs as text
317
318To demonstrate how to work with a `Source`, let's take the textual log format from before:
319
320```
321[INF 2018-09-27T09:32:03Z] Operation completed successfully in 18ms
322module: "basic"
323service: "database"
324correlation: 123
325took: 18
326```
327
328Each key-value pair, shown as a `$key: $value` line, can be formatted from the `Source` on a record using the `std::fmt` machinery:
329
330```rust
331use log::key_values::Source;
332
333fn log_record(w: impl Write, r: &Record) -> io::Result<()> {
334    // Write the first line of the log record
335    ..
336
337    // Write each key-value pair on a new line
338    record
339        .key_values()
340        .for_each(|k, v| writeln!("{}: {}", k, v))?;
341
342    Ok(())
343}
344```
345
346In the above example, the `Source::for_each` method iterates over each key-value pair in the `Source` and writes them to the output stream.
347
348### Writing key-value pairs as JSON
349
350Let's look at a structured example. Take the following JSON map:
351
352```json
353{
354    "ts": 1538040723000,
355    "lvl": "INFO",
356    "msg": "Operation completed successfully in 18ms",
357    "module": "basic",
358    "service": "database",
359    "correlation": 123,
360    "took": 18
361}
362```
363
364A `Source` can be serialized as a map using a serialization framework like `serde` or `sval`. Using `serde` for this example requires the `kv_serde` feature:
365
366```toml
367[dependencies.log]
368features = ["kv_serde"]
369```
370
371Defining a serializable structure based on a log record for the previous JSON map could then be done using `serde_derive`, and then written using `serde_json`:
372
373```rust
374use log::key_values::Source;
375
376fn log_record(w: impl Write, r: &Record) -> io::Result<()> {
377    let r = SerializeRecord {
378        lvl: r.level(),
379        ts: epoch_millis(),
380        msg: r.args().to_string(),
381        kvs: r.key_values().as_map(),
382    };
383
384    serde_json::to_writer(w, &r)?;
385
386    Ok(())
387}
388
389#[derive(Serialize)]
390struct SerializeRecord<KVS> {
391    lvl: Level,
392    ts: u64,
393    msg: String,
394    #[serde(flatten)]
395    kvs: KVS,
396}
397```
398
399This time, instead of using the `Source::for_each` method, we use the `Source::as_map` method to get an adapter that implements `serde::Serialize` by serializing each key-value pair as an entry in a `serde` map.
400
401## Integrating log frameworks with `log`
402
403The `Source` trait we saw previously describes some container for structured key-value pairs that can be iterated or serialized. Other log frameworks that want to integrate with the `log` crate should build `Record`s that contain some implementation of `Source` based on their own structured logging.
404
405The previous section demonstrated some of the methods available on `Source` like `Source::try_for_each` and `Source::as_map`. Both of those methods are provided on top of a required lower-level `Source::visit` method, which looks something like this:
406
407```rust
408trait Source {
409    fn visit<'kvs>(&'kvs self, visitor: &mut impl Visitor<'kvs>) -> Result<(), Error>;
410
411    // Provided methods
412}
413```
414
415where `Visitor` is another trait that accepts individual key-value pairs:
416
417```rust
418trait Visitor<'kvs> {
419    fn visit_pair(&mut self, k: Key<'kvs>, v: Value<'kvs>) -> Result<(), Error>;
420}
421```
422
423where `Key` is a container for a string and `Value` is the container for structured data we saw previously. The lifetime `'kvs` is threaded from the original borrow of the `Source` through to the `Key`s and `Value`s that a `Visitor` sees. That allows visitors to work with key-value pairs that can live for longer than a single call to `Visitor::visit_pair`.
424
425Let's implement a `Source`. As an example, let's say our log framework captures its key-value pairs in a `BTreeMap`:
426
427```rust
428struct KeyValues {
429    data: BTreeMap<String, serde_json::Value>,
430}
431```
432
433The `Source` trait could be implemented for `KeyValues` like this:
434
435```rust
436use log::key_values::source::{self, Source};
437
438impl Source for KeyValues {
439    fn visit<'kvs>(&'kvs self, visitor: &mut impl source::Visitor<'kvs>) -> Result<(), source::Error> {
440        for (k, v) in self.data {
441            visitor.visit_pair(source::Key::from_str(k), source::Value::from_serde(v))
442        }
443    }
444}
445```
446
447The `Key::from_str` method accepts any `T: Borrow<str>`. The `Value::from_serde` accepts any `T: serde::Serialize + Debug`.
448
449A `Source` doesn't have to just contain key-value pairs directly like `BTreeMap<String, serde_json::Value>` though. It could act like an adapter that changes its pairs before emitting them, like we have for iterators in the standard library. As another example, the following `Source` doesn't store any key-value pairs of its own, instead it will sort and de-duplicate pairs read from another source by first reading them into a map before forwarding them on:
450
451```rust
452use log::key_values::source::{self, Source, Visitor};
453
454pub struct SortRetainLast<KVS>(KVS);
455
456impl<KVS> Source for SortRetainLast<KVS>
457where
458    KVS: Source,
459{
460    fn visit<'kvs>(&'kvs self, visitor: &mut impl source::Visitor<'kvs>) -> Result<(), source::Error> {
461        // `Seen` is a visitor that will capture key-value pairs
462        // in a `BTreeMap`. We use it internally to sort and de-duplicate
463        // the key-value pairs that `SortRetainLast` is wrapping.
464        struct Seen<'kvs>(BTreeMap<source::Key<'kvs>, source::Value<'kvs>>);
465
466        impl<'kvs> Visitor<'kvs> for Seen<'kvs> {
467            fn visit_pair<'vis>(&'vis mut self, k: source::Key<'kvs>, v: source::Value<'kvs>) -> Result<(), source::Error> {
468                self.0.insert(k, v);
469
470                Ok(())
471            }
472        }
473
474        // Visit the inner source and collect its key-value pairs into `seen`
475        let mut seen = Seen(BTreeMap::new());
476        self.0.visit(&mut seen)?;
477
478        // Iterate through the seen key-value pairs in order
479        // and pass them to the `visitor`.
480        for (k, v) in seen.0 {
481            visitor.visit_pair(k, v)?;
482        }
483
484        Ok(())
485    }
486}
487```
488
489## How producers and consumers of structured values interact
490
491The previous sections demonstrated some of the APIs for capturing and consuming structured data on log records. The `ToValue` trait and `Value::from_any` methods capture values into a common `Value` container. The `Source` trait allows these `Value`s to be consumed using `std::fmt`, `sval` or `serde`.
492
493Values captured from any one supported framework can be represented by any other. That means a value can be captured in terms of `sval` and consumed in terms of `serde`, with its underlying structure retained.
494
495# Reference-level explanation
496[reference-level-explanation]: #reference-level-explanation
497
498This section details the nuts-and-bolts of the structured logging API.
499
500## Design considerations
501
502### Don't break anything
503
504Allow structured logging to be added in the current `0.4.x` series of `log`. This gives us the option of including structured logging in an `0.4.x` release, or bumping the minor crate version without introducing any actual breaking changes.
505
506### Don't create a public dependency
507
508Don't create a new serialization API that requires `log` to become a public dependency of any library that wants their data types to be logged. Logging is a truly cross-cutting concern, so if `log` was a public dependency it would become even more difficult to develop its API without compounding churn. Any traits that are expected to be publicly implemented should be narrowly scoped to make backwards compatibility easier.
509
510### Support arbitrary producers and arbitrary consumers
511
512Provide an API that's suitable for two independent logging frameworks to integrate through if they want. Producers of structured data and consumers of structured data should be able to use different serialization frameworks opaquely and still get good results. As an example, a producer of a `log::Record` should be able to log a map that implements `sval::Value`, and the implementor of the receiving `Log` trait should be able to format that map using `serde::Serialize`.
513
514### Remain object safe
515
516`log` is already designed to be object-safe so this new structured logging API needs to be object-safe too.
517
518### Enable the next round of `log` development
519
520Once structured logging is available, there will be a lot of new ways to hold `log` and new concepts to try out, such as a procedural-macro-based `log!` implementation and explicit treatment of `std::error::Error`. The APIs introduced by this RFC should enable others to build these features in external crates, and look at integrating that work back into `log` in the future.
521
522## Cargo features
523
524Structured logging will be supported in either `std` or `no_std` contexts by default.
525
526```toml
527[features]
528std = []
529kv_sval = ["sval"]
530kv_serde = ["std", "serde", "erased-serde", "sval"]
531```
532
533### `kv_sval` and `kv_serde`
534
535Using default features, implementors of the `Log` trait will be able to format structured data (in the form of `Value`s) using the `std::fmt` machinery.
536
537`sval` is a new serialization framework that's specifically designed with structured logging in mind. It's `no_std` and object-safe, but isn't stable and requires `rustc` `1.31.0`. Using the `kv_sval` feature, any `Value` will also implement `sval::Value` so its underlying structure will be visible to consumers of structured data using `sval::Stream`s.
538
539`serde` is the de-facto general-purpose serialization framework for Rust. It's widely supported and stable, but requires some significant runtime machinery in order to be object-safe. Using the `kv_serde` feature, any `Value` will also implement `serde::Serialize` so its underlying structure will be visible to consumers of structured data using `serde::Serializer`s.
540
541## A complete key-values API
542
543The following section details the public API for structured values in `log`, along with possible future extensions. Actual implementation details are excluded for brevity unless they're particularly noteworthy. See the original comment on the [RFC issue](https://github.com/rust-lang-nursery/log/pull/296#issue-222687727) for a reference implementation.
544
545### `Error`
546
547Just about the only things you can do with a structured value are format it or serialize it. Serialization and writing might fail, so to allow errors to get carried back to callers there needs to be a general error type that they can early return with:
548
549```rust
550pub struct Error(Inner);
551
552impl Error {
553    pub fn msg(msg: &'static str) -> Self {
554        ..
555    }
556}
557
558impl Debug for Error {
559    ..
560}
561
562impl Display for Error {
563    ..
564}
565
566impl From<fmt::Error> for Error {
567    ..
568}
569
570impl From<Error> for fmt::Error {
571    ..
572}
573
574#[cfg(feature = "kv_sval")]
575mod sval_support {
576    impl From<sval::Error> for Error {
577        ..
578    }
579
580    impl Error {
581        pub fn into_sval(self) -> sval::Error {
582            ..
583        }
584    }
585}
586
587#[cfg(feature = "kv_serde")]
588mod serde_support {
589    impl Error {
590        pub fn into_serde<E>(self) -> E
591        where
592            E: serde::ser::Error,
593        {
594            ..
595        }
596    }
597}
598
599#[cfg(feature = "std")]
600mod std_support {
601    impl Error {
602        pub fn custom(err: impl fmt::Display) -> Self {
603            ..
604        }
605    }
606
607    impl From<io::Error> for Error {
608        ..
609    }
610
611    impl From<Error> for io::Error {
612        ..
613    }
614
615    impl error::Error for Error {
616        ..
617    }
618}
619```
620
621There's no really universal way to handle errors in a logging pipeline. Knowing that some error occurred, and knowing where, should be enough for implementations of `Log` to decide how to handle it. The `Error` type doesn't try to be a general-purpose error management tool, it tries to make it easy to early-return with other errors.
622
623To make it possible to carry any arbitrary `S::Error` type, where we don't know how long the value can live for and whether it's `Send` or `Sync`, without extra work, the `Error` type does not attempt to store the error value itself. It just converts it into a `String`.
624
625### `Value`
626
627A `Value` is an erased container for some type whose structure can be visited, with a potentially short-lived lifetime:
628
629```rust
630pub struct Value<'v>(_);
631
632impl<'v> Value<'v> {
633    pub fn from_any<T>(v: &'v T, from: FromAnyFn<T>) -> Self {
634        ..
635    }
636
637    pub fn from_debug(v: &'v impl Debug) -> Self {
638        Self::from_any(v, |from, v| from.debug(v))
639    }
640
641    #[cfg(feature = "kv_sval")]
642    pub fn from_sval(v: &'v (impl sval::Value + Debug)) -> Self {
643        Self::from_any(v, |from, v| from.sval(v))
644    }
645
646    #[cfg(feature = "kv_serde")]
647    pub fn from_serde(v: &'v (impl serde::Serialize + Debug)) -> Self {
648        Self::from_any(v, |from, v| from.serde(v))
649    }
650}
651
652impl<'v> Debug for Value<'v> {
653    ..
654}
655
656impl<'v> Display for Value<'v> {
657    ..
658}
659
660#[cfg(feature = "kv_sval")]
661impl<'v> sval::Value for Value<'v> {
662    ..
663}
664
665#[cfg(feature = "kv_serde")]
666impl<'v> serde::Serialize for Value<'v> {
667    ..
668}
669
670type FromAnyFn<T> = fn(FromAny, &T) -> Result<(), Error>;
671```
672
673The `FromAny` type is like a visitor that accepts values with a particular structure, but doesn't require those values satisfy any lifetime constraints:
674
675```rust
676pub struct FromAny<'a>(_);
677
678impl<'a> FromAny<'a> {
679    pub fn debug(self, v: impl Debug) -> Result<(), Error> {
680        ..
681    }
682
683    #[cfg(feature = "kv_sval")]
684    pub fn sval(self, v: impl sval::Value + Debug) -> Result<(), Error> {
685        ..
686    }
687
688    #[cfg(feature = "kv_serde")]
689    pub fn serde(self, v: impl serde::Serialize + Debug) -> Result<(), Error> {
690        ..
691    }
692
693    fn value(self, v: Value) -> Result<(), Error> {
694        ..
695    }
696
697    fn u64(self, v: u64) -> Result<(), Error> {
698        ..
699    }
700
701    fn u128(self, v: u128) -> Result<(), Error> {
702        ..
703    }
704
705    fn i64(self, v: i64) -> Result<(), Error> {
706        ..
707    }
708
709    fn i128(self, v: i128) -> Result<(), Error> {
710        ..
711    }
712
713    fn f64(self, v: f64) -> Result<(), Error> {
714        ..
715    }
716
717    fn bool(self, v: bool) -> Result<(), Error> {
718        ..
719    }
720
721    fn char(self, v: char) -> Result<(), Error> {
722        ..
723    }
724
725    fn none(self) -> Result<(), Error> {
726        ..
727    }
728
729    fn str(self, v: &str) -> Result<(), Error> {
730        ..
731    }
732}
733```
734
735#### Erasing values in `Value::from_any`
736
737Internally, the `Value` type uses similar machinery to `std::fmt::Argument` for pairing an erased incoming type with a function for operating on it:
738
739```rust
740pub struct Value<'v>(Inner<'v>);
741
742impl<'v> Value<'v> {
743    pub fn from_any<T>(v: &'v T, from: FromAnyFn<T>) -> Self {
744        Value(Inner::new(v, from))
745    }
746}
747
748struct Void {
749    _priv: (),
750    _oibit_remover: PhantomData<*mut dyn Fn()>,
751}
752
753#[derive(Clone, Copy)]
754struct Inner<'a> {
755    data: &'a Void,
756    from: FromAnyFn<Void>,
757}
758
759type FromAnyFn<T> = fn(FromAny, &T) -> Result<(), Error>;
760
761impl<'a> Inner<'a> {
762    fn new<T>(data: &'a T, from: FromAnyFn<T>) -> Self {
763        unsafe {
764            Inner {
765                data: mem::transmute::<&'a T, &'a Void>(data),
766                from: mem::transmute::<FromAnyFn<T>, FromAnyFn<Void>>(from),
767            }
768        }
769    }
770
771    // Backend is an internal trait that bridges supported serialization frameworks
772    fn visit(&self, backend: &mut dyn Backend) -> Result<(), Error> {
773        (self.from)(FromAny(backend), self.data)
774    }
775}
776```
777
778The benefit of the `Value::from_any` approach over a dedicated trait is that `Value::from_any` doesn't make any constraints on the incoming `&'v T` besides needing to satisfy the `'v` lifetime. That makes it possible to materialize newtypes from the borrowed `&'v T` to satisfy serialization constraints for cases where the caller doesn't own `T` and can't implement traits on it.
779
780#### Ownership
781
782The `Value` type borrows from its inner value.
783
784#### Thread-safety
785
786The `Value` type doesn't try to guarantee that values are `Send` or `Sync`, and doesn't offer any way of retaining that information when erasing.
787
788### `ToValue`
789
790The `ToValue` trait represents a type that can be converted into a `Value`:
791
792```rust
793pub trait ToValue {
794    fn to_value(&self) -> Value;
795}
796```
797
798It's the generic trait bound that macros capturing structured values can require.
799
800#### Object safety
801
802The `ToValue` trait is object-safe.
803
804#### Implementors
805
806`ToValue` is implemented for fundamental primitive types:
807
808```rust
809impl<'v> ToValue for Value<'v> {
810    fn to_value(&self) -> Value {
811        Value(self.0)
812    }
813}
814
815impl<'a, T> ToValue for &'a T
816where
817    T: ToValue,
818{
819    fn to_value(&self) -> Value {
820        (**self).to_value()
821    }
822}
823
824impl ToValue for () {
825    fn to_value(&self) -> Value {
826        Value::from_any(self, |from, _| from.none())
827    }
828}
829
830impl ToValue for u8 {
831    fn to_value(&self) -> Value {
832        Value::from_any(self, |from, v| from.u64(*v as u64))
833    }
834}
835
836impl ToValue for u16 {
837    fn to_value(&self) -> Value {
838        Value::from_any(self, |from, v| from.u64(*v as u64))
839    }
840}
841
842impl ToValue for u32 {
843    fn to_value(&self) -> Value {
844        Value::from_any(self, |from, v| from.u64(*v as u64))
845    }
846}
847
848impl ToValue for u64 {
849    fn to_value(&self) -> Value {
850        Value::from_any(self, |from, v| from.u64(*v))
851    }
852}
853
854impl ToValue for u128 {
855    fn to_value(&self) -> Value {
856        Value::from_any(self, |from, v| from.u128(*v))
857    }
858}
859
860impl ToValue for i8 {
861    fn to_value(&self) -> Value {
862        Value::from_any(self, |from, v| from.i64(*v as i64))
863    }
864}
865
866impl ToValue for i16 {
867    fn to_value(&self) -> Value {
868        Value::from_any(self, |from, v| from.i64(*v as i64))
869    }
870}
871
872impl ToValue for i32 {
873    fn to_value(&self) -> Value {
874        Value::from_any(self, |from, v| from.i64(*v as i64))
875    }
876}
877
878impl ToValue for i64 {
879    fn to_value(&self) -> Value {
880        Value::from_any(self, |from, v| from.i64(*v))
881    }
882}
883
884impl ToValue for i128 {
885    fn to_value(&self) -> Value {
886        Value::from_any(self, |from, v| from.i128(*v))
887    }
888}
889
890impl ToValue for f32 {
891    fn to_value(&self) -> Value {
892        Value::from_any(self, |from, v| from.f64(*v as f64))
893    }
894}
895
896impl ToValue for f64 {
897    fn to_value(&self) -> Value {
898        Value::from_any(self, |from, v| from.f64(*v))
899    }
900}
901
902impl ToValue for bool {
903    fn to_value(&self) -> Value {
904        Value::from_any(self, |from, v| from.bool(*v))
905    }
906}
907
908impl ToValue for char {
909    fn to_value(&self) -> Value {
910        Value::from_any(self, |from, v| from.char(*v))
911    }
912}
913
914impl<T> ToValue for Option<T>
915where
916    T: ToValue,
917{
918    fn to_value(&self) -> Value {
919        Value::from_any(self, |from, v| match v {
920            Some(ref v) => from.value(v.to_value()),
921            None => from.none(),
922        })
923    }
924}
925
926impl<'a> ToValue for &'a str {
927    fn to_value(&self) -> Value {
928        Value::from_any(self, |from, v| from.str(*v))
929    }
930}
931
932impl<'a> ToValue for &'a Arguments {
933    fn to_value(&self) -> Value {
934        Value::from_any(self, |from, v| from.debug(*v))
935    }
936}
937```
938
939When `std` is available, `ToValue` is implemented for additional types:
940
941```rust
942impl<T: ?Sized> ToValue for Box<T> where T: ToValue {
943    fn to_value(&self) -> Value {
944        (**self).to_value()
945    }
946}
947
948impl<T: ?Sized> ToValue for Arc<KVS> where KVS: ToValue  {
949    fn to_value(&self) -> Value {
950        (**self).to_value()
951    }
952}
953
954impl<T: ?Sized> ToValue for Rc<KVS> where KVS: ToValue  {
955    fn to_value(&self) -> Value {
956        (**self).to_value()
957    }
958}
959
960impl ToValue for String {
961    fn to_value(&self) -> Value {
962        Value::from_any(self, |from, v| from.str(*v))
963    }
964}
965
966impl<'a> ToValue for &'a Path {
967    fn to_value(&self) -> Value {
968        Value::from_any(self, |from, v| from.debug(*v))
969    }
970}
971
972impl ToValue for PathBuf {
973    fn to_value(&self) -> Value {
974        Value::from_any(self, |from, v| from.debug(*v))
975    }
976}
977```
978
979Other implementations for `std` types can be added in the same fashion.
980
981### `Key`
982
983A `Key` is a short-lived structure that can be represented as a UTF-8 string:
984
985```rust
986pub struct Key<'k>(_);
987
988impl<'k> Key<'k> {
989    pub fn from_str(key: &'k (impl Borrow<str> + ?Sized)) -> Self {
990        ..
991    }
992
993    pub fn as_str(&self) -> &str {
994        ..
995    }
996}
997
998impl<'k> AsRef<str> for Key<'k> {
999    ..
1000}
1001
1002impl<'k> Borrow<str> for Key<'k> {
1003    ..
1004}
1005
1006impl<'k> From<&'k str> for Key<'k> {
1007    ..
1008}
1009
1010impl<'k> PartialEq for Key<'k> {
1011    ..
1012}
1013
1014impl<'k> Eq for Key<'k> {}
1015
1016impl<'k> PartialOrd for Key<'k> {
1017    ..
1018}
1019
1020impl<'k> Ord for Key<'k> {
1021    ..
1022}
1023
1024impl<'k> Hash for Key<'k> {
1025    ..
1026}
1027
1028impl<'k> Debug for Key<'k> {
1029    ..
1030}
1031
1032impl<'k> Display for Key<'k> {
1033    ..
1034}
1035```
1036
1037When `std` is available, a `Key` can also contain an owned `String`:
1038
1039```rust
1040impl<'k> Key<'k> {
1041    pub fn from_owned(key: impl Into<String>) -> Self {
1042        ..
1043    }
1044}
1045
1046impl ToKey for String {
1047    fn to_key(&self) -> Key {
1048        Key::from_str(self, None)
1049    }
1050}
1051
1052impl<'k> From<String> for Key<'k> {
1053    ..
1054}
1055```
1056
1057When the `kv_sval` or `kv_serde` features are enabled, a `Key` can be serialized using `sval` or `serde`:
1058
1059```rust
1060#[cfg(feature = "kv_sval")]
1061mod sval_support {
1062    impl<'k> sval::Value for Key<'k> {
1063        ..
1064    }
1065}
1066
1067#[cfg(feature = "kv_serde")]
1068mod serde_support {
1069    impl<'k> Serialize for Key<'k> {
1070        ..
1071    }
1072}
1073```
1074
1075Other standard implementations could be added for any `K: Borrow<str>` in the same fashion.
1076
1077#### Ownership
1078
1079The `Key` type can either borrow or own its inner value.
1080
1081#### Thread-safety
1082
1083The `Key` type is probably `Send` and `Sync`, but that's not guaranteed.
1084
1085#### Extensibility: Adding an index to keys
1086
1087The `Key` type could be extended to hold an optional index into a source. This could be used to retrieve a specific key-value pair more efficiently than scanning. There's some subtlety to consider though when the sources of keys are combined in various ways that might invalidate a previous index.
1088
1089### `ToKey`
1090
1091The `ToKey` trait represents a type that can be converted into a `Key`:
1092
1093```rust
1094pub trait ToKey {
1095    fn to_key(&self) -> Key;
1096}
1097```
1098
1099#### Object safety
1100
1101The `ToKey` trait is object-safe.
1102
1103#### Implementors
1104
1105The `ToKey` trait is implemented for common string containers in the standard library:
1106
1107```rust
1108impl<'a, T: ?Sized> ToKey for &'a T
1109where
1110    T: ToKey,
1111{
1112    fn to_key(&self) -> Key {
1113        (**self).to_key()
1114    }
1115}
1116
1117impl ToKey for str {
1118    fn to_key(&self) -> Key {
1119        Key::from_str(self, None)
1120    }
1121}
1122
1123impl<'k> ToKey for Key<'k> {
1124    fn to_key(&self) -> Key {
1125        Key::from_str(self)
1126    }
1127}
1128```
1129
1130### `Source`
1131
1132The `Source` trait is a bit like `std::iter::Iterator` for key-value pairs. It gives us a way to inspect some arbitrary collection of key-value pairs using an object-safe visitor pattern:
1133
1134```rust
1135pub trait Source {
1136    fn visit<'kvs>(&'kvs self, visitor: &mut impl Visitor<'kvs>) -> Result<(), Error>;
1137
1138    fn erase(&self) -> ErasedSource
1139    where
1140        Self: Sized,
1141    {
1142        ..
1143    }
1144
1145    fn get<'kvs, Q>(&'kvs self, key: Q) -> Option<Value<'kvs>>
1146    where
1147        Q: ToKey,
1148    {
1149        ..
1150    }
1151
1152    fn by_ref(&self) -> &Self {
1153        ..
1154    }
1155
1156    fn chain<KVS>(self, other: KVS) -> Chained<Self, KVS>
1157    where
1158        Self: Sized,
1159    {
1160        ..
1161    }
1162
1163    fn for_each<F>(self, f: F) -> Result<(), Error>
1164    where
1165        Self: Sized,
1166        F: FnMut(Key, Value),
1167    {
1168        ..
1169    }
1170
1171    fn try_for_each<F, E>(self, f: F) -> Result<(), Error>
1172    where
1173        Self: Sized,
1174        F: FnMut(Key, Value) -> Result<(), E>,
1175        E: Into<Error>,
1176    {
1177        ..
1178    }
1179
1180    #[cfg(any(feature = "kv_serde", feature = "kv_sval"))]
1181    fn as_map(self) -> AsMap<Self>
1182    where
1183        Self: Sized,
1184    {
1185        ..
1186    }
1187
1188    #[cfg(any(feature = "kv_serde", feature = "kv_sval"))]
1189    fn as_seq(self) -> AsSeq<Self>
1190    where
1191        Self: Sized,
1192    {
1193        ..
1194    }
1195}
1196```
1197
1198The `Source` trait is the main extensibility point for structured logging in the `log` crate that's used by the `Record` type. It doesn't make any assumptions about how many key-value pairs it contains or how they're visited. That means the visitor may observe keys in any order, and observe the same key multiple times.
1199
1200#### Adapters
1201
1202Some useful adapters exist as provided methods on the `Source` trait. They're similar to adapters on the standard `Iterator` trait:
1203
1204- `by_ref` to get a reference to a `Source` within a method chain.
1205- `chain` to concatenate one source with another. This is useful for composing implementations of `Log` together for contextual logging.
1206- `get` to try find the value associated with a key. This is useful for well-defined key-value pairs that a framework built over `log` might want to provide, like timestamps or message templates.
1207- `for_each` to execute some closure over all key-value pairs. This is a convenient way to do something with each key-value pair without having to create and implement a `Visitor`. One potential downside of `for_each` is the `Result` return value, which seems surprising when the closure itself can't fail. The `Source::for_each` call might itself fail if the underlying `visit` call fails when iterating over its key-value pairs. This shouldn't be common though, so when paired with `try_for_each`, it might be reasonable to make `for_each` return a `()` and rely on `try_for_each` for surfacing any fallibility.
1208- `try_for_each` is like `for_each`, but takes a fallible closure.
1209- `as_map` to get a serializable map. This is a convenient way to serialize key-value pairs without having to create and implement a `Visitor`.
1210- `as_seq` is like `as_map`, but for serializing as a sequence of tuples.
1211
1212None of these methods are required for the core API. They're helpful tools for working with key-value pairs with minimal machinery. Even if we don't necessarily include them right away it's worth having an API that can support them later without breakage.
1213
1214#### Object safety
1215
1216`Source` is not object-safe because of the provided adapter methods not being object-safe. The only required method, `visit`, is safe though, so an object-safe version of `Source` that forwards this method can be reasonably written:
1217
1218```rust
1219#[derive(Clone, Copy)]
1220pub struct ErasedSource<'a>(&'a dyn ErasedSourceBridge);
1221
1222impl<'a> ErasedSource<'a> {
1223    pub fn erased(kvs: &'a impl Source) -> Self {
1224        ErasedSource(kvs)
1225    }
1226
1227    pub fn empty() -> Self {
1228        ErasedSource(&(&[] as &[(&str, Value)]))
1229    }
1230}
1231
1232impl<'a> Source for ErasedSource<'a> {
1233    fn visit<'kvs>(&'kvs self, visitor: &mut impl Visitor<'kvs>) -> Result<(), Error> {
1234        self.0.erased_visit(visitor)
1235    }
1236
1237    fn get<'kvs, Q>(&'kvs self, key: Q) -> Option<Value<'kvs>>
1238    where
1239        Q: ToKey,
1240    {
1241        self.0.erased_get(key.to_key())
1242    }
1243}
1244
1245trait ErasedSourceBridge {
1246    fn erased_visit<'kvs>(&'kvs self, visitor: &mut impl Visitor<'kvs>) -> Result<(), Error>;
1247    fn erased_get<'kvs>(&'kvs self, key: Key) -> Option<Value<'kvs>>;
1248}
1249
1250impl<KVS> ErasedSourceBridge for KVS
1251where
1252    KVS: Source + ?Sized,
1253{
1254    fn erased_visit<'kvs>(&'kvs self, visitor: &mut impl Visitor<'kvs>) -> Result<(), Error> {
1255        self.visit(visitor)
1256    }
1257
1258    fn erased_get<'kvs>(&'kvs self, key: Key) -> Option<Value<'kvs>> {
1259        self.get(key)
1260    }
1261}
1262```
1263
1264#### Implementors
1265
1266A `Source` containing a single key-value pair is implemented for a tuple of a key and value:
1267
1268```rust
1269impl<K, V> Source for (K, V)
1270where
1271    K: ToKey,
1272    V: ToValue,
1273{
1274    fn visit<'kvs>(&'kvs self, visitor: &mut impl Visitor<'kvs>) -> Result<(), Error>
1275    {
1276        visitor.visit_pair(self.0.to_key(), self.1.to_value())
1277    }
1278}
1279```
1280
1281A `Source` with multiple pairs is implemented for arrays of `Source`s:
1282
1283```rust
1284impl<KVS> Source for [KVS] where KVS: Source {
1285    fn visit<'kvs>(&'kvs self, visitor: &mut impl Visitor<'kvs>) -> Result<(), Error> {
1286        for kv in self {
1287            kv.visit(visitor)?;
1288        }
1289
1290        Ok(())
1291    }
1292}
1293```
1294
1295When `std` is available, `Source` is implemented for some standard collections too:
1296
1297```rust
1298impl<KVS: ?Sized> Source for Box<KVS> where KVS: Source {
1299    fn visit<'kvs>(&'kvs self, visitor: &mut impl Visitor<'kvs>) -> Result<(), Error> {
1300        (**self).visit(visitor)
1301    }
1302}
1303
1304impl<KVS: ?Sized> Source for Arc<KVS> where KVS: Source  {
1305    fn visit<'kvs>(&'kvs self, visitor: &mut impl Visitor<'kvs>) -> Result<(), Error> {
1306        (**self).visit(visitor)
1307    }
1308}
1309
1310impl<KVS: ?Sized> Source for Rc<KVS> where KVS: Source  {
1311    fn visit<'kvs>(&'kvs self, visitor: &mut impl Visitor<'kvs>) -> Result<(), Error> {
1312        (**self).visit(visitor)
1313    }
1314}
1315
1316impl<KVS> Source for Vec<KVS> where KVS: Source {
1317    fn visit<'kvs>(&'kvs self, visitor: &mut impl Visitor<'kvs>) -> Result<(), Error> {
1318        self.as_slice().visit(visitor)
1319    }
1320}
1321
1322impl<K, V> Source for BTreeMap<K, V>
1323where
1324    K: ToKey + Borrow<str> + Ord,
1325    V: ToValue,
1326{
1327    fn visit<'kvs>(&'kvs self, visitor: &mut impl Visitor<'kvs>) -> Result<(), Error>
1328    {
1329        for (k, v) in self {
1330            visitor.visit_pair(k.borrow().to_key(), v.to_value())?;
1331        }
1332
1333        Ok(())
1334    }
1335
1336    fn get<'kvs, Q>(&'kvs self, key: Q) -> Option<Value<'kvs>>
1337    where
1338        Q: ToKey,
1339    {
1340        BTreeMap::get(self, key.to_key().borrow()).map(|v| v.to_value())
1341    }
1342}
1343
1344impl<K, V> Source for HashMap<K, V>
1345where
1346    K: ToKey + Borrow<str> + Eq + Hash,
1347    V: ToValue,
1348{
1349    fn visit<'kvs>(&'kvs self, visitor: &mut impl Visitor<'kvs>) -> Result<(), Error>
1350    {
1351        for (k, v) in self {
1352            visitor.visit_pair(k.borrow().to_key(), v.to_value())?;
1353        }
1354
1355        Ok(())
1356    }
1357
1358    fn get<'kvs, Q>(&'kvs self, key: Q) -> Option<Value<'kvs>>
1359    where
1360        Q: ToKey,
1361    {
1362        HashMap::get(self, key.to_key().borrow()).map(|v| v.to_value())
1363    }
1364}
1365```
1366
1367The `BTreeMap` and `HashMap` implementations provide more efficient implementations of `Source::get`.
1368
1369#### Extensibility: Sending `Source`s between threads
1370
1371Implementations of `Log` might want to offload the processing of records to some background thread. The record would need to be converted into some owned representation before being sent across threads. This is straightforward for the existing borrowed string metadata on a record, but less so for any structured data. The `Source` trait is the point where having some way to convert from a borrowed to an owned set of key-value pairs would make the most sense because that's where the knowledge of the underlying key-value storage is.
1372
1373A new provided method could be added to the `Source` trait that allows it to be converted into an owned variant that is `Send + Sync + 'static`:
1374
1375```rust
1376pub trait Source {
1377    ..
1378
1379    fn to_owned(&self) -> OwnedSource {
1380        OwnedSource::collect(self)
1381    }
1382}
1383
1384#[derive(Clone)]
1385pub struct OwnedSource(Arc<dyn ErasedSource + Send + Sync>);
1386
1387impl OwnedSource {
1388    pub fn new(impl Into<Arc<impl Source + Send + Sync>>) -> Self {
1389        OwnedSource(source.into())
1390    }
1391
1392    pub fn collect(impl Source) -> Self {
1393        // Serialize the `Source` to something like
1394        // `Vec<(String, OwnedValue)>`
1395        // where `OwnedValue` is like `serde_json::Value`
1396        ..
1397    }
1398}
1399```
1400
1401Other implementations of `Source` would be encouraged to override the `to_owned` method if they could provide a more efficient implementation. As an example, if there's a `Source` that is already wrapped up in an `Arc` then it can implement `to_owned` by just cloning itself.
1402
1403### `Visitor`
1404
1405The `Visitor` trait used by `Source` can visit a single key-value pair:
1406
1407```rust
1408pub trait Visitor<'kvs> {
1409    fn visit_pair(&mut self, k: Key<'kvs>, v: Value<'kvs>) -> Result<(), Error>;
1410}
1411
1412impl<'a, 'kvs, T: ?Sized> Visitor<'kvs> for &'a mut T
1413where
1414    T: Visitor<'kvs>
1415{
1416    ..
1417}
1418```
1419
1420A `Visitor` may serialize the keys and values as it sees them. It may also do other work, like sorting or de-duplicating them. Operations that involve ordering keys will probably require allocations.
1421
1422#### Implementors
1423
1424There aren't any public implementors of `Visitor` in the `log` crate. Other crates that use key-value pairs will implement `Visitor`, or use the adapter methods on `Source` and never need to touch `Visitor`s directly.
1425
1426#### Object safety
1427
1428The `Visitor` trait is object-safe.
1429
1430### `Record` and `RecordBuilder`
1431
1432Structured key-value pairs can be set on a `RecordBuilder` as an implementation of a `Source`:
1433
1434```rust
1435impl<'a> RecordBuilder<'a> {
1436    pub fn key_values(&mut self, kvs: ErasedSource<'a>) -> &mut RecordBuilder<'a> {
1437        self.record.kvs = kvs;
1438        self
1439    }
1440}
1441```
1442
1443These key-value pairs can then be accessed on the built `Record`:
1444
1445```rust
1446#[derive(Clone, Debug)]
1447pub struct Record<'a> {
1448    ..
1449
1450    kvs: ErasedSource<'a>,
1451}
1452
1453impl<'a> Record<'a> {
1454    pub fn key_values(&self) -> ErasedSource {
1455        self.kvs.clone()
1456    }
1457}
1458```
1459
1460## A minimal key-values API
1461
1462The following API is just the fundamental pieces of what's proposed by this RFC. Everything else could be implemented on top of this subset without introducing breakage. It also offers the freedom to move in a different direction entirely:
1463
1464```rust
1465impl<'a> RecordBuilder<'a> {
1466    pub fn key_values(&mut self, kvs: ErasedSource<'a>) -> &mut RecordBuilder<'a> {
1467        ..
1468    }
1469}
1470
1471impl<'a> Record<'a> {
1472    pub fn key_values(&self) -> ErasedSource {
1473        ..
1474    }
1475}
1476
1477pub struct Error(_);
1478
1479impl Error {
1480    pub fn msg(msg: &'static str) -> Self {
1481        ..
1482    }
1483}
1484
1485impl Debug for Error {
1486    ..
1487}
1488
1489impl Display for Error {
1490    ..
1491}
1492
1493impl From<fmt::Error> for Error {
1494    ..
1495}
1496
1497impl From<Error> for fmt::Error {
1498    ..
1499}
1500
1501#[cfg(feature = "std")]
1502impl Error {
1503    pub fn custom(err: impl fmt::Display) -> Self {
1504        ..
1505    }
1506}
1507
1508#[cfg(feature = "std")]
1509impl From<io::Error> for Error {
1510    ..
1511}
1512
1513#[cfg(feature = "std")]
1514impl From<Error> for io::Error {
1515    ..
1516}
1517
1518#[cfg(feature = "std")]
1519impl error::Error for Error {
1520    ..
1521}
1522
1523pub struct Value<'v>(_);
1524
1525impl<'v> Value<'v> {
1526    pub fn from_debug(value: &'v impl Debug) -> Self {
1527        ..
1528    }
1529}
1530
1531impl<'v> Debug for Value<'v> {
1532    ..
1533}
1534
1535impl<'v> Display for Value<'v> {
1536    ..
1537}
1538
1539pub struct Key<'k>(_);
1540
1541impl<'k> Key<'k> {
1542    pub fn from_str(key: &'k (impl Borrow<str> + ?Sized)) -> Self {
1543        ..
1544    }
1545
1546    pub fn as_str(&self) -> &str {
1547        ..
1548    }
1549}
1550
1551pub trait Source {
1552    fn visit<'kvs>(&'kvs self, visitor: &mut impl Visitor<'kvs>) -> Result<(), Error>;
1553
1554    fn erase(&self) -> ErasedSource
1555    where
1556        Self: Sized,
1557    {
1558        ..
1559    }
1560}
1561
1562pub struct ErasedSource<'a>(_);
1563
1564pub trait Visitor<'kvs> {
1565    fn visit_pair(&mut self, k: Key<'kvs>, v: Value<'kvs>) -> Result<(), Error>;
1566}
1567```
1568
1569## The `log!` macros
1570
1571The existing `log!` macros will not be changed in the initial implementation of structured logging. Instead, `log` will rely on new `log!` macro implementations and existing structured frameworks like `slog` and `tokio-trace` to capture structured key-value pairs.
1572
1573It's expected that an external library will be created to explore new implementations of the `log!` macros that are structured by design, rather than attempting to graft structured logging support onto the existing macros. The result of this work should eventually find its way back into the `log` crate.
1574
1575# Drawbacks, rationale, and alternatives
1576[drawbacks]: #drawbacks
1577
1578## Supporting structured logging at all
1579
1580Structured logging is a non-trivial feature to support. It adds complexity and overhead to the `log` crate. The alternative, not supporting structured logging, is not a suitable long-term solution for `log` unless we plan to eventually deprecate it.
1581
1582## Internalizing `sval` and `serde`
1583
1584Values captured from any one supported framework can be represented by any other. That means a value can be captured by `sval` can be consumed by `serde`, with its underlying structure retained. This is done through an internal one-to-one integration from each framework to each other framework.
1585
1586The choice of `sval` as a supported framework is because it's purpose-built for serializing values in structured logging. The choice of `serde` as a supported framework is because it's the de-facto standard in the Rust ecosystem and already used within `log`.
1587
1588### Drawbacks
1589
1590The one-to-one bridge between serialization frameworks within `log` makes the effort needed to support them increase exponentially with each addition, and discourages it from supporting more than a few.
1591
1592It also introduces direct coupling between `log` and these frameworks. For `sval` specifically, this is risky because it's not currently stable and breaking changes are a possibility.
1593
1594The mechanism suggested in this RFC for erasing values in `Value::from_any` relies on unsafe code. It's the same as what's used in `std::fmt`, but in `std::fmt` the machinery isn't directly exposed to callers outside of unstable features.
1595
1596### Alternatives
1597
1598#### Build a serialization contract in `log`
1599
1600Instead of internalizing a few serialization frameworks, `log` could provide a public common contract for them to conform to:
1601
1602```rust
1603// Instead of `Value::from_any` + `FromAny`
1604
1605pub trait Visit {
1606    fn visit(&self, visitor: &mut dyn Visitor) -> Result<(), Error>;
1607}
1608
1609pub trait Visitor {
1610    fn u64(&mut self, v: u64) -> Result<(), Error>;
1611    fn i64(&mut self, v: i64) -> Result<(), Error>;
1612
1613    ..
1614}
1615```
1616
1617This is fairly straightforward for primitive types like integers and strings, but becomes much more involved when dealing with complex values likes maps and sequences. Not supporting these complex structures is limiting, and reduces `log`s interoperability with other frameworks that do. A serialization framework needs to do more than just provide a contract, its API needs to work to support implementations on either side of that contract, otherwise it won't gain adoption.
1618
1619Maintaining a useful serialization framework is a distraction for `log`. Serialization of structured values is a complex, necessary, but not primary function of `log`, so it should avoid owning that contract and the baggage that comes along with it if it can. That's why the `sval` library was created; to manage the necessary complexity of building a serialization framework that's suitable for structured logging externally from the `log` crate.
1620
1621Within the `log` crate itself, internalizing fundamental serialization frameworks reduces the effort needed from building a complete framework down to shimming an existing framework. These shims would exist in the wider `log` ecosystem in either case. The effort of managing breaking changes in supported serialization frameworks isn't less than the effort of managing breaking changes in a common contract provided by `log`. The owner of that contract, whether it's `log` or `serde` or `sval`, has to consider the churn introduced by breakage. As a downstream consumer of that breakage, `log` is in the same boat as its consumers.
1622
1623#### Just pick an existing framework
1624
1625Instead of building a common shim around several serialization frameworks, `log` could just pick one and bake it in directly. This would have the benefit of offering the best end-user experience for existing users of that framework when interacting with the `log!` macros and `Source`s. It also means accepting the trade-offs that framework makes. For `serde`, that means requiring the standard library for boxing values. For `sval`, that means accepting churn as it matures.
1626
1627The API proposed using the `Value` type as an opaque container along with APIs for specific frameworks is a reasonable middle-ground between baking in a specific framework and only offering a contract without any direct support. It gives producers of structured data a way to plug their data using a framework of choice into a `Value`, either directly or through compatibility with a supported framework. It gives consumers of structured data a first-class experience for plugging `Value`s into their framework of choice by deriving the appropriate traits. It also gives `log` room to add and deprecate support for frameworks if mindshare shifts in the future.
1628
1629# Prior art
1630[prior-art]: #prior-art
1631
1632Structured logging is a paradigm that's supported by logging frameworks in many language ecosystems.
1633
1634## Rust
1635
1636The `slog` library is a structured logging framework for Rust. Its API predates a stable `serde` crate so it defines its own traits that are similar to `serde::Serialize`. A log record consists of a rendered message and bag of structured key-value pairs. `slog` goes further than this RFC proposes by requiring callers of its `log!` macros to state whether key-values are owned or borrowed by the record, and whether the data is safe to share across threads.
1637
1638This RFC proposes an API that's inspired by `slog`, but doesn't directly support distinguishing between owned or borrowed key-value pairs. Everything is borrowed. That means the only way to send a `Record` to another thread is to serialize it into a different type.
1639
1640## Go
1641
1642The `logrus` library is a structured logging framework for Go. It uses a similar separation of the textual log message from structured key-value pairs that this API proposes.
1643
1644## .NET
1645
1646The C# community has mostly standardized around using message templates for packaging a log message with structured key-value pairs. Instead of logging a rendered message and separate bag of structured data, the log record contains a template that allows key-value pairs to be interpolated from the same bag of structured data. It avoids duplicating the same information multiple times.
1647
1648Supporting something like message templates in Rust using the `log!` macros would probably require procedural macros. A macro like that could be built on top of the API proposed by this RFC.
1649
1650# Unresolved questions
1651[unresolved-questions]: #unresolved-questions
1652