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}