Open jswrenn opened 2 years ago
See also existing issues #1939 and #1123 — this seems like a specific proposal for how to implement the behavior discussed in those issues. I believe the comments on those issues may discuss additional alternative approaches.
One thing I'll add is that we can always use non-const-eval-able code as the span's message for now, which would let us add your proposed approach for getting qualified method names into the current #[instrument]
-generated spans, and we could then go back and use them for the span's name later if (say) type_name
becomes possible in const contexts.
What's the reason behind the decision to require span names to be const evaluable?
What's the reason behind the decision to require span names to be const evaluable?
Span names are part of a span's metadata, which is a static
struct generated at compile-time. Filtering (determining whether a span or event is enabled) is performed using this static metadata, before any data is recorded. This makes skipping disabled spans or events fast, because none of the data recorded by skipped spans/events must be evaluated in order to disable them.
Feature Request
Crates
This feature directly impacts:
tracing-attributes
The discussed implementation strategies impact:
tracing
tracing-core
Motivation
The
name
metadata on spans generated by#[instrument]
includes only the name the name of the annotated function, and thus lacks useful context. Consider the following two motivations:Motivation: Distinguishing between methods with the same name
Because the name of the
Self
type is not included inname
, it is difficult to distinguish between methods of different types that have the same name. For instance, the two methods in this example (reduced frommini-redis-server
) have identical names ("run
") and targets ("server
"):Since
name
andtarget
are the primary mechanism for filtering on spans, the spans emitted by each of these functions are incredibly difficult to distinguish from each other. Currently, the most viable recourse is to manually set their name:...which is fragile, mistake-prone, and verbose.
Motivation: Distinguishing between distinct instantiations of methods.
Because
name
does not factor in generic parameters, it is impossible to distinguish between distinct instantiations of generic methods. This is unfortunate, because how generic types are instantiated can have significant impact how methods behave and how they perform. E.g., all tracing spans emitted bylock
in the below example will have identicalname
s, regardless of howT
is instantiated:Proposals
Improving this situation is tricky, because:
function_name!()
macro comparable tomodule_path!()
Self
type during expansion.However, it's nonetheless possible to generate names that include the necessary extra context.
A solution using
core::any::type_name
.The below code (playground) defines two macros that produce
&'static str
:abstract_fn_name!
Produces the name of the current function/method, EXCLUDING generics.concrete_fn_name!
Produces the name of the surrounding function/method, INCLUDING generics.The above snippet prints:
Unfortunately, neither of the above approaches can be easily be adapted to be runnable in a const context, since
core::any::type_name
is not aconst fn
. At minimum, implementing this approach would require revising the expansion ofspan!
. Furthermore, the result ofconcrete_fn_name!()
cannot be cached in astatic
without indirection, since distinct generic instantiations of functions in Rust currently share their statics.Implementing either of the above two approaches thus would require changes to
tracing
. (That said, relaxing the stiff requirements that tracing imposes onSpan
names (i.e., that they're const evaluable and static), would have other benefits. When I was working on allocating tracing, for instance, I really wanted to be able to dynamically generate event names from the backtrace frames inalloc
andfree
, but could not do so.)A solution (ab)using proc macros.
Although proc-macro attributes do not have access to the qualified function name of annotated fn items, they do have unstable access to:
...of processed token spans. Using these, the
#[instrument]
macro could be modified re-parse a full syntax tree from the source-file containing the annotated item, then find thefn
item in that three with a matching source location, and then look back up the parse tree to determine whether thefn
item is associated with a type.Alternatives
Aside from the option of taking no action, we could also advocate for any of the following Rust language/library features:
type_name
a const fnThese features would, in the long term, eliminate the implementation drawbacks of the
type_name
-based approach.