Skip to content

Instantly share code, notes, and snippets.

@nivekuil
Created January 26, 2020 20:36
Show Gist options
  • Save nivekuil/1c242e2627cc28e870699a28df5625d6 to your computer and use it in GitHub Desktop.
Save nivekuil/1c242e2627cc28e870699a28df5625d6 to your computer and use it in GitHub Desktop.
#[proc_macro_attribute]
pub fn instrument_err(args: TokenStream, item: TokenStream) -> TokenStream {
let input: ItemFn = syn::parse_macro_input!(item as ItemFn);
let args = syn::parse_macro_input!(args as AttributeArgs);
// these are needed ahead of time, as ItemFn contains the function body _and_
// isn't representable inside a quote!/quote_spanned! macro
// (Syn's ToTokens isn't implemented for ItemFn)
let ItemFn {
attrs,
vis,
block,
sig,
..
} = input;
let Signature {
output: return_type,
inputs: params,
unsafety,
asyncness,
constness,
abi,
ident,
generics:
syn::Generics {
params: gen_params,
where_clause,
..
},
..
} = sig;
// function name
let ident_str = ident.to_string();
// Pull out the arguments-to-be-skipped first, so we can filter results below.
let skips = match skips(&args) {
Ok(skips) => skips,
Err(err) => return quote!(#err).into(),
};
let param_names: Vec<Ident> = params
.clone()
.into_iter()
.flat_map(|param| match param {
FnArg::Typed(PatType { pat, .. }) => param_names(*pat),
FnArg::Receiver(_) => Box::new(iter::once(Ident::new("self", param.span()))),
})
.filter(|ident| !skips.contains(ident))
.collect();
let param_names_clone = param_names.clone();
let level = level(&args);
let target = target(&args);
let span_name = name(&args, ident_str);
// Generate the instrumented function body.
// If the function is an `async fn`, this will wrap it in an async block,
// which is `instrument`ed using `tracing-futures`. Otherwise, this will
// enter the span and then perform the rest of the body.
let body = if asyncness.is_some() {
quote_spanned!(
block.span() =>
tracing_futures::Instrument::instrument(async move {
match async move { #block }.await {
Ok(x) => Ok(x),
Err(e) => {
tracing::error!("{}", e);
Err(e)
}
}
}, __tracing_attr_span).await
)
} else {
quote_spanned!(
block.span() =>
let __tracing_attr_guard = __tracing_attr_span.enter();
match { #block } {
Ok(x) => Ok(x),
Err(e) => {
tracing::error!("{}", e);
Err(e)
}
}
)
};
quote!(
#(#attrs) *
#vis #constness #unsafety #asyncness #abi fn #ident<#gen_params>(#params) #return_type
#where_clause
{
let __tracing_attr_span = tracing::span!(
target: #target,
#level,
#span_name,
#(#param_names = tracing::field::debug(&#param_names_clone)),*
);
#body
}
)
.into()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment