Skip to content

Commit 21b6ddc

Browse files
jswrennhawkw
authored andcommitted
attributes: permit setting parent span via #[instrument(parent = …)] (#2091)
This PR extends the `#[instrument]` attribute to accept an optionally `parent = …` argument that provides an explicit parent to the generated `Span`. ## Motivation This PR resolves #2021 and partially resolves #879. (It only partly resolves #879 in that it only provides a mechanism for specifying an explicit parent, but *not* for invoking `follows_from`.) ## Solution This PR follows the implementation strategy articulated by @hawkw: #879 (comment). The user-provided value to the `parent` argument may be any expression, and this expression is provided directly to the invocation of [`span!`](https://tracing.rs/tracing/macro.span.html).
1 parent 3d7a18b commit 21b6ddc

File tree

4 files changed

+153
-0
lines changed

4 files changed

+153
-0
lines changed

tracing-attributes/src/attr.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ pub(crate) struct InstrumentArgs {
1111
level: Option<Level>,
1212
pub(crate) name: Option<LitStr>,
1313
target: Option<LitStr>,
14+
pub(crate) parent: Option<Expr>,
1415
pub(crate) skips: HashSet<Ident>,
1516
pub(crate) skip_all: bool,
1617
pub(crate) fields: Option<Fields>,
@@ -123,6 +124,12 @@ impl Parse for InstrumentArgs {
123124
}
124125
let target = input.parse::<StrArg<kw::target>>()?.value;
125126
args.target = Some(target);
127+
} else if lookahead.peek(kw::parent) {
128+
if args.target.is_some() {
129+
return Err(input.error("expected only a single `parent` argument"));
130+
}
131+
let parent = input.parse::<ExprArg<kw::parent>>()?;
132+
args.parent = Some(parent.value);
126133
} else if lookahead.peek(kw::level) {
127134
if args.level.is_some() {
128135
return Err(input.error("expected only a single `level` argument"));
@@ -193,6 +200,23 @@ impl<T: Parse> Parse for StrArg<T> {
193200
}
194201
}
195202

203+
struct ExprArg<T> {
204+
value: Expr,
205+
_p: std::marker::PhantomData<T>,
206+
}
207+
208+
impl<T: Parse> Parse for ExprArg<T> {
209+
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
210+
let _ = input.parse::<T>()?;
211+
let _ = input.parse::<Token![=]>()?;
212+
let value = input.parse()?;
213+
Ok(Self {
214+
value,
215+
_p: std::marker::PhantomData,
216+
})
217+
}
218+
}
219+
196220
struct Skips(HashSet<Ident>);
197221

198222
impl Parse for Skips {
@@ -374,6 +398,7 @@ mod kw {
374398
syn::custom_keyword!(skip_all);
375399
syn::custom_keyword!(level);
376400
syn::custom_keyword!(target);
401+
syn::custom_keyword!(parent);
377402
syn::custom_keyword!(name);
378403
syn::custom_keyword!(err);
379404
syn::custom_keyword!(ret);

tracing-attributes/src/expand.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,8 @@ fn gen_block<B: ToTokens>(
135135

136136
let target = args.target();
137137

138+
let parent = args.parent.iter();
139+
138140
// filter out skipped fields
139141
let quoted_fields: Vec<_> = param_names
140142
.iter()
@@ -182,6 +184,7 @@ fn gen_block<B: ToTokens>(
182184

183185
quote!(tracing::span!(
184186
target: #target,
187+
#(parent: #parent,)*
185188
#level,
186189
#span_name,
187190
#(#quoted_fields,)*

tracing-attributes/src/lib.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,29 @@ mod expand;
345345
/// // ...
346346
/// }
347347
/// ```
348+
/// Overriding the generated span's parent:
349+
/// ```
350+
/// # use tracing_attributes::instrument;
351+
/// #[instrument(parent = None)]
352+
/// pub fn my_function() {
353+
/// // ...
354+
/// }
355+
/// ```
356+
/// ```
357+
/// # use tracing_attributes::instrument;
358+
/// // A struct which owns a span handle.
359+
/// struct MyStruct
360+
/// {
361+
/// span: tracing::Span
362+
/// }
363+
///
364+
/// impl MyStruct
365+
/// {
366+
/// // Use the struct's `span` field as the parent span
367+
/// #[instrument(parent = &self.span, skip(self))]
368+
/// fn my_method(&self) {}
369+
/// }
370+
/// ```
348371
///
349372
/// To skip recording an argument, pass the argument's name to the `skip`:
350373
///
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
use tracing::{collect::with_default, Id, Level};
2+
use tracing_attributes::instrument;
3+
use tracing_mock::*;
4+
5+
#[instrument]
6+
fn with_default_parent() {}
7+
8+
#[instrument(parent = parent_span, skip(parent_span))]
9+
fn with_explicit_parent<P>(parent_span: P)
10+
where
11+
P: Into<Option<Id>>,
12+
{
13+
}
14+
15+
#[test]
16+
fn default_parent_test() {
17+
let contextual_parent = span::mock().named("contextual_parent");
18+
let child = span::mock().named("with_default_parent");
19+
20+
let (collector, handle) = collector::mock()
21+
.new_span(
22+
contextual_parent
23+
.clone()
24+
.with_contextual_parent(None)
25+
.with_explicit_parent(None),
26+
)
27+
.new_span(
28+
child
29+
.clone()
30+
.with_contextual_parent(Some("contextual_parent"))
31+
.with_explicit_parent(None),
32+
)
33+
.enter(child.clone())
34+
.exit(child.clone())
35+
.enter(contextual_parent.clone())
36+
.new_span(
37+
child
38+
.clone()
39+
.with_contextual_parent(Some("contextual_parent"))
40+
.with_explicit_parent(None),
41+
)
42+
.enter(child.clone())
43+
.exit(child)
44+
.exit(contextual_parent)
45+
.done()
46+
.run_with_handle();
47+
48+
with_default(collector, || {
49+
let contextual_parent = tracing::span!(Level::TRACE, "contextual_parent");
50+
51+
with_default_parent();
52+
53+
contextual_parent.in_scope(|| {
54+
with_default_parent();
55+
});
56+
});
57+
58+
handle.assert_finished();
59+
}
60+
61+
#[test]
62+
fn explicit_parent_test() {
63+
let contextual_parent = span::mock().named("contextual_parent");
64+
let explicit_parent = span::mock().named("explicit_parent");
65+
let child = span::mock().named("with_explicit_parent");
66+
67+
let (collector, handle) = collector::mock()
68+
.new_span(
69+
contextual_parent
70+
.clone()
71+
.with_contextual_parent(None)
72+
.with_explicit_parent(None),
73+
)
74+
.new_span(
75+
explicit_parent
76+
.with_contextual_parent(None)
77+
.with_explicit_parent(None),
78+
)
79+
.enter(contextual_parent.clone())
80+
.new_span(
81+
child
82+
.clone()
83+
.with_contextual_parent(Some("contextual_parent"))
84+
.with_explicit_parent(Some("explicit_parent")),
85+
)
86+
.enter(child.clone())
87+
.exit(child)
88+
.exit(contextual_parent)
89+
.done()
90+
.run_with_handle();
91+
92+
with_default(collector, || {
93+
let contextual_parent = tracing::span!(Level::INFO, "contextual_parent");
94+
let explicit_parent = tracing::span!(Level::INFO, "explicit_parent");
95+
96+
contextual_parent.in_scope(|| {
97+
with_explicit_parent(&explicit_parent);
98+
});
99+
});
100+
101+
handle.assert_finished();
102+
}

0 commit comments

Comments
 (0)