quick_tracing_derive/
lib.rs1use proc_macro2::TokenStream;
6use quote::quote;
7use syn::{parse_macro_input, ItemFn};
8
9#[derive(deluxe::ParseMetaItem)]
14struct LoggerBuilder {
15 file: Option<String>,
16 test: Option<String>,
17 #[deluxe(default = true)]
18 stdio: bool,
19 #[deluxe(default = "TRACE".to_string())]
20 level: String,
21}
22
23pub(crate) fn parse_level(level: &str) -> TokenStream {
24 let level = level.to_ascii_lowercase();
25 let level = match level.as_str() {
26 "trace" => quote! { tracing::metadata::LevelFilter::TRACE },
27 "debug" => quote! { tracing::metadata::LevelFilter::DEBUG },
28 "info" => quote! { tracing::metadata::LevelFilter::INFO },
29 "warn" => quote! { tracing::metadata::LevelFilter::WARN },
30 "error" => quote! { tracing::metadata::LevelFilter::ERROR },
31 unknown => {
32 panic!("The log level must be one of TRACE|DEBUG|INFO|WARN|ERROR. But got {unknown}")
33 }
34 };
35 quote! { .filter(#level) }
36}
37
38#[proc_macro_attribute]
67pub fn init(
68 attr: proc_macro::TokenStream,
69 item: proc_macro::TokenStream,
70) -> proc_macro::TokenStream {
71 let LoggerBuilder {
72 file,
73 test,
74 stdio,
75 level,
76 } = match deluxe::parse::<LoggerBuilder>(attr) {
77 Ok(desc) => desc,
78 Err(e) => return e.into_compile_error().into(),
79 };
80
81 if file.is_some() && test.is_some() {
82 panic!("Use either one of `test` or `file`.")
83 };
84
85 let test = test.map_or(quote! {}, |name| {
86 quote! {
87 .test_name(#name).expect("Failed to create log directory.")
88 }
89 });
90 let file = file.map_or(quote! {}, |path| {
91 quote! {
92 .file({
93 let _ = std::path::Path::new(#path).parent().map(|dir| std::fs::create_dir_all(dir));
94 #path
95 })
96 }
97 });
98 let stdio = match stdio {
99 true => quote! { .stdio() },
100 false => quote!(),
101 };
102
103 let level_filter = parse_level(&level);
104
105 let logger_init = quote! {
106 let (worker_guard, default_guard) = quick_tracing::builder::LoggerBuilder::new()
107 #stdio
108 #test
109 #file
110 #level_filter
111 .build()
112 .expect("Failed to initialize logger");
113 };
114
115 let mut item_fn = parse_macro_input!(item as ItemFn);
116 item_fn
117 .block
118 .stmts
119 .insert(0, syn::parse2(logger_init).unwrap());
120
121 quote! { #item_fn }.into()
122}
123
124#[proc_macro_attribute]
155pub fn try_init(
156 attr: proc_macro::TokenStream,
157 item: proc_macro::TokenStream,
158) -> proc_macro::TokenStream {
159 let LoggerBuilder {
160 file,
161 test,
162 stdio,
163 level,
164 } = match deluxe::parse::<LoggerBuilder>(attr) {
165 Ok(desc) => desc,
166 Err(e) => return e.into_compile_error().into(),
167 };
168
169 if file.is_some() && test.is_some() {
170 panic!("Use either one of `test` or `file`.")
171 };
172
173 let test = test.map_or(quote! {}, |name| {
174 quote! {
175 .test_name(#name)?
176 }
177 });
178 let file = file.map_or(quote! {}, |path| {
179 quote! {
180 .file({
181 if let Some(dir) = std::path::Path::new(#path).parent() {
182 std::fs::create_dir_all(dir)?;
183 };
184 #path
185 })
186 }
187 });
188 let stdio = match stdio {
189 true => quote! { .stdio() },
190 false => quote!(),
191 };
192
193 let level_filter = parse_level(&level);
194
195 let logger_init = quote! {
196 let (worker_guard, default_guard) = quick_tracing::builder::LoggerBuilder::new()
197 #stdio
198 #test
199 #file
200 #level_filter
201 .build()?;
202 };
203
204 let mut item_fn = parse_macro_input!(item as ItemFn);
205 item_fn
206 .block
207 .stmts
208 .insert(0, syn::parse2(logger_init).unwrap());
209
210 quote! { #item_fn }.into()
211}