spandoc/
lib.rs

1//! Attribute macro that transforms doc comments in functions into tracing
2//! [`spans`](https://docs.rs/tracing/0.1.16/tracing/span/index.html).
3//!
4//! Spandoc provides a function level attribute macro that converts doc !
5//! comments _within_ a function into tracing spans. These spans are entered
6//! before the annotated expression and exited immediately after. Spandoc is also
7//! async aware, so expressions that contain `.await`s will correctly instrument
8//! the future, exit before yielding, and enter the span again when the future
9//! resumes.
10//!
11//! # Details
12//!
13//! All doc comments intended to be transformed into spans **must** begin with `SPANDOC: `:
14//!
15//! ```rust
16//! use spandoc::spandoc;
17//! use tracing::info;
18//!
19//! #[spandoc]
20//! fn foo() {
21//!     /// SPANDOC: this will be converted into a span
22//!     info!("event 1");
23//!
24//!     /// this will be ignored and produce a warning for an unused doc comment
25//!     info!("event 2");
26//! }
27//! ```
28//!
29//! The spans that are created by spandoc are explicitly scoped to the expression
30//! they're associated with.
31//!
32//! ```rust
33//! use spandoc::spandoc;
34//! use tracing::info;
35//!
36//! #[spandoc]
37//! fn main() {
38//!     tracing_subscriber::fmt::init();
39//!     let local = 4;
40//!
41//!     /// SPANDOC: Emit a tracing info event {?local}
42//!     info!("event 1");
43//!
44//!     info!("event 2");
45//! }
46//! ```
47//!
48//! Running the above example will produce the following output
49//!
50//! <pre><font color="#06989A"><b>spandoc</b></font> on <font color="#75507B"><b> await-support</b></font> <font color="#CC0000"><b>[!+] </b></font>is <font color="#FF8700"><b>📦 v0.1.3</b></font> via <font color="#CC0000"><b>🦀 v1.44.1</b></font>
51//! <font color="#4E9A06"><b>❯</b></font> cargo run --example scoped
52//! <font color="#4E9A06"><b>    Finished</b></font> dev [unoptimized + debuginfo] target(s) in 0.03s
53//! <font color="#4E9A06"><b>     Running</b></font> `target/debug/examples/scoped`
54//! <font color="#A1A1A1">Jul 09 12:42:43.691 </font><font color="#4E9A06"> INFO</font> <b>main::comment{</b>local=4 text=Emit a tracing info event<b>}</b>: scoped: event 1
55//! <font color="#A1A1A1">Jul 09 12:42:43.691 </font><font color="#4E9A06"> INFO</font> scoped: event 2</pre>
56//!
57//! Local variables can be associated with the generated spans by adding a
58//! trailing block to the doc comment. The syntax for fields in the span is the
59//! [same as in `tracing`](https://docs.rs/tracing/*/tracing/index.html#using-the-macros).
60//!
61//! ```rust
62//! use spandoc::spandoc;
63//! use tracing::info;
64//!
65//! #[spandoc]
66//! fn foo() {
67//!     let path = "fake.txt";
68//!     /// SPANDOC: going to load config {?path}
69//!     info!("event 1");
70//!
71//!     /// this will be ignored and produce a warning for an unused doc comment
72//!     info!("event 2");
73//! }
74//! ```
75//!
76//! When applied to expressions that contain `await`s spandoc will correctly
77//! use `instrument()` and exit/enter the span when suspending and resuming the
78//! future. If there are multiple await expressions inside of the annotated
79//! expression it will instrument each expression with the same span. The macro
80//! will not recurse into `async` blocks.
81//!
82//!
83//! ```rust
84//! use std::future::Future;
85//! use spandoc::spandoc;
86//! use tracing::info;
87//!
88//! fn make_liz() -> impl Future<Output = Result<(), ()>> {
89//!     info!("this will be printed in the span from `clever_girl`");
90//!
91//!     liz()
92//! }
93//!
94//! async fn liz() -> Result<(), ()> {
95//!     # let span = tracing::Span::current();
96//!     # let metadata = span.metadata().expect("span should have metadata");
97//!     # assert_eq!(metadata.name(), "clever_girl::comment");
98//!     #
99//!     info!("this will also be printed in the span from `clever_girl`");
100//!
101//!     // return a result so we can call map outside of the scope of the future
102//!     Ok(())
103//! }
104//!
105//! #[spandoc]
106//! async fn clever_girl() {
107//!     // This span will be entered before the await, exited correctly when the
108//!     // future suspends, and instrument the future returned from `liz` with
109//!     // `tracing-futures`
110//!     /// SPANDOC: clever_girl async span
111//!     make_liz().await.map(|()| info!("this will also be printed in the span"));
112//! }
113//! #
114//! # tracing_subscriber::fmt::init();
115//! # futures::executor::block_on(clever_girl());
116//! ```
117#![doc(html_root_url = "https://docs.rs/spandoc/0.2.2")]
118#![cfg_attr(docsrs, feature(doc_cfg))]
119#![warn(
120    missing_docs,
121    rustdoc::missing_doc_code_examples,
122    rust_2018_idioms,
123    unreachable_pub,
124    bad_style,
125    const_err,
126    dead_code,
127    improper_ctypes,
128    non_shorthand_field_patterns,
129    no_mangle_generic_items,
130    overflowing_literals,
131    path_statements,
132    patterns_in_fns_without_body,
133    private_in_public,
134    unconditional_recursion,
135    unused,
136    unused_allocation,
137    unused_comparisons,
138    unused_parens,
139    while_true
140)]
141#![allow(clippy::needless_doctest_main)]
142use std::sync::atomic::{AtomicBool, Ordering};
143
144use tracing_futures::Instrument;
145
146/// Attribute macro that transforms doc comments in functions into tracing [`spans`](https://docs.rs/tracing/0.1.16/tracing/span/index.html).
147pub use spandoc_attribute::spandoc;
148
149#[doc(hidden)]
150pub struct FancyGuard<'a> {
151    span: &'a tracing::Span,
152    entered: AtomicBool,
153}
154
155impl<'a> FancyGuard<'a> {
156    #[doc(hidden)]
157    pub fn new(span: &'a tracing::Span) -> FancyGuard<'a> {
158        span.with_subscriber(|(id, sub)| sub.enter(id));
159        Self {
160            span,
161            entered: AtomicBool::new(true),
162        }
163    }
164
165    #[doc(hidden)]
166    pub async fn wrap<F>(&self, fut: F) -> F::Output
167    where
168        F: std::future::Future,
169    {
170        self.span.with_subscriber(|(id, sub)| sub.exit(id));
171        self.entered.store(false, Ordering::Relaxed);
172        let output = fut.instrument(self.span.clone()).await;
173        self.span.with_subscriber(|(id, sub)| sub.enter(id));
174        self.entered.store(true, Ordering::Relaxed);
175        output
176    }
177}
178
179impl Drop for FancyGuard<'_> {
180    fn drop(&mut self) {
181        if self.entered.load(Ordering::Relaxed) {
182            self.span.with_subscriber(|(id, sub)| sub.exit(id));
183        }
184    }
185}