async_log/
lib.rs

1//! Async tracing capabilities for the standard [`log`] crate.
2//!
3//! [`log`]: https://docs.rs/log
4//!
5//! This crate provides extension types and hooks to `log` to enable asynchronous logging.
6//!
7//! ## What is Async Logging?
8//! When building a _synchronous_ application, log messages can be relied on to always happen
9//! in sequence. But unfortunately synchronous applications are rarely capable of utilizating
10//! system resources to their full potential.
11//!
12//! In contrast, concurrent applications make a lot better use of system resources. But it also
13//! means we can no longer rely on log messages to strictly happen in sequence. In order to make
14//! sense of logs in asynchronous applications, we need to be able to correlate sequences of events
15//! with each other:
16//!
17//! ```txt
18//! a1 -> b1 -> b2 -> a2 -> b3     # raw log stream
19//!
20//! a1 -------------> a2           # parsed log stream a
21//!       b1 -> b2 -------> b3     # parsed log stream b
22//! ```
23//! _The raw log stream contains items for both "a" and "b". With async logging you want to be able
24//! to distinguish between the items for "a", and the items from "b"._
25//!
26//! ## How do we correlate messages?
27//! The goal of async logging is to determine which events happened in sequence inside your code. In
28//! practice this means being able to correlate events with each other past _yield points_ (e.g.
29//! `.await`), and _thread bounds_.
30//!
31//! The way we do this is by adding the current task ID, and thread ID from where the log is
32//! occurring. An whenever a _new_ task is spawned we log the following values:
33//!
34//! - The ID of the task from which the new task is spawned (`task_parent_id`)
35//! - The ID of the new task that's spawned (`task_id`)
36//! - The current thread ID (`thread_id`)
37//! - The line from where the task was spawned (`spawn_line`, when `RUST_BACKTRACE=1` enabled)
38//!
39//! With all this information we have all the information to correlate tasks with each other. We
40//! know what the parent task was, what the new task is, and log that information together. On the
41//! receiving side we can then reconstruct that to create correlations.
42//!
43//! ## What is a span?
44//! A span is a pair of messages. One is emitted at the start of an operation, and the other is
45//! emitted at the end of the operation. If we add timestamps to when each message was sent, we're
46//! able to determine how long operations take. Or determine which operations never finished.
47//!
48//! In `async-log` each span is annotated with a `span_mark` message:
49//! - `span_mark=start` marks the start of a span
50//! - `span_mark=end` marks the end of a span
51//!
52//! __example__
53//! ```txt
54//! runtime::fs::read_to_string, span_mark=start, path=/tmp/foob, task_id=7, thread_id=8
55//! runtime::fs::read_to_string, span_mark=end, path=/tmp/foob, task_id=7, thread_id=8
56//! ```
57//!
58//! ## Why build on the log crate?
59//! [`log`](https://docs.rs/log/) is Rust's standard log crate. It's incredibly flexible, and was
60//! built with extensibility in mind. Because it's so widely used, being able to extend it allows
61//! us to add tracing data to crates without needing to make any changes to their `log!` calls.
62//!
63//! ## Formatting
64//! Structured logging (key-value logging) is [currently in the
65//! process](https://github.com/rust-lang-nursery/log/issues/328) of being added to `log`.
66//!
67//! At the time of writing there are no published versions available with even the experimental
68//! features available. So until then we have to add key-value pairs using strings. Once key-value
69//! logging is added to `log` we'll publish a breaking change, and move over.
70//!
71//! The syntax we've chosen to use is `foo=bar` pairs. Multiple pairs should be delimited using
72//! commas (`,`). Every pair should come _after_ the first message. An example log looks like this:
73//!
74//! ```txt
75//! a new cat has logged on, name=nori, snacks=always
76//! ```
77//!
78//! ## Example
79//!
80//! ```rust
81//! use async_log::span;
82//! use log::info;
83//!
84//! fn setup_logger() {
85//!     let logger = femme::pretty::Logger::new();
86//!     async_log::Logger::wrap(logger, || 12)
87//!         .start(log::LevelFilter::Trace)
88//!         .unwrap();
89//! }
90//!
91//! fn main() {
92//!     setup_logger();
93//!
94//!     span!("new level, depth={}", 1, {
95//!         let x = "beep";
96//!         info!("look at this value, x={}", x);
97//!
98//!         span!("new level, depth={}", 2, {
99//!             let y = "boop";
100//!             info!("another nice value, y={}", y);
101//!         })
102//!     })
103//! }
104//! ```
105
106#![forbid(unsafe_code, future_incompatible, rust_2018_idioms)]
107#![deny(missing_debug_implementations, nonstandard_style)]
108#![warn(missing_docs, unreachable_pub)]
109#![cfg_attr(test, deny(warnings))]
110
111pub use async_log_attributes::instrument;
112
113use std::fmt::Arguments;
114
115mod backtrace;
116mod logger;
117mod macros;
118
119pub use logger::Logger;
120
121/// A new span created by [`span!`].
122///
123/// An `trace!` is emitted when this struct is constructed. And another `trace!` is emitted when
124/// this struct is dropped.
125///
126/// [`span!`]: macro.span.html
127#[must_use]
128#[derive(Debug)]
129pub struct Span {
130    args: String,
131}
132
133impl Span {
134    /// Create a new instance.
135    ///
136    /// You should generally prefer to call `span!` instead of constructing this manually.
137    pub fn new(args: impl AsRef<str>) -> Self {
138        let args = args.as_ref();
139        struct KeyValues;
140        impl log::kv::Source for KeyValues {
141            fn visit<'kvs>(
142                &'kvs self,
143                visitor: &mut dyn log::kv::Visitor<'kvs>,
144            ) -> Result<(), log::kv::Error> {
145                visitor.visit_pair("span_mark".into(), "start".into())?;
146                Ok(())
147            }
148        }
149
150        print(log::Level::Trace, format_args!("{}", args), KeyValues {});
151        Self {
152            args: args.to_owned(),
153        }
154    }
155}
156
157impl Drop for Span {
158    fn drop(&mut self) {
159        struct KeyValues;
160        impl log::kv::Source for KeyValues {
161            fn visit<'kvs>(
162                &'kvs self,
163                visitor: &mut dyn log::kv::Visitor<'kvs>,
164            ) -> Result<(), log::kv::Error> {
165                visitor.visit_pair("span_mark".into(), "end".into())?;
166                Ok(())
167            }
168        }
169
170        print(
171            log::Level::Trace,
172            format_args!("{}", self.args),
173            KeyValues {},
174        );
175    }
176}
177
178fn print(level: log::Level, msg: Arguments<'_>, key_values: impl log::kv::Source) {
179    if level <= log::STATIC_MAX_LEVEL && level <= log::max_level() {
180        log::logger().log(
181            &log::Record::builder()
182                .args(msg)
183                .key_values(&key_values)
184                .level(level)
185                .target(module_path!())
186                .module_path(Some(module_path!()))
187                .file(Some(file!()))
188                .line(Some(line!()))
189                .build(),
190        );
191    }
192}