Created
January 26, 2020 20:36
-
-
Save nivekuil/1c242e2627cc28e870699a28df5625d6 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#[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