Skip to content

tracing::instrument args by autoref specialization #1612

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 14 additions & 93 deletions tracing-attributes/src/expand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,17 +99,12 @@ fn gen_block<B: ToTokens>(
let span = (|| {
// Pull out the arguments-to-be-skipped first, so we can filter results
// below.
let param_names: Vec<(Ident, (Ident, RecordType))> = params
let param_names: Vec<(Ident, Ident)> = params
.clone()
.into_iter()
.flat_map(|param| match param {
FnArg::Typed(PatType { pat, ty, .. }) => {
param_names(*pat, RecordType::parse_from_ty(&*ty))
}
FnArg::Receiver(_) => Box::new(iter::once((
Ident::new("self", param.span()),
RecordType::Debug,
))),
FnArg::Typed(PatType { pat, .. }) => param_names(*pat),
FnArg::Receiver(_) => Box::new(iter::once(Ident::new("self", param.span()))),
})
// Little dance with new (user-exposed) names and old (internal)
// names of identifiers. That way, we could do the following
Expand All @@ -121,13 +116,13 @@ fn gen_block<B: ToTokens>(
// async fn foo(&self, v: usize) {}
// }
// ```
.map(|(x, record_type)| {
.map(|x| {
// if we are inside a function generated by async-trait <=0.1.43, we need to
// take care to rewrite "_self" as "self" for 'user convenience'
if self_type.is_some() && x == "_self" {
(Ident::new("self", x.span()), (x, record_type))
(Ident::new("self", x.span()), x)
} else {
(x.clone(), (x, record_type))
(x.clone(), x)
}
})
.collect();
Expand Down Expand Up @@ -163,16 +158,13 @@ fn gen_block<B: ToTokens>(
true
}
})
.map(|(user_name, (real_name, record_type))| match record_type {
RecordType::Value => quote!(#user_name = #real_name),
RecordType::Debug => quote!(#user_name = tracing::field::debug(&#real_name)),
})
.map(|(user_name, real_name)| quote!(#user_name = tracing::__tracing_capture_value!(#real_name)))
.collect();

// replace every use of a variable with its original name
if let Some(Fields(ref mut fields)) = args.fields {
let mut replacer = IdentAndTypesRenamer {
idents: param_names.into_iter().map(|(a, (b, _))| (a, b)).collect(),
idents: param_names,
types: Vec::new(),
};

Expand Down Expand Up @@ -351,95 +343,24 @@ fn gen_block<B: ToTokens>(
}
}

/// Indicates whether a field should be recorded as `Value` or `Debug`.
enum RecordType {
/// The field should be recorded using its `Value` implementation.
Value,
/// The field should be recorded using `tracing::field::debug()`.
Debug,
}

impl RecordType {
/// Array of primitive types which should be recorded as [RecordType::Value].
const TYPES_FOR_VALUE: &'static [&'static str] = &[
"bool",
"str",
"u8",
"i8",
"u16",
"i16",
"u32",
"i32",
"u64",
"i64",
"f32",
"f64",
"usize",
"isize",
"NonZeroU8",
"NonZeroI8",
"NonZeroU16",
"NonZeroI16",
"NonZeroU32",
"NonZeroI32",
"NonZeroU64",
"NonZeroI64",
"NonZeroUsize",
"NonZeroIsize",
"Wrapping",
];

/// Parse `RecordType` from [syn::Type] by looking up
/// the [RecordType::TYPES_FOR_VALUE] array.
fn parse_from_ty(ty: &syn::Type) -> Self {
match ty {
syn::Type::Path(syn::TypePath { path, .. })
if path
.segments
.iter()
.last()
.map(|path_segment| {
let ident = path_segment.ident.to_string();
Self::TYPES_FOR_VALUE.iter().any(|&t| t == ident)
})
.unwrap_or(false) =>
{
RecordType::Value
}
syn::Type::Reference(syn::TypeReference { elem, .. }) => {
RecordType::parse_from_ty(&*elem)
}
_ => RecordType::Debug,
}
}
}

fn param_names(pat: Pat, record_type: RecordType) -> Box<dyn Iterator<Item = (Ident, RecordType)>> {
fn param_names(pat: Pat) -> Box<dyn Iterator<Item = Ident>> {
match pat {
Pat::Ident(PatIdent { ident, .. }) => Box::new(iter::once((ident, record_type))),
Pat::Reference(PatReference { pat, .. }) => param_names(*pat, record_type),
Pat::Ident(PatIdent { ident, .. }) => Box::new(iter::once(ident)),
Pat::Reference(PatReference { pat, .. }) => param_names(*pat),
// We can't get the concrete type of fields in the struct/tuple
// patterns by using `syn`. e.g. `fn foo(Foo { x, y }: Foo) {}`.
// Therefore, the struct/tuple patterns in the arguments will just
// always be recorded as `RecordType::Debug`.
Pat::Struct(PatStruct { fields, .. }) => Box::new(
fields
.into_iter()
.flat_map(|FieldPat { pat, .. }| param_names(*pat, RecordType::Debug)),
),
Pat::Tuple(PatTuple { elems, .. }) => Box::new(
elems
.into_iter()
.flat_map(|p| param_names(p, RecordType::Debug)),
.flat_map(|FieldPat { pat, .. }| param_names(*pat)),
),
Pat::Tuple(PatTuple { elems, .. }) => Box::new(elems.into_iter().flat_map(param_names)),
Pat::TupleStruct(PatTupleStruct {
pat: PatTuple { elems, .. },
..
}) => Box::new(
elems
.into_iter()
.flat_map(|p| param_names(p, RecordType::Debug)),
),
}) => Box::new(elems.into_iter().flat_map(param_names)),

// The above *should* cover all cases of irrefutable patterns,
// but we purposefully don't do any funny business here
Expand Down
28 changes: 14 additions & 14 deletions tracing-attributes/tests/destructuring.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ fn destructure_tuples() {
.new_span(
span.clone().with_field(
field::mock("arg1")
.with_value(&format_args!("1"))
.and(field::mock("arg2").with_value(&format_args!("2")))
.with_value(&1usize)
.and(field::mock("arg2").with_value(&2usize))
.only(),
),
)
Expand Down Expand Up @@ -42,10 +42,10 @@ fn destructure_nested_tuples() {
.new_span(
span.clone().with_field(
field::mock("arg1")
.with_value(&format_args!("1"))
.and(field::mock("arg2").with_value(&format_args!("2")))
.and(field::mock("arg3").with_value(&format_args!("3")))
.and(field::mock("arg4").with_value(&format_args!("4")))
.with_value(&1usize)
.and(field::mock("arg2").with_value(&2usize))
.and(field::mock("arg3").with_value(&3usize))
.and(field::mock("arg4").with_value(&4usize))
.only(),
),
)
Expand Down Expand Up @@ -100,8 +100,8 @@ fn destructure_tuple_structs() {
.new_span(
span.clone().with_field(
field::mock("arg1")
.with_value(&format_args!("1"))
.and(field::mock("arg2").with_value(&format_args!("2")))
.with_value(&1usize)
.and(field::mock("arg2").with_value(&2usize))
.only(),
),
)
Expand Down Expand Up @@ -141,8 +141,8 @@ fn destructure_structs() {
.new_span(
span.clone().with_field(
field::mock("arg1")
.with_value(&format_args!("1"))
.and(field::mock("arg2").with_value(&format_args!("2")))
.with_value(&1usize)
.and(field::mock("arg2").with_value(&2usize))
.only(),
),
)
Expand Down Expand Up @@ -186,10 +186,10 @@ fn destructure_everything() {
.new_span(
span.clone().with_field(
field::mock("arg1")
.with_value(&format_args!("1"))
.and(field::mock("arg2").with_value(&format_args!("2")))
.and(field::mock("arg3").with_value(&format_args!("3")))
.and(field::mock("arg4").with_value(&format_args!("4")))
.with_value(&1usize)
.and(field::mock("arg2").with_value(&2usize))
.and(field::mock("arg3").with_value(&3usize))
.and(field::mock("arg4").with_value(&4usize))
.only(),
),
)
Expand Down
12 changes: 12 additions & 0 deletions tracing-core/src/field.rs
Original file line number Diff line number Diff line change
Expand Up @@ -238,10 +238,12 @@ pub trait Value: crate::sealed::Sealed {
/// Uses `record_debug` in the `Value` implementation to
/// avoid an unnecessary evaluation.
#[derive(Clone)]
#[repr(transparent)]
pub struct DisplayValue<T: fmt::Display>(T);

/// A `Value` which serializes as a string using `fmt::Debug`.
#[derive(Clone)]
#[repr(transparent)]
pub struct DebugValue<T: fmt::Debug>(T);

/// Wraps a type implementing `fmt::Display` as a `Value` that can be
Expand All @@ -262,6 +264,16 @@ where
DebugValue(t)
}

/// Wraps a type behind a refererence implementing `fmt::Debug` as a
/// `Value` that can be recorded using its `Debug` implementation.
pub fn debug_ref<T>(t: &T) -> &DebugValue<T>
where
T: fmt::Debug,
{
// SAFETY: DebugValue is a repr(transparent) wrapper of T
unsafe { &*(t as *const T as *const DebugValue<T>) }
}

// ===== impl Visit =====

impl<'a, 'b> Visit for fmt::DebugStruct<'a, 'b> {
Expand Down
79 changes: 79 additions & 0 deletions tracing/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1137,6 +1137,85 @@ pub mod __macro_support {
.finish()
}
}

/// Implementation of [autoref specialization] for span/event value capture.
///
/// Used by the `__tracing_capture_value!` macro by importing all of the
/// traits in this module, which all provide `__tracing_capture_value()`,
/// and then calling `(&&value).__tracing_capture_value()`.
///
/// Method resolution checks `T`, then `&T`, and then `&&T` for the
/// `__tracing_capture_value()` method. We take advantage of this via the
/// different blanket implementations of `__tracing_capture_value()` for
/// the different amounts of indirection.
///
/// [autoref specialization]: https://github.com/dtolnay/case-studies/blob/master/autoref-specialization/README.md
///
/// /!\ WARNING: This is *not* a stable API! /!\
/// This type, and all code contained in the `__macro_support` module, is
/// a *private* API of `tracing`. It is exposed publicly because it is used
/// by the `tracing` macros, but it is not part of the stable versioned API.
/// Breaking changes to this module may occur in small-numbered versions
/// without warning.
pub mod __tracing_capture_value_by {
/// Autoref specialization level 0 for `__tracing_capture_value!`
///
/// /!\ WARNING: This is *not* a stable API! /!\
/// This type, and all code contained in the `__macro_support` module, is
/// a *private* API of `tracing`. It is exposed publicly because it is used
/// by the `tracing` macros, but it is not part of the stable versioned API.
/// Breaking changes to this module may occur in small-numbered versions
/// without warning.
#[cfg(all(tracing_unstable, feature = "valuable"))]
pub trait TracingCaptureValueByValuable {
fn __tracing_capture_value(&self) -> &dyn crate::field::Value;
}

#[cfg(all(tracing_unstable, feature = "valuable"))]
impl<T: valuable::Valuable> TracingCaptureValueByValuable for T {
fn __tracing_capture_value(&self) -> valuable::Value<'_> {
crate::field::valuable(self)
}
}

/// Autoref specialization level 1 for `__tracing_capture_value!`
///
/// /!\ WARNING: This is *not* a stable API! /!\
/// This type, and all code contained in the `__macro_support` module, is
/// a *private* API of `tracing`. It is exposed publicly because it is used
/// by the `tracing` macros, but it is not part of the stable versioned API.
/// Breaking changes to this module may occur in small-numbered versions
/// without warning.
#[cfg(not(all(tracing_unstable, feature = "valuable")))]
pub trait TracingCaptureValueByValue {
fn __tracing_capture_value(&self) -> &dyn crate::field::Value;
}

#[cfg(not(all(tracing_unstable, feature = "valuable")))]
impl<T: crate::field::Value> TracingCaptureValueByValue for &T {
fn __tracing_capture_value(&self) -> &dyn crate::field::Value {
*self
}
}

/// Autoref specialization level 2 for `__tracing_capture_value!`
///
/// /!\ WARNING: This is *not* a stable API! /!\
/// This type, and all code contained in the `__macro_support` module, is
/// a *private* API of `tracing`. It is exposed publicly because it is used
/// by the `tracing` macros, but it is not part of the stable versioned API.
/// Breaking changes to this module may occur in small-numbered versions
/// without warning.
pub trait TracingCaptureValueByDebug {
fn __tracing_capture_value(&self) -> &dyn crate::field::Value;
}

impl<T: core::fmt::Debug> TracingCaptureValueByDebug for &&T {
fn __tracing_capture_value(&self) -> &dyn crate::field::Value {
crate::field::debug_ref(**self)
}
}
}
}

#[cfg(feature = "log")]
Expand Down
10 changes: 10 additions & 0 deletions tracing/src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2391,6 +2391,16 @@ macro_rules! fieldset {

}

#[doc(hidden)]
#[macro_export]
macro_rules! __tracing_capture_value {
($e:expr) => {{
#[allow(unused_imports)]
use $crate::__macro_support::__tracing_capture_value_by::*;
(&&$e).__tracing_capture_value()
}};
}

#[cfg(feature = "log")]
#[doc(hidden)]
#[macro_export]
Expand Down