trace_tools_attributes/
lib.rs1#![deny(missing_docs, warnings)]
7
8use proc_macro2::TokenStream;
9use quote::{quote, quote_spanned};
10use syn::{
11 parse::{Parse, ParseStream},
12 parse_macro_input,
13 spanned::Spanned,
14 Attribute, Signature, Visibility,
15};
16
17#[proc_macro_attribute]
66pub fn observe(
67 _args: proc_macro::TokenStream,
68 input: proc_macro::TokenStream,
69) -> proc_macro::TokenStream {
70 let observe_impl = parse_macro_input!(input as ObserveImpl);
71 observe_impl.gen_tokens().into()
72}
73
74#[derive(Debug)]
75struct ObserveImpl {
76 attributes: Vec<Attribute>,
77 visibility: Visibility,
78 signature: Signature,
79 block: TokenStream,
80}
81
82impl Parse for ObserveImpl {
83 fn parse(input: ParseStream) -> syn::Result<Self> {
84 let attributes = input.call(Attribute::parse_outer)?;
85 let visibility = input.parse()?;
86 let signature = input.parse()?;
87 let block = input.parse()?;
88
89 Ok(Self {
90 attributes,
91 visibility,
92 signature,
93 block,
94 })
95 }
96}
97
98impl ObserveImpl {
99 fn gen_tokens(self) -> TokenStream {
100 let ObserveImpl {
101 attributes,
102 visibility,
103 signature,
104 block,
105 } = self;
106
107 let fn_name = signature.ident.to_string();
108
109 let block = match &signature.asyncness {
110 Some(_) => Self::gen_async_block(fn_name, &block),
111 None => Self::gen_block(fn_name, &block),
112 };
113
114 quote! {
115 #(#attributes)*
116 #visibility #signature
117 {
118 #block
119 }
120 }
121 }
122
123 fn gen_block(fn_name: String, block: &TokenStream) -> TokenStream {
124 let span = quote! {
125 {
126 let location = std::panic::Location::caller();
127
128 tracing::trace_span!(
129 target: "trace_tools::observe",
130 "observed",
131 observed.name = #fn_name,
132 loc.file = location.file(),
133 loc.line = location.line(),
134 loc.col = location.column(),
135 )
136 }
137 };
138
139 quote_spanned! {
140 block.span() => {
141 let span = #span;
142 let _guard = span.enter();
143 #block
144 }
145 }
146 }
147
148 fn gen_async_block(fn_name: String, block: &TokenStream) -> TokenStream {
149 let observed_future = quote_spanned! {
150 block.span() => async move #block
151 };
152
153 quote! {
154 trace_tools::Observe::observe(#observed_future, #fn_name).await
155 }
156 }
157}
158
159#[cfg(test)]
160mod tests {
161 use super::{quote, ObserveImpl, TokenStream};
162
163 #[test]
164 fn observe_async() {
165 let input: TokenStream = quote! {
166 async fn test_observe_fn() {
167 println!("observing this function");
168 }
169 };
170
171 let observe_impl = syn::parse2::<ObserveImpl>(input);
172 assert!(observe_impl.is_ok());
173
174 let output_tokens = observe_impl.unwrap().gen_tokens();
175 let expected_tokens: TokenStream = quote! {
176 async fn test_observe_fn() {
177 trace_tools::Observe::observe(
178 async move {
179 println!("observing this function");
180 },
181 "test_observe_fn"
182 ).await
183 }
184 };
185
186 assert_eq!(output_tokens.to_string(), expected_tokens.to_string());
187 }
188
189 #[test]
190 fn observe() {
191 let input: TokenStream = quote! {
192 fn test_observe_fn() {
193 println!("observing this function");
194 }
195 };
196
197 let observe_impl = syn::parse2::<ObserveImpl>(input);
198 assert!(observe_impl.is_ok());
199
200 let output_tokens = observe_impl.unwrap().gen_tokens();
201 let expected_tokens: TokenStream = quote! {
202 fn test_observe_fn() {
203 {
204 let span = {
205 let location = std::panic::Location::caller();
206
207 tracing::trace_span!(
208 target: "trace_tools::observe",
209 "observed",
210 observed.name = "test_observe_fn",
211 loc.file = location.file(),
212 loc.line = location.line(),
213 loc.col = location.column(),
214 )
215 };
216
217 let _guard = span.enter();
218 {
219 println!("observing this function");
220 }
221 }
222 }
223 };
224
225 assert_eq!(output_tokens.to_string(), expected_tokens.to_string());
226 }
227}