kvlog_macros/
lib.rs

1#![allow(dead_code)]
2extern crate proc_macro;
3mod lit;
4
5macro_rules! builtin_keys {
6    ($($key: ident),* $(,)?) => {
7        #[allow(non_camel_case_types)]
8        #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
9        #[repr(u8)]
10        enum StaticKey{
11            $($key,)*
12        }
13        fn get_static_key(key: &str) -> Option<StaticKey> {
14            match key {
15                $(stringify!($key) => Some(StaticKey::$key),)*
16                _ => None
17            }
18        }
19    };
20}
21
22builtin_keys! {
23    msg,
24    err,
25    error,
26    cause,
27    method,
28    status,
29    size,
30    time,
31    count,
32    total,
33    ms,
34    id,
35    user_id,
36    object_id,
37    caller,
38    target,
39    duration,
40    timezone,
41    content_type,
42    conn_id,
43    path,
44    length,
45    on,
46    kind,
47    sensor_id,
48    handler,
49    timestamp,
50    elapsed,
51    camera_id,
52    system_id,
53    next
54}
55
56use lit::{literal_inline, InlineKind};
57use proc_macro::{
58    token_stream::IntoIter, Delimiter, Group, Ident, Literal, Punct, Spacing, Span, TokenStream,
59    TokenTree,
60};
61
62fn chr(ch: char) -> TokenTree {
63    TokenTree::Punct(Punct::new(ch, Spacing::Alone))
64}
65fn j(ch: char) -> TokenTree {
66    TokenTree::Punct(Punct::new(ch, Spacing::Joint))
67}
68
69fn braced(ts: TokenStream) -> TokenTree {
70    TokenTree::Group(Group::new(Delimiter::Brace, ts))
71}
72
73macro_rules! tok {
74    ($ident:ident) => {
75        TokenTree::Ident(Ident::new(stringify!($ident), Span::call_site()))
76    };
77    (_) => {
78        TokenTree::Ident(Ident::new("_", Span::call_site()))
79    };
80    (()) => {
81        TokenTree::Group(Group::new(Delimiter::Parenthesis, TokenStream::default()))
82    };
83    (( $($tt:tt)*) ) => {
84        TokenTree::Group(Group::new(Delimiter::Parenthesis, TokenStream::from_iter(toks!($($tt)*))))
85    };
86    ({$($tt:tt)*}) => { $($tt)* };
87    ([$com:literal]) => {
88        TokenTree::Literal(Literal::string($com))
89    };
90    ([$com:literal; $ident:ident]) => {
91        TokenTree::Literal(Literal::$ident($com))
92    };
93    ([_, $span: expr]) => {
94        TokenTree::Ident(Ident::new("_", $span))
95    };
96    ([$ident:ident, $span: expr]) => {
97        TokenTree::Ident(Ident::new(stringify!($ident), $span))
98    };
99    (<) => { chr('<') };
100    (%) => { j(':') };
101    (:) => { chr(':') };
102    (>) => { chr('>') };
103    (.) => { chr('.') };
104    (;) => { chr(';') };
105    (&) => { chr('&') };
106    (=) => { chr('=') };
107    (,) => { chr(',') };
108    ($com:ident; $tt:tt) => {
109        TokenTree::from($com.$tt.clone())
110    };
111}
112
113macro_rules! toks {
114    ($($tt:tt)*) => {
115        [$(tok!($tt)),*]
116    }
117}
118
119fn is_char(tt: &TokenTree, ch: char) -> bool {
120    if let TokenTree::Punct(p) = tt {
121        if p.as_char() == ch {
122            return true;
123        }
124    }
125    false
126}
127
128// could optize this but won't worry about it for now.
129#[derive(Default)]
130struct SpanAttributes {
131    current: Option<Vec<TokenTree>>,
132    start: Option<Vec<TokenTree>>,
133    end: Option<Vec<TokenTree>>,
134    parent: Option<Vec<TokenTree>>,
135}
136
137struct Codegen {
138    out: TokenStream,
139    collect: Vec<TokenTree>,
140    builder: Ident,
141    error: Option<(Span, Box<str>)>,
142    message: Option<Box<str>>,
143    span: SpanAttributes,
144}
145
146enum ExprKind {
147    Debug,
148    Display,
149    Normal,
150}
151
152fn munch_expr(input: &mut IntoIter, output: &mut Vec<TokenTree>) -> ExprKind {
153    output.clear();
154    let mut kind = ExprKind::Normal;
155    let Some(first) = input.next() else {
156        return kind;
157    };
158    'lemmy: {
159        if let TokenTree::Punct(tt) = &first {
160            match tt.as_char() {
161                '%' => {
162                    kind = ExprKind::Display;
163                    break 'lemmy;
164                }
165                '?' => {
166                    kind = ExprKind::Debug;
167                    break 'lemmy;
168                }
169                _ => (),
170            }
171        }
172        output.push(first);
173    }
174    while let Some(tok) = input.next() {
175        if is_char(&tok, ',') {
176            break;
177        }
178        output.push(tok);
179    }
180    return kind;
181}
182
183impl Codegen {
184    fn new(level: &str, builder: Ident) -> Codegen {
185        let mut codegen = Codegen {
186            out: TokenStream::new(),
187            collect: Vec::with_capacity(64),
188            builder,
189            error: None,
190            message: None,
191            span: SpanAttributes::default(),
192        };
193        let log_level = TokenTree::Ident(Ident::new(level, Span::call_site()));
194        codegen.out.extend(toks![
195            use kvlog%:encoding%:Encode;
196            let mut log = kvlog%:global_logger();
197            let mut {codegen.fields()} = log.encoder.append_now(
198                kvlog%:LogLevel%:{log_level}
199            );
200        ]);
201        codegen
202    }
203    fn fields(&self) -> TokenTree {
204        self.builder.clone().into()
205    }
206    fn runtime_field_from_key(&self, field: Ident) -> TokenTree {
207        let field_text = field.to_string();
208        if let Some(static_key) = get_static_key(&field_text) {
209            let key = TokenTree::Literal(Literal::u8_unsuffixed(static_key as u8));
210            TokenTree::Group(Group::new(
211                Delimiter::Parenthesis,
212                TokenStream::from_iter(toks![{ self.fields() }.raw_key({ key })]),
213            ))
214        } else {
215            let key = TokenTree::Literal(Literal::string(&field_text));
216            TokenTree::Group(Group::new(
217                Delimiter::Parenthesis,
218                TokenStream::from_iter(toks![{ self.fields() }.dynamic_key({ key })]),
219            ))
220        }
221    }
222    fn emit_field_value(&mut self, expr: TokenTree, field: Ident) {
223        let method = TokenTree::Ident(Ident::new("encode_log_value_into", field.span()));
224        self.collect.extend_from_slice(&toks![
225            { expr }.{method}{self.runtime_field_from_key(field)};
226        ]);
227    }
228    fn emit_display_field_value(&mut self, expr: TokenTree, field: Ident) {
229        self.collect.extend_from_slice(&toks![
230            { self.runtime_field_from_key(field) }.value_via_display(&{ expr });
231        ]);
232    }
233    fn emit_debug_field_value(&mut self, expr: TokenTree, field: Ident) {
234        self.collect.extend_from_slice(&toks![
235            { self.runtime_field_from_key(field) }.value_via_debug(&{ expr });
236        ]);
237    }
238    fn error(&mut self, span: Span, msg: &str) {
239        if self.error.is_none() {
240            self.error = Some((span, msg.into()))
241        }
242    }
243    fn error_fmt(&mut self, span: Span, msg: Box<str>) {
244        if self.error.is_none() {
245            self.error = Some((span, msg))
246        }
247    }
248    fn finish_creating(mut self) -> TokenStream {
249        let target_key = TokenTree::Literal(Literal::u8_unsuffixed(StaticKey::target as u8));
250        let message_key = TokenTree::Literal(Literal::u8_unsuffixed(StaticKey::msg as u8));
251        self.collect.extend_from_slice(&toks![
252            (module_path {chr('!')} ()).encode_log_value_into(
253                {self.fields()}.raw_key({target_key})
254            );
255
256        ]);
257        let fields = self.fields();
258        self.out.extend(std::mem::take(&mut self.collect));
259
260        if let Some(message) = &self.message {
261            self.out.extend(toks![
262                ({TokenTree::Literal(Literal::string(message))}).encode_log_value_into(
263                    {fields.clone()}.raw_key({message_key})
264                );
265            ]);
266        };
267        let base_set = (self.span.current.is_some() as u8)
268            + (self.span.start.is_some() as u8)
269            + (self.span.end.is_some() as u8);
270
271        if base_set > 1 {
272            self.error(
273                Span::call_site(),
274                "More then one 'start', 'end', 'current' field for span specified.".into(),
275            )
276        }
277        if self.span.parent.is_some() && self.span.start.is_none() {
278            self.error(
279                Span::call_site(),
280                "`span.parent` can only be specified with span.start".into(),
281            )
282        }
283
284        if let Some(current) = self.span.current {
285            let tok = TokenTree::Group(Group::new(
286                Delimiter::Parenthesis,
287                TokenStream::from_iter(current),
288            ));
289            self.out.extend(toks![ {fields}.apply_span {tok}; ]);
290        } else if let Some(start) = self.span.start {
291            if let Some(parent) = self.span.parent {
292                let mut args = TokenStream::from_iter(start);
293                args.extend([TokenTree::Punct(Punct::new(',', Spacing::Alone))]);
294                args.extend(parent);
295                let tok = TokenTree::Group(Group::new(Delimiter::Parenthesis, args));
296                self.out
297                    .extend(toks![ {fields}.start_span_with_parent {tok}; ]);
298            } else {
299                let tok = TokenTree::Group(Group::new(
300                    Delimiter::Parenthesis,
301                    TokenStream::from_iter(start),
302                ));
303                self.out.extend(toks![ {fields}.start_span {tok}; ]);
304            }
305        } else if let Some(end) = self.span.end {
306            let tok = TokenTree::Group(Group::new(
307                Delimiter::Parenthesis,
308                TokenStream::from_iter(end),
309            ));
310            self.out.extend(toks![ {fields}.end_span {tok}; ]);
311        } else {
312            self.out.extend(toks![ {fields}.apply_current_span(); ]);
313        }
314
315        self.out.extend(toks![ log.poke(); ]);
316
317        if let Some((span, msg)) = self.error {
318            let mut group = TokenTree::Group(Group::new(
319                Delimiter::Parenthesis,
320                TokenStream::from_iter([TokenTree::Literal(Literal::string(&msg))]),
321            ));
322            let mut punc = TokenTree::Punct(Punct::new('!', Spacing::Alone));
323            punc.set_span(span);
324            group.set_span(span);
325
326            self.out.extend([
327                TokenTree::Ident(Ident::new("compile_error", span)),
328                punc,
329                group,
330                TokenTree::Punct(Punct::new(';', Spacing::Alone)),
331            ]);
332            //     }
333        }
334        TokenStream::from_iter([braced(self.out)])
335    }
336    fn parse_if(&mut self, ident: Ident, tokens: &mut IntoIter) {
337        let span = ident.span();
338        self.collect.push(ident.into());
339        loop {
340            let Some(tok) = tokens.next() else {
341                self.error(span, "Expected Block for if statement".into());
342                return;
343            };
344            if let TokenTree::Group(group) = &tok {
345                if group.delimiter() == Delimiter::Brace {
346                    let mut tmp = std::mem::take(&mut self.collect);
347                    self.parse_values(group.stream().into_iter());
348                    std::mem::swap(&mut tmp, &mut self.collect);
349                    self.collect.push(TokenTree::Group(Group::new(
350                        Delimiter::Brace,
351                        TokenStream::from_iter(tmp),
352                    )));
353                    return;
354                }
355            }
356            self.collect.push(tok);
357        }
358    }
359    fn parse_span_attrib(&mut self, span: Ident, tokens: &mut IntoIter) {
360        let Some(tok) = tokens.next() else {
361            self.error(
362                span.span(),
363                "Eof will parsing span attribute expected".into(),
364            );
365            return;
366        };
367        let TokenTree::Ident(ident) = tok else {
368            self.error(Span::call_site(), "Expected span field".into());
369            return;
370        };
371        let Some(eq_tok) = tokens.next() else {
372            self.error(ident.span(), "Eof after span expected `=`".into());
373            return;
374        };
375        if !is_char(&eq_tok, '=') {
376            self.error(eq_tok.span(), "expected `=` after span field".into());
377            return;
378        }
379        let field = ident.to_string();
380        let mut output = Vec::default();
381        let _expr = munch_expr(tokens, &mut output);
382        let attrib = match field.as_str() {
383            "current" => &mut self.span.current,
384            "start" => &mut self.span.start,
385            "end" => &mut self.span.end,
386            "parent" => &mut self.span.parent,
387            other => {
388                self.error_fmt(ident.span(), format!("unknown span field {}", other).into());
389                return;
390            }
391        };
392        if attrib.is_some() {
393            self.error_fmt(
394                ident.span(),
395                format!("Span `{}` specified more than once", field.as_str()).into(),
396            );
397            return;
398        }
399        *attrib = Some(output);
400    }
401
402    fn parse_values(&mut self, mut tokens: IntoIter) {
403        let mut tmp: Vec<TokenTree> = Vec::with_capacity(8);
404        let mut last_was_block_set = false;
405        while let Some(tok) = tokens.next() {
406            let last_was_block = last_was_block_set;
407            last_was_block_set = false;
408            let key = match tok {
409                TokenTree::Group(value) => {
410                    self.error(value.span(), "unexpected span");
411                    continue;
412                }
413                TokenTree::Ident(ident) => ident,
414                TokenTree::Punct(punct) => {
415                    let ch = punct.as_char();
416                    if ch == ',' && last_was_block {
417                        continue;
418                    }
419                    if ch != '?' && ch != '%' {
420                        self.error(punct.span(), "Unexpected punct");
421                        continue;
422                    }
423                    let Some(tt) = tokens.next() else {
424                        self.error(punct.span(), "Unexpected EOF");
425                        return;
426                    };
427                    let TokenTree::Ident(key_expr) = tt else {
428                        self.error(tt.span(), "Expected name");
429                        continue;
430                    };
431
432                    if ch == '?' {
433                        self.emit_debug_field_value(TokenTree::Ident(key_expr.clone()), key_expr);
434                    } else {
435                        self.emit_display_field_value(TokenTree::Ident(key_expr.clone()), key_expr);
436                    }
437
438                    if let Some(tt) = tokens.next() {
439                        if !is_char(&tt, ',') {
440                            self.error(tt.span(), "Expected ,");
441                            return;
442                        }
443                    }
444                    continue;
445                }
446                TokenTree::Literal(lit) => {
447                    if let InlineKind::String(lit) = literal_inline(lit.to_string()) {
448                        self.message = Some(lit);
449                    } else {
450                        self.error(lit.span(), "Expected string message ");
451                        return;
452                    }
453                    if let Some(tt) = tokens.next() {
454                        if !is_char(&tt, ',') {
455                            self.error(tt.span(), "Expected ,");
456                            continue;
457                        }
458                    }
459                    continue;
460                }
461            };
462            let key_name = key.to_string();
463            if key_name == "if" {
464                self.parse_if(key, &mut tokens);
465                last_was_block_set = true;
466                continue;
467            }
468            let Some(tt) = tokens.next() else {
469                self.emit_field_value(TokenTree::Ident(key.clone()), key);
470                return;
471            };
472            match &tt {
473                TokenTree::Punct(punct) => {
474                    let ch = punct.as_char();
475                    if ch == ',' {
476                        self.emit_field_value(TokenTree::Ident(key.clone()), key);
477                        continue;
478                    }
479                    if ch == '.' && key_name == "span" {
480                        self.parse_span_attrib(key, &mut tokens);
481                        continue;
482                    }
483                    if ch != '=' {
484                        self.error(punct.span(), "Expected =");
485                        continue;
486                    }
487                    tmp.clear();
488                    let kind = munch_expr(&mut tokens, &mut tmp);
489                    if tmp.is_empty() {
490                        self.error(punct.span(), "Expected Expression following =");
491                        continue;
492                    }
493                    let expr = TokenStream::from_iter(tmp.drain(..));
494                    let expr = TokenTree::Group(Group::new(Delimiter::Parenthesis, expr));
495                    match kind {
496                        ExprKind::Debug => {
497                            self.emit_debug_field_value(expr, key);
498                        }
499                        ExprKind::Display => {
500                            self.emit_display_field_value(expr, key);
501                        }
502                        ExprKind::Normal => {
503                            self.emit_field_value(expr, key);
504                        }
505                    }
506                }
507                _ => {
508                    self.error(tt.span(), "Expected =");
509                    continue;
510                }
511            }
512        }
513    }
514}
515
516fn emit_log(input: TokenStream, level: &str) -> TokenStream {
517    let toks = input.into_iter();
518    let mut codegen = Codegen::new(level, Ident::new("fields", Span::call_site()));
519    codegen.parse_values(toks);
520    codegen.finish_creating()
521}
522
523#[proc_macro]
524pub fn info(input: TokenStream) -> TokenStream {
525    emit_log(input, "Info")
526}
527
528#[proc_macro]
529pub fn debug(input: TokenStream) -> TokenStream {
530    emit_log(input, "Debug")
531}
532
533#[proc_macro]
534pub fn error(input: TokenStream) -> TokenStream {
535    emit_log(input, "Error")
536}
537
538#[proc_macro]
539pub fn warn(input: TokenStream) -> TokenStream {
540    emit_log(input, "Warn")
541}