1#![recursion_limit = "128"]
2
3extern crate proc_macro;
95extern crate syn;
96use darling::{Error, FromMeta};
97use proc_macro2::TokenStream;
98use quote::{quote, ToTokens};
99
100use syn::punctuated::Punctuated;
101use syn::{
102 parse_macro_input, spanned::Spanned, token, AttributeArgs, Expr, ExprAsync, ExprAwait, ExprBlock, ExprCall, ExprClosure,
103 ExprParen, FnArg, Ident, ItemFn, Meta, NestedMeta, Pat, Result, ReturnType, Stmt, Type, TypePath,
104};
105
106struct FormattedAttributes {
107 ok_expr: TokenStream,
108 err_expr: TokenStream,
109 log_ts: bool,
110 contained_ok_or_err: bool,
111}
112
113impl FormattedAttributes {
114 pub fn parse_attributes(attr: &[NestedMeta], fmt_default: String) -> darling::Result<Self> {
115 OutputOptions::from_list(attr).map(|opts| Self::get_ok_err_streams(opts, fmt_default))
116 }
117
118 fn get_ok_err_streams(att: OutputOptions, fmt_default: String) -> Self {
119 let contained_ok_or_err = att.contains_ok_or_err();
120 let log_ts = att.log_ts();
121 let ok_log = att.ok_log();
122 let err_log = att.err_log();
123 let mut fmt = att.fmt().unwrap_or(fmt_default);
124 if log_ts {
125 fmt += ", ts={:#?}"
126 };
127
128 let ok_expr = match ok_log {
129 Some(loglevel) => {
130 let log_token = get_logger_token(&loglevel);
131 if log_ts {
132 quote! {log::log!(#log_token, #fmt, result, ts); }
133 } else {
134 quote! {log::log!(#log_token, #fmt, result); }
135 }
136 }
137 None => quote! {()},
138 };
139
140 let err_expr = match err_log {
141 Some(loglevel) => {
142 let log_token = get_logger_token(&loglevel);
143 if log_ts {
144 quote! {log::log!(#log_token, #fmt, err, ts); }
145 } else {
146 quote! {log::log!(#log_token, #fmt, err); }
147 }
148 }
149 None => quote! {()},
150 };
151 FormattedAttributes { ok_expr, err_expr, log_ts, contained_ok_or_err }
152 }
153}
154
155#[derive(Default, FromMeta)]
156#[darling(default)]
157struct OutputNamedOptions {
158 ok: Option<Ident>,
159 err: Option<Ident>,
160 fmt: Option<String>,
161 log_ts: Option<bool>,
162}
163
164struct OutputOptions {
165 leading_level: Option<Ident>,
167 named: OutputNamedOptions,
168}
169
170struct InputOptions {
171 level: Ident,
172 fmt: Option<String>,
173}
174
175impl FromMeta for InputOptions {
176 fn from_list(items: &[NestedMeta]) -> darling::Result<Self> {
177 let level;
178 let mut fmt = None;
179 if items.is_empty() {
180 return Err(Error::too_few_items(1));
181 }
182
183 match &items[0] {
184 NestedMeta::Meta(first) => {
185 if let Meta::Path(path) = first {
186 if let Some(ident) = path.get_ident() {
187 level = ident.clone();
188 } else {
189 return Err(Error::unexpected_type("first item should be a log level"));
190 }
191 } else {
192 return Err(Error::unexpected_type("first item should be a log level"));
193 }
194 }
195 NestedMeta::Lit(lit) => return Err(Error::unexpected_lit_type(lit)),
196 }
197
198 if items.len() > 1 {
199 fmt = String::from_nested_meta(&items[1]).ok();
200 }
201
202 Ok(InputOptions { level, fmt })
203 }
204}
205
206impl OutputOptions {
207 pub fn ok_log(&self) -> Option<&Ident> {
208 self.named.ok.as_ref().or_else(|| self.leading_level.as_ref())
209 }
210
211 pub fn err_log(&self) -> Option<&Ident> {
212 self.named.err.as_ref().or_else(|| self.leading_level.as_ref())
213 }
214
215 pub fn contains_ok_or_err(&self) -> bool {
216 self.named.ok.is_some() || self.named.err.is_some()
217 }
218
219 pub fn log_ts(&self) -> bool {
220 self.named.log_ts.unwrap_or(false)
221 }
222
223 pub fn fmt(&self) -> Option<String> {
224 self.named.fmt.clone()
225 }
226}
227
228impl FromMeta for OutputOptions {
229 fn from_list(items: &[NestedMeta]) -> darling::Result<Self> {
230 if items.is_empty() {
231 return Err(darling::Error::too_few_items(1));
232 }
233
234 let mut leading_level = None;
235
236 if let NestedMeta::Meta(first) = &items[0] {
237 if let Meta::Path(path) = first {
238 leading_level = path.get_ident().cloned();
239 }
240 }
241
242 let named =
243 if leading_level.is_some() { OutputNamedOptions::from_list(&items[1..])? } else { OutputNamedOptions::from_list(items)? };
244
245 Ok(OutputOptions { leading_level, named })
246 }
247}
248
249pub(crate) fn is_result_type(ty: &TypePath) -> bool {
253 if let Some(segment) = ty.path.segments.iter().last() {
254 segment.ident == "Result"
255 } else {
256 false
257 }
258}
259
260fn check_if_return_result(f: &ItemFn) -> bool {
261 if let ReturnType::Type(_, t) = &f.sig.output {
262 return match t.as_ref() {
263 Type::Path(path) => is_result_type(path),
264 _ => false,
265 };
266 }
267
268 false
269}
270
271fn get_logger_token(att: &Ident) -> TokenStream {
272 let attr_str = att.to_string().to_lowercase();
274 let mut attr_char = attr_str.chars();
275 let attr_str = attr_char.next().unwrap().to_uppercase().to_string() + attr_char.as_str();
276 let att_str = Ident::new(&attr_str, att.span());
277 quote!(log::Level::#att_str)
278}
279
280fn make_closure(original: &ItemFn) -> Expr {
281 match original.sig.asyncness {
282 Some(asyncness) => Expr::Await(ExprAwait {
283 attrs: Default::default(),
284 await_token: Default::default(),
285 dot_token: Default::default(),
286 base: Box::new(syn::Expr::Async(ExprAsync {
287 attrs: Default::default(),
288 capture: Some(token::Move { span: original.span() }),
289 block: *original.block.clone(),
290 async_token: asyncness,
291 })),
292 }),
293 None => Expr::Call(ExprCall {
294 attrs: Default::default(),
295 args: Default::default(),
296 paren_token: Default::default(),
297 func: Box::new(syn::Expr::Paren(ExprParen {
298 attrs: Default::default(),
299 paren_token: Default::default(),
300 expr: Box::new(syn::Expr::Closure(ExprClosure {
301 attrs: Default::default(),
302 asyncness: Default::default(),
303 movability: Default::default(),
304 capture: Some(token::Move { span: original.span() }),
305 or1_token: Default::default(),
306 inputs: Default::default(),
307 or2_token: Default::default(),
308 output: ReturnType::Default,
309 body: Box::new(Expr::Block(ExprBlock {
310 attrs: Default::default(),
311 label: Default::default(),
312 block: *original.block.clone(),
313 })),
314 })),
315 })),
316 }),
317 }
318}
319
320fn replace_function_headers(original: ItemFn, new: &mut ItemFn) {
321 let block = new.block.clone();
322 *new = original;
323 new.block = block;
324}
325
326fn generate_function(closure: &Expr, expressions: FormattedAttributes, result: bool) -> Result<ItemFn> {
327 let FormattedAttributes { ok_expr, err_expr, log_ts, contained_ok_or_err } = expressions;
328 let result = result || contained_ok_or_err;
329 let code = if log_ts {
330 if result {
331 quote! {
332 fn temp() {
333 let instant = std::time::Instant::now();
334 let result = #closure;
335 let ts = instant.elapsed();
336 result.map(|result| { #ok_expr; result })
337 .map_err(|err| { #err_expr; err })
338 }
339 }
340 } else {
341 quote! {
342 fn temp() {
343 let instant = std::time::Instant::now();
344 let result = #closure;
345 let ts = instant.elapsed();
346 #ok_expr;
347 result
348 }
349 }
350 }
351 } else if result {
352 quote! {
353 fn temp() {
354 let result = #closure;
355 result.map(|result| { #ok_expr; result })
356 .map_err(|err| { #err_expr; err })
357 }
358 }
359 } else {
360 quote! {
361 fn temp() {
362 let result = #closure;
363 #ok_expr;
364 result
365 }
366 }
367 };
368
369 syn::parse2(code)
370}
371
372#[proc_macro_attribute]
387pub fn logfn(attr: proc_macro::TokenStream, item: proc_macro::TokenStream) -> proc_macro::TokenStream {
388 let attr = parse_macro_input!(attr as AttributeArgs);
389 let original_fn: ItemFn = parse_macro_input!(item as ItemFn);
390 let fmt_default = original_fn.sig.ident.to_string() + "() => {:?}";
391 let parsed_attributes: FormattedAttributes = match FormattedAttributes::parse_attributes(&attr, fmt_default) {
392 Ok(val) => val,
393 Err(err) => {
394 return err.write_errors().into();
395 }
396 };
397 let closure = make_closure(&original_fn);
398 let is_result = check_if_return_result(&original_fn);
399 let mut new_fn = generate_function(&closure, parsed_attributes, is_result).expect("Failed Generating Function");
400 replace_function_headers(original_fn, &mut new_fn);
401 new_fn.into_token_stream().into()
402}
403
404#[proc_macro_attribute]
419pub fn logfn_inputs(attr: proc_macro::TokenStream, item: proc_macro::TokenStream) -> proc_macro::TokenStream {
420 let mut original_fn: ItemFn = parse_macro_input!(item as ItemFn);
421
422 let attr = parse_macro_input!(attr as AttributeArgs);
423 let parsed_attributes = match InputOptions::from_list(&attr) {
424 Ok(val) => val,
425 Err(err) => {
426 return err.write_errors().into();
427 }
428 };
429
430 let mut stmts = match log_fn_inputs(&original_fn, parsed_attributes) {
431 Ok(input_log) => vec![input_log],
432 Err(e) => return e.to_compile_error().into(),
433 };
434
435 stmts.extend(original_fn.block.stmts);
436 original_fn.block.stmts = stmts;
437 original_fn.into_token_stream().into()
438}
439
440fn log_fn_inputs(func: &ItemFn, attr: InputOptions) -> syn::Result<Stmt> {
441 let fn_name = func.sig.ident.to_string();
442 let inputs: Vec<Ident> = func
443 .sig
444 .inputs
445 .iter()
446 .cloned()
447 .map(|arg| match arg {
448 FnArg::Receiver(arg) => arg.self_token.into(),
449 FnArg::Typed(pat_type) => {
450 if let Pat::Ident(ident) = *pat_type.pat {
451 ident.ident
452 } else {
453 unimplemented!()
454 }
455 }
456 })
457 .collect();
458
459 let items: Punctuated<_, token::Comma> = inputs.iter().cloned().collect();
460
461 let level = get_logger_token(&attr.level);
462 let fmt = attr.fmt.unwrap_or_else(|| {
463 let mut fmt = String::with_capacity(inputs.len() * 9);
464 fmt.push_str(&fn_name);
465 fmt.push('(');
466
467 for input in inputs {
468 fmt.push_str(&input.to_string());
469 fmt.push_str(": {:?},");
470 }
471 fmt.pop(); fmt.push(')');
473 fmt
474 });
475
476 let res = quote! {
477 log::log!(#level, #fmt, #items);
478 };
479 syn::parse2(res)
480}
481
482#[cfg(test)]
483mod tests {
484 use syn::parse_quote;
485
486 use super::is_result_type;
487
488 #[test]
489 fn result_type() {
490 assert!(is_result_type(&parse_quote!(Result<T, E>)));
491 assert!(is_result_type(&parse_quote!(std::result::Result<T, E>)));
492 assert!(is_result_type(&parse_quote!(fmt::Result)));
493 }
494}