Skip to content

Commit b6c07e7

Browse files
authored
attributes: don't record primitive types of the function arguments as fmt::Debug (#1378)
## Motivation The default behavior of `tracing::instrument` attribution will record all of the function arguments as `fmt::Debug`, which is overwhelmed and unnecessary for those primitive types, such as `bool`, `u8`, `i8`, `u16`, `i16`, `u32`, `i32`, `u64`, `i64`, `usize`, and `isize`. Another concerning reason is that we‘ll lose the type info of those primitive types when record by a `Visitor`, while those type infos is essential to some people. For example, I need to show my spans in Jaeger UI. ## Solution Make the `tracing::instrument` records other function arguments as `fmt::Debug ` while not for those primitive types. However, I'm not good at naming. Maybe the `RecordType` enum and its variant aren't a good name? I'd love to seek suggestions. Thanks.
1 parent 393982e commit b6c07e7

File tree

6 files changed

+119
-31
lines changed

6 files changed

+119
-31
lines changed

tracing-attributes/src/lib.rs

Lines changed: 105 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -96,9 +96,16 @@ use syn::{
9696
/// the function is called.
9797
///
9898
/// Unless overriden, a span with `info` level will be generated.
99-
/// The generated span's name will be the name of the function. Any arguments
100-
/// to that function will be recorded as fields using [`fmt::Debug`]. To skip
101-
/// recording a function's or method's argument, pass the argument's name
99+
/// The generated span's name will be the name of the function.
100+
/// By default, all arguments to the function are included as fields on the
101+
/// span. Arguments that are `tracing` [primitive types] implementing the
102+
/// [`Value` trait] will be recorded as fields of that type. Types which do
103+
/// not implement `Value` will be recorded using [`std::fmt::Debug`].
104+
///
105+
/// [primitive types]: https://docs.rs/tracing/latest/tracing/field/trait.Value.html#foreign-impls
106+
/// [`Value` trait]: https://docs.rs/tracing/latest/tracing/field/trait.Value.html
107+
///
108+
/// To skip recording a function's or method's argument, pass the argument's name
102109
/// to the `skip` argument on the `#[instrument]` macro. For example,
103110
/// `skip` can be used when an argument to an instrumented function does
104111
/// not implement [`fmt::Debug`], or to exclude an argument with a verbose
@@ -400,12 +407,17 @@ fn gen_block(
400407
let span = (|| {
401408
// Pull out the arguments-to-be-skipped first, so we can filter results
402409
// below.
403-
let param_names: Vec<(Ident, Ident)> = params
410+
let param_names: Vec<(Ident, (Ident, RecordType))> = params
404411
.clone()
405412
.into_iter()
406413
.flat_map(|param| match param {
407-
FnArg::Typed(PatType { pat, .. }) => param_names(*pat),
408-
FnArg::Receiver(_) => Box::new(iter::once(Ident::new("self", param.span()))),
414+
FnArg::Typed(PatType { pat, ty, .. }) => {
415+
param_names(*pat, RecordType::parse_from_ty(&*ty))
416+
}
417+
FnArg::Receiver(_) => Box::new(iter::once((
418+
Ident::new("self", param.span()),
419+
RecordType::Debug,
420+
))),
409421
})
410422
// Little dance with new (user-exposed) names and old (internal)
411423
// names of identifiers. That way, we could do the following
@@ -417,13 +429,13 @@ fn gen_block(
417429
// async fn foo(&self, v: usize) {}
418430
// }
419431
// ```
420-
.map(|x| {
432+
.map(|(x, record_type)| {
421433
// if we are inside a function generated by async-trait <=0.1.43, we need to
422434
// take care to rewrite "_self" as "self" for 'user convenience'
423435
if self_type.is_some() && x == "_self" {
424-
(Ident::new("self", x.span()), x)
436+
(Ident::new("self", x.span()), (x, record_type))
425437
} else {
426-
(x.clone(), x)
438+
(x.clone(), (x, record_type))
427439
}
428440
})
429441
.collect();
@@ -458,13 +470,16 @@ fn gen_block(
458470
true
459471
}
460472
})
461-
.map(|(user_name, real_name)| quote!(#user_name = tracing::field::debug(&#real_name)))
473+
.map(|(user_name, (real_name, record_type))| match record_type {
474+
RecordType::Value => quote!(#user_name = #real_name),
475+
RecordType::Debug => quote!(#user_name = tracing::field::debug(&#real_name)),
476+
})
462477
.collect();
463478

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

@@ -853,20 +868,93 @@ impl Parse for Level {
853868
}
854869
}
855870

856-
fn param_names(pat: Pat) -> Box<dyn Iterator<Item = Ident>> {
871+
/// Indicates whether a field should be recorded as `Value` or `Debug`.
872+
enum RecordType {
873+
/// The field should be recorded using its `Value` implementation.
874+
Value,
875+
/// The field should be recorded using `tracing::field::debug()`.
876+
Debug,
877+
}
878+
879+
impl RecordType {
880+
/// Array of primitive types which should be recorded as [RecordType::Value].
881+
const TYPES_FOR_VALUE: [&'static str; 23] = [
882+
"bool",
883+
"str",
884+
"u8",
885+
"i8",
886+
"u16",
887+
"i16",
888+
"u32",
889+
"i32",
890+
"u64",
891+
"i64",
892+
"usize",
893+
"isize",
894+
"NonZeroU8",
895+
"NonZeroI8",
896+
"NonZeroU16",
897+
"NonZeroI16",
898+
"NonZeroU32",
899+
"NonZeroI32",
900+
"NonZeroU64",
901+
"NonZeroI64",
902+
"NonZeroUsize",
903+
"NonZeroIsize",
904+
"Wrapping",
905+
];
906+
907+
/// Parse `RecordType` from [syn::Type] by looking up
908+
/// the [RecordType::TYPES_FOR_VALUE] array.
909+
fn parse_from_ty(ty: &syn::Type) -> Self {
910+
match ty {
911+
syn::Type::Path(syn::TypePath { path, .. })
912+
if path
913+
.segments
914+
.iter()
915+
.last()
916+
.map(|path_segment| {
917+
let ident = path_segment.ident.to_string();
918+
Self::TYPES_FOR_VALUE.iter().any(|&t| t == ident)
919+
})
920+
.unwrap_or(false) =>
921+
{
922+
RecordType::Value
923+
}
924+
syn::Type::Reference(syn::TypeReference { elem, .. }) => {
925+
RecordType::parse_from_ty(&*elem)
926+
}
927+
_ => RecordType::Debug,
928+
}
929+
}
930+
}
931+
932+
fn param_names(pat: Pat, record_type: RecordType) -> Box<dyn Iterator<Item = (Ident, RecordType)>> {
857933
match pat {
858-
Pat::Ident(PatIdent { ident, .. }) => Box::new(iter::once(ident)),
859-
Pat::Reference(PatReference { pat, .. }) => param_names(*pat),
934+
Pat::Ident(PatIdent { ident, .. }) => Box::new(iter::once((ident, record_type))),
935+
Pat::Reference(PatReference { pat, .. }) => param_names(*pat, record_type),
936+
// We can't get the concrete type of fields in the struct/tuple
937+
// patterns by using `syn`. e.g. `fn foo(Foo { x, y }: Foo) {}`.
938+
// Therefore, the struct/tuple patterns in the arguments will just
939+
// always be recorded as `RecordType::Debug`.
860940
Pat::Struct(PatStruct { fields, .. }) => Box::new(
861941
fields
862942
.into_iter()
863-
.flat_map(|FieldPat { pat, .. }| param_names(*pat)),
943+
.flat_map(|FieldPat { pat, .. }| param_names(*pat, RecordType::Debug)),
944+
),
945+
Pat::Tuple(PatTuple { elems, .. }) => Box::new(
946+
elems
947+
.into_iter()
948+
.flat_map(|p| param_names(p, RecordType::Debug)),
864949
),
865-
Pat::Tuple(PatTuple { elems, .. }) => Box::new(elems.into_iter().flat_map(param_names)),
866950
Pat::TupleStruct(PatTupleStruct {
867951
pat: PatTuple { elems, .. },
868952
..
869-
}) => Box::new(elems.into_iter().flat_map(param_names)),
953+
}) => Box::new(
954+
elems
955+
.into_iter()
956+
.flat_map(|p| param_names(p, RecordType::Debug)),
957+
),
870958

871959
// The above *should* cover all cases of irrefutable patterns,
872960
// but we purposefully don't do any funny business here

tracing-attributes/tests/async_fn.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ fn async_fn_with_async_trait_and_fields_expressions() {
182182
.new_span(
183183
span.clone().with_field(
184184
field::mock("_v")
185-
.with_value(&tracing::field::debug(5))
185+
.with_value(&5usize)
186186
.and(field::mock("test").with_value(&tracing::field::debug(10)))
187187
.and(field::mock("val").with_value(&42u64))
188188
.and(field::mock("val2").with_value(&42u64)),

tracing-attributes/tests/destructuring.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ fn destructure_refs() {
7474
let (collector, handle) = collector::mock()
7575
.new_span(
7676
span.clone()
77-
.with_field(field::mock("arg1").with_value(&format_args!("1")).only()),
77+
.with_field(field::mock("arg1").with_value(&1usize).only()),
7878
)
7979
.enter(span.clone())
8080
.exit(span.clone())

tracing-attributes/tests/err.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ fn impl_trait_return_type() {
152152
let (collector, handle) = collector::mock()
153153
.new_span(
154154
span.clone()
155-
.with_field(field::mock("x").with_value(&format_args!("10")).only()),
155+
.with_field(field::mock("x").with_value(&10usize).only()),
156156
)
157157
.enter(span.clone())
158158
.exit(span.clone())

tracing-attributes/tests/fields.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ fn fields() {
6161
fn expr_field() {
6262
let span = span::mock().with_field(
6363
mock("s")
64-
.with_value(&tracing::field::debug("hello world"))
64+
.with_value(&"hello world")
6565
.and(mock("len").with_value(&"hello world".len()))
6666
.only(),
6767
);
@@ -74,7 +74,7 @@ fn expr_field() {
7474
fn two_expr_fields() {
7575
let span = span::mock().with_field(
7676
mock("s")
77-
.with_value(&tracing::field::debug("hello world"))
77+
.with_value(&"hello world")
7878
.and(mock("s.len").with_value(&"hello world".len()))
7979
.and(mock("s.is_empty").with_value(&false))
8080
.only(),
@@ -120,7 +120,7 @@ fn parameters_with_fields() {
120120
let span = span::mock().with_field(
121121
mock("foo")
122122
.with_value(&"bar")
123-
.and(mock("param").with_value(&format_args!("1")))
123+
.and(mock("param").with_value(&1u32))
124124
.only(),
125125
);
126126
run_test(span, || {

tracing-attributes/tests/instrument.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@ fn fields() {
5959
.new_span(
6060
span.clone().with_field(
6161
field::mock("arg1")
62-
.with_value(&format_args!("2"))
63-
.and(field::mock("arg2").with_value(&format_args!("false")))
62+
.with_value(&2usize)
63+
.and(field::mock("arg2").with_value(&false))
6464
.only(),
6565
),
6666
)
@@ -70,8 +70,8 @@ fn fields() {
7070
.new_span(
7171
span2.clone().with_field(
7272
field::mock("arg1")
73-
.with_value(&format_args!("3"))
74-
.and(field::mock("arg2").with_value(&format_args!("true")))
73+
.with_value(&3usize)
74+
.and(field::mock("arg2").with_value(&true))
7575
.only(),
7676
),
7777
)
@@ -108,15 +108,15 @@ fn skip() {
108108
let (collector, handle) = collector::mock()
109109
.new_span(
110110
span.clone()
111-
.with_field(field::mock("arg1").with_value(&format_args!("2")).only()),
111+
.with_field(field::mock("arg1").with_value(&2usize).only()),
112112
)
113113
.enter(span.clone())
114114
.exit(span.clone())
115115
.drop_span(span)
116116
.new_span(
117117
span2
118118
.clone()
119-
.with_field(field::mock("arg1").with_value(&format_args!("3")).only()),
119+
.with_field(field::mock("arg1").with_value(&3usize).only()),
120120
)
121121
.enter(span2.clone())
122122
.exit(span2.clone())
@@ -184,7 +184,7 @@ fn methods() {
184184
span.clone().with_field(
185185
field::mock("self")
186186
.with_value(&format_args!("Foo"))
187-
.and(field::mock("arg1").with_value(&format_args!("42"))),
187+
.and(field::mock("arg1").with_value(&42usize)),
188188
),
189189
)
190190
.enter(span.clone())
@@ -213,7 +213,7 @@ fn impl_trait_return_type() {
213213
let (collector, handle) = collector::mock()
214214
.new_span(
215215
span.clone()
216-
.with_field(field::mock("x").with_value(&format_args!("10")).only()),
216+
.with_field(field::mock("x").with_value(&10usize).only()),
217217
)
218218
.enter(span.clone())
219219
.exit(span.clone())

0 commit comments

Comments
 (0)