Skip to main content

ttlog_macros/
lib.rs

1use proc_macro::TokenStream;
2use quote::quote;
3use syn::{
4  parse::{Parse, ParseStream},
5  parse_macro_input, Expr, Ident, LitStr, Token,
6};
7
8struct LogInput {
9  kvs: Vec<(Ident, Expr)>,
10  message: Option<LitStr>,
11}
12
13impl Parse for LogInput {
14  fn parse(input: ParseStream) -> syn::Result<Self> {
15    let mut kvs = Vec::new();
16    let mut message = None;
17
18    while !input.is_empty() {
19      if input.peek(LitStr) {
20        if message.is_some() {
21          return Err(input.error("multiple message strings not allowed"));
22        }
23        message = Some(input.parse()?);
24      } else {
25        let key: Ident = input.parse()?;
26        input.parse::<Token![=]>()?;
27        let value: Expr = input.parse()?;
28        kvs.push((key, value));
29      }
30
31      if input.peek(Token![,]) {
32        input.parse::<Token![,]>()?;
33      }
34    }
35
36    Ok(LogInput { kvs, message })
37  }
38}
39
40fn generate_log_call(level: u8, parsed: LogInput) -> TokenStream {
41  let thread_id_expr = quote! {
42    ttlog::utils::current_thread_id_u32() as u8
43  };
44
45  let common_constants = quote! {
46    const LEVEL: u8 = #level;
47    const MODULE: &str = module_path!();
48    const FILE: &str = file!();
49    const POSITION: (u32, u32) = (line!(), column!());
50  };
51
52  let common_statics = quote! {
53    static TARGET_ID: std::sync::OnceLock<u16> = std::sync::OnceLock::new();
54    static FILE_ID: std::sync::OnceLock<u16> = std::sync::OnceLock::new();
55  };
56
57  // Different expansion paths
58  match (parsed.message, parsed.kvs.is_empty()) {
59    // Case 1: Message only
60    (Some(message), true) => quote! {
61      {
62        #common_constants
63        const MESSAGE: &str = #message;
64
65        #common_statics
66        static MESSAGE_ID: std::sync::OnceLock<u16> = std::sync::OnceLock::new();
67
68        if let Some(logger) = ttlog::trace::GLOBAL_LOGGER.get() {
69          if LEVEL >= logger.level.load(std::sync::atomic::Ordering::Relaxed) {
70            let target_id = *TARGET_ID.get_or_init(|| logger.interner.intern_target(MODULE));
71            let message_id = *MESSAGE_ID.get_or_init(|| logger.interner.intern_message(MESSAGE));
72            let file_id = *FILE_ID.get_or_init(|| logger.interner.intern_file(FILE));
73
74            logger.send_event_fast(
75              LEVEL,
76              target_id,
77              std::num::NonZeroU16::new(message_id),
78              #thread_id_expr,
79              file_id,
80              POSITION,
81              None,
82            );
83          }
84        }
85      }
86    },
87
88    // Case 2: Message + KV
89    (Some(message), false) => {
90      let kv_keys: Vec<_> = parsed.kvs.iter().map(|(k, _)| k).collect();
91      let kv_values = parsed.kvs.iter().map(|(_, v)| v);
92      let num_kvs = parsed.kvs.len();
93
94      quote! {
95        {
96          #common_constants
97          const MESSAGE: &str = #message;
98          const NUM_VALUES: usize = #num_kvs;
99
100          #common_statics
101          static MESSAGE_ID: std::sync::OnceLock<u16> = std::sync::OnceLock::new();
102
103          if let Some(logger) = ttlog::trace::GLOBAL_LOGGER.get() {
104            if LEVEL >= logger.level.load(std::sync::atomic::Ordering::Relaxed) {
105              let mut buf = ttlog::kv::KvTransformer::with_capacity(128);
106              {
107                use serde::ser::{SerializeMap, Serializer};
108                let mut ser = serde_json::Serializer::new(&mut buf);
109                let mut map = ser.serialize_map(Some(NUM_VALUES)).unwrap();
110                #({
111                  let wrapper = ttlog::kv::IntOrSer(&#kv_values);
112                  map.serialize_entry(stringify!(#kv_keys), &wrapper).unwrap();
113                })*
114                map.end().unwrap();
115              }
116
117              let target_id = *TARGET_ID.get_or_init(|| logger.interner.intern_target(MODULE));
118              let file_id = *FILE_ID.get_or_init(|| logger.interner.intern_file(FILE));
119              let message_id = *MESSAGE_ID.get_or_init(|| logger.interner.intern_message(MESSAGE));
120              let kv_id = logger.interner.intern_kv(buf.into_inner());
121
122              logger.send_event_fast(
123                LEVEL,
124                target_id,
125                std::num::NonZeroU16::new(message_id),
126                #thread_id_expr,
127                file_id,
128                POSITION,
129                std::num::NonZeroU16::new(kv_id),
130              );
131            }
132          }
133        }
134      }
135    },
136
137    // Case 3: KV only
138    (None, false) => {
139      let kv_keys: Vec<_> = parsed.kvs.iter().map(|(k, _)| k).collect();
140      let kv_values = parsed.kvs.iter().map(|(_, v)| v);
141      let num_kvs = parsed.kvs.len();
142
143      quote! {
144        {
145          #common_constants
146          const NUM_VALUES: usize = #num_kvs;
147
148          #common_statics
149
150          if let Some(logger) = ttlog::trace::GLOBAL_LOGGER.get() {
151            if LEVEL >= logger.level.load(std::sync::atomic::Ordering::Relaxed) {
152              let mut buf = ttlog::kv::KvTransformer::with_capacity(128);
153              {
154                use serde::ser::{SerializeMap, Serializer};
155                let mut ser = serde_json::Serializer::new(&mut buf);
156                let mut map = ser.serialize_map(Some(NUM_VALUES)).unwrap();
157                #({
158                  let wrapper = ttlog::kv::IntOrSer(&#kv_values);
159                  map.serialize_entry(stringify!(#kv_keys), &wrapper).unwrap();
160                })*
161                map.end().unwrap();
162              }
163
164              let kv_id = logger.interner.intern_kv(buf.into_inner());
165              let target_id = *TARGET_ID.get_or_init(|| logger.interner.intern_target(MODULE));
166              let file_id = *FILE_ID.get_or_init(|| logger.interner.intern_file(FILE));
167
168              logger.send_event_fast(
169                LEVEL,
170                target_id,
171                None,
172                #thread_id_expr,
173                file_id,
174                POSITION,
175                std::num::NonZeroU16::new(kv_id),
176              );
177            }
178          }
179        }
180      }
181    },
182
183    // Case 4: Empty call
184    (None, true) => quote! {
185      {
186        #common_constants
187        #common_statics
188
189        if let Some(logger) = ttlog::trace::GLOBAL_LOGGER.get() {
190          if LEVEL >= logger.level.load(std::sync::atomic::Ordering::Relaxed) {
191            let target_id = *TARGET_ID.get_or_init(|| logger.interner.intern_target(MODULE));
192            let file_id = *FILE_ID.get_or_init(|| logger.interner.intern_file(FILE));
193
194            logger.send_event_fast(
195              LEVEL,
196              target_id,
197              None,
198              #thread_id_expr,
199              file_id,
200              POSITION,
201              None,
202            );
203          }
204        }
205      }
206    },
207  }
208  .into()
209}
210
211#[proc_macro]
212pub fn trace(input: TokenStream) -> TokenStream {
213  generate_log_call(0, parse_macro_input!(input as LogInput))
214}
215#[proc_macro]
216pub fn debug(input: TokenStream) -> TokenStream {
217  generate_log_call(1, parse_macro_input!(input as LogInput))
218}
219#[proc_macro]
220pub fn info(input: TokenStream) -> TokenStream {
221  generate_log_call(2, parse_macro_input!(input as LogInput))
222}
223#[proc_macro]
224pub fn warn(input: TokenStream) -> TokenStream {
225  generate_log_call(3, parse_macro_input!(input as LogInput))
226}
227#[proc_macro]
228pub fn error(input: TokenStream) -> TokenStream {
229  generate_log_call(4, parse_macro_input!(input as LogInput))
230}
231#[proc_macro]
232pub fn fatal(input: TokenStream) -> TokenStream {
233  generate_log_call(5, parse_macro_input!(input as LogInput))
234}