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