From 666358f85bf8ecc7fd6b93a4f40154450b7171d7 Mon Sep 17 00:00:00 2001 From: KodrAus Date: Sun, 24 Mar 2024 10:49:14 +1000 Subject: [PATCH] support emit attributes on stable --- core/src/ctxt.rs | 1 - core/src/path.rs | 10 +- core/src/str.rs | 5 +- core/src/template.rs | 2 - core/src/timestamp.rs | 6 + core/src/value.rs | 2 +- core/src/well_known.rs | 4 +- macros/src/capture.rs | 9 +- macros/src/fmt.rs | 18 +-- macros/src/hook.rs | 42 ++++++- macros/src/lib.rs | 251 ++++++++++++++++++++++++--------------- macros/src/props.rs | 2 +- macros/src/template.rs | 29 +++-- src/lib.rs | 6 +- tests/smoke-test/main.rs | 2 - 15 files changed, 254 insertions(+), 135 deletions(-) diff --git a/core/src/ctxt.rs b/core/src/ctxt.rs index b481ed8..fa96bc0 100644 --- a/core/src/ctxt.rs +++ b/core/src/ctxt.rs @@ -211,7 +211,6 @@ mod internal { #[cfg(feature = "alloc")] mod alloc_support { - use alloc::boxed::Box; use core::any::Any; use crate::props::ErasedProps; diff --git a/core/src/path.rs b/core/src/path.rs index 8f06469..5364c79 100644 --- a/core/src/path.rs +++ b/core/src/path.rs @@ -69,6 +69,14 @@ impl<'a> Path<'a> { } } +impl<'a> Eq for Path<'a> {} + +impl<'a, 'b> PartialEq> for Path<'a> { + fn eq(&self, other: &Path<'b>) -> bool { + self.0 == other.0 + } +} + impl<'a> fmt::Debug for Path<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Debug::fmt(&self.0, f) @@ -106,7 +114,7 @@ impl<'a> serde::Serialize for Path<'a> { #[cfg(feature = "alloc")] mod alloc_support { - use alloc::{borrow::Cow, boxed::Box}; + use alloc::borrow::Cow; use super::*; diff --git a/core/src/str.rs b/core/src/str.rs index b248951..0a8f5cd 100644 --- a/core/src/str.rs +++ b/core/src/str.rs @@ -247,10 +247,7 @@ impl<'k> serde::Serialize for Str<'k> { #[cfg(feature = "alloc")] mod alloc_support { - use alloc::{ - borrow::{Cow, ToOwned}, - string::String, - }; + use alloc::borrow::Cow; use super::*; diff --git a/core/src/template.rs b/core/src/template.rs index 88db2b0..98ec082 100644 --- a/core/src/template.rs +++ b/core/src/template.rs @@ -513,8 +513,6 @@ enum PartKind<'a> { mod alloc_support { use super::*; - use alloc::vec::Vec; - impl Template<'static> { pub fn new_owned(parts: impl Into]>>) -> Self { let parts = parts.into(); diff --git a/core/src/timestamp.rs b/core/src/timestamp.rs index 6026eb8..c030967 100644 --- a/core/src/timestamp.rs +++ b/core/src/timestamp.rs @@ -52,6 +52,12 @@ impl Timestamp { } pub fn from_parts(parts: Parts) -> Option { + /* + Original implementation: https://github.com/tokio-rs/prost/blob/master/prost-types/src/datetime.rs + + Licensed under Apache 2.0 + */ + let is_leap; let start_of_year; let year = (parts.years as i64) - 1900; diff --git a/core/src/value.rs b/core/src/value.rs index 063361e..5174737 100644 --- a/core/src/value.rs +++ b/core/src/value.rs @@ -259,7 +259,7 @@ impl<'v, const N: usize> From<&'v [i64; N]> for Value<'v> { mod alloc_support { use super::*; - use alloc::{borrow::Cow, vec::Vec}; + use alloc::borrow::Cow; impl<'v> Value<'v> { pub fn as_f64_sequence(&self) -> Vec { diff --git a/core/src/well_known.rs b/core/src/well_known.rs index fe24d98..7f8c176 100644 --- a/core/src/well_known.rs +++ b/core/src/well_known.rs @@ -5,7 +5,7 @@ pub const KEY_TS_START: &'static str = "ts_start"; pub const KEY_TPL: &'static str = "tpl"; pub const KEY_MSG: &'static str = "msg"; -// Level +// Log pub const KEY_LVL: &'static str = "lvl"; pub const LVL_DEBUG: &'static str = "debug"; @@ -21,7 +21,7 @@ pub const KEY_TRACE_ID: &'static str = "trace_id"; pub const KEY_SPAN_ID: &'static str = "span_id"; pub const KEY_SPAN_PARENT: &'static str = "span_parent"; -// Metrics +// Metric pub const KEY_METRIC_NAME: &'static str = "metric_name"; pub const KEY_METRIC_AGG: &'static str = "metric_agg"; pub const KEY_METRIC_VALUE: &'static str = "metric_value"; diff --git a/macros/src/capture.rs b/macros/src/capture.rs index c41c237..b60b2d6 100644 --- a/macros/src/capture.rs +++ b/macros/src/capture.rs @@ -35,7 +35,7 @@ pub fn key_value_with_hook( fv: &FieldValue, interpolated: bool, captured: bool, -) -> TokenStream { +) -> syn::Result { let fn_name = match &*fv.key_name() { emit_core::well_known::KEY_LVL => quote_spanned!(fv.span()=> __private_capture_as_level), emit_core::well_known::KEY_ERR => quote_spanned!(fv.span()=> __private_capture_as_error), @@ -73,11 +73,12 @@ pub fn key_value_with_hook( (#expr).__private_optional_capture_some().__private_optional_map_some(|v| v.#fn_name()) #interpolated_expr #captured_expr }); - quote_spanned!(fv.span()=> - #(#attrs)* + hook::eval_hooks( + &attrs, + syn::parse_quote_spanned!(fv.span()=> { (#key_tokens, #value_tokens) - } + }), ) } diff --git a/macros/src/fmt.rs b/macros/src/fmt.rs index 04e8f13..8292f5f 100644 --- a/macros/src/fmt.rs +++ b/macros/src/fmt.rs @@ -14,7 +14,7 @@ pub fn template_hole_with_hook( hole: &ExprLit, interpolated: bool, captured: bool, -) -> TokenStream { +) -> syn::Result { let interpolated_expr = if interpolated { quote!(.__private_interpolated()) } else { @@ -27,13 +27,15 @@ pub fn template_hole_with_hook( quote!(.__private_uncaptured()) }; - quote_spanned!(hole.span()=> - #(#attrs)* - #[allow(unused_imports)] - { - use emit::__private::{__PrivateFmtHook as _, __PrivateInterpolatedHook as _}; - emit::template::Part::hole(#hole).__private_fmt_as_default()#interpolated_expr #captured_expr - } + hook::eval_hooks( + &attrs, + syn::parse_quote_spanned!(hole.span()=> + #[allow(unused_imports)] + { + use emit::__private::{__PrivateFmtHook as _, __PrivateInterpolatedHook as _}; + emit::template::Part::hole(#hole).__private_fmt_as_default()#interpolated_expr #captured_expr + } + ), ) } diff --git a/macros/src/hook.rs b/macros/src/hook.rs index 667199d..a0ac59c 100644 --- a/macros/src/hook.rs +++ b/macros/src/hook.rs @@ -1,4 +1,4 @@ -use std::fmt::Write; +use std::{collections::HashMap, fmt::Write, sync::OnceLock}; use proc_macro2::TokenStream; use quote::ToTokens; @@ -8,11 +8,21 @@ use syn::{ spanned::Spanned, token::Comma, visit_mut::{self, VisitMut}, - Expr, ExprMethodCall, Ident, + Attribute, Expr, ExprMethodCall, Ident, Meta, MetaList, }; use crate::util::parse_comma_separated2; +static HOOKS: OnceLock< + HashMap<&'static str, fn(TokenStream, TokenStream) -> syn::Result>, +> = OnceLock::new(); + +pub(crate) fn get( + name: &str, +) -> Option syn::Result> { + HOOKS.get_or_init(crate::hooks).get(name) +} + pub struct RenameHookTokens { pub args: TokenStream, pub expr: TokenStream, @@ -120,3 +130,31 @@ impl ToTokens for Hook { tokens.extend(quote!(#expr)); } } + +pub(crate) fn eval_hooks(attrs: &[Attribute], expr: Expr) -> syn::Result { + let mut unapplied = Vec::new(); + let mut expr = quote!(#expr); + + for attr in attrs { + if attr.path().segments.len() == 2 { + let root = attr.path().segments.first().unwrap(); + let name = attr.path().segments.last().unwrap(); + + if root.ident == "emit" { + let args = match &attr.meta { + Meta::List(MetaList { ref tokens, .. }) => Some(tokens), + _ => None, + }; + + if let Some(eval) = get(&name.ident.to_string()) { + expr = eval(quote!(#args), expr)?; + continue; + } + } + } + + unapplied.push(attr.clone()); + } + + Ok(quote_spanned!(expr.span()=> #(#unapplied)* #expr)) +} diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 29658aa..66da287 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -12,6 +12,8 @@ extern crate quote; #[macro_use] extern crate syn; +use std::collections::HashMap; + use proc_macro2::TokenStream; mod args; @@ -33,6 +35,141 @@ mod util; use util::ResultToTokens; +fn hooks() -> HashMap<&'static str, fn(TokenStream, TokenStream) -> syn::Result> { + let mut map = HashMap::new(); + + map.insert( + "fmt", + (|args: TokenStream, expr: TokenStream| { + fmt::rename_hook_tokens(fmt::RenameHookTokens { args, expr }) + }) as fn(TokenStream, TokenStream) -> syn::Result, + ); + + map.insert( + "key", + (|args: TokenStream, expr: TokenStream| { + key::rename_hook_tokens(key::RenameHookTokens { args, expr }) + }) as fn(TokenStream, TokenStream) -> syn::Result, + ); + + map.insert( + "optional", + (|args: TokenStream, expr: TokenStream| { + optional::rename_hook_tokens(optional::RenameHookTokens { args, expr }) + }) as fn(TokenStream, TokenStream) -> syn::Result, + ); + + map.insert( + "as_value", + (|args: TokenStream, expr: TokenStream| { + capture_as( + "as_value", + args, + expr, + quote!(__private_capture_as_value), + quote!(__private_capture_anon_as_value), + ) + }) as fn(TokenStream, TokenStream) -> syn::Result, + ); + + map.insert( + "as_debug", + (|args: TokenStream, expr: TokenStream| { + capture_as( + "as_debug", + args, + expr, + quote!(__private_capture_as_debug), + quote!(__private_capture_anon_as_debug), + ) + }) as fn(TokenStream, TokenStream) -> syn::Result, + ); + + map.insert( + "as_display", + (|args: TokenStream, expr: TokenStream| { + capture_as( + "as_display", + args, + expr, + quote!(__private_capture_as_display), + quote!(__private_capture_anon_as_display), + ) + }) as fn(TokenStream, TokenStream) -> syn::Result, + ); + + map.insert( + "as_sval", + (|args: TokenStream, expr: TokenStream| { + #[cfg(feature = "sval")] + { + capture_as( + "as_sval", + args, + expr, + quote!(__private_capture_as_sval), + quote!(__private_capture_anon_as_sval), + ) + } + #[cfg(not(feature = "sval"))] + { + let _ = args; + + Err(syn::Error::new(expr.span(), "capturing with `sval` is only possible when the `sval` Cargo feature is enabled")) + } + }) as fn(TokenStream, TokenStream) -> syn::Result + ); + + map.insert( + "as_serde", + (|args: TokenStream, expr: TokenStream| { + #[cfg(feature = "serde")] + { + capture_as( + "as_serde", + args, + expr, + quote!(__private_capture_as_serde), + quote!(__private_capture_anon_as_serde), + ) + } + #[cfg(not(feature = "serde"))] + { + let _ = args; + + Err(syn::Error::new(expr.span(), "capturing with `serde` is only possible when the `serde` Cargo feature is enabled")) + } + }) as fn(TokenStream, TokenStream) -> syn::Result + ); + + map.insert( + "as_error", + (|args: TokenStream, expr: TokenStream| { + #[cfg(feature = "std")] + { + capture_as( + "as_error", + args, + expr, + quote!(__private_capture_as_error), + quote!(__private_capture_as_error), + ) + } + #[cfg(not(feature = "std"))] + { + let _ = args; + + Err(syn::Error::new( + expr.span(), + "capturing errors is only possible when the `std` Cargo feature is enabled", + )) + } + }) as fn(TokenStream, TokenStream) -> syn::Result, + ); + + map +} + /** Format a template. */ @@ -253,11 +390,8 @@ pub fn fmt( args: proc_macro::TokenStream, item: proc_macro::TokenStream, ) -> proc_macro::TokenStream { - fmt::rename_hook_tokens(fmt::RenameHookTokens { - args: TokenStream::from(args), - expr: TokenStream::from(item), - }) - .unwrap_or_compile_error() + (hook::get("fmt").unwrap())(TokenStream::from(args), TokenStream::from(item)) + .unwrap_or_compile_error() } #[proc_macro_attribute] @@ -265,11 +399,8 @@ pub fn key( args: proc_macro::TokenStream, item: proc_macro::TokenStream, ) -> proc_macro::TokenStream { - key::rename_hook_tokens(key::RenameHookTokens { - args: TokenStream::from(args), - expr: TokenStream::from(item), - }) - .unwrap_or_compile_error() + (hook::get("key").unwrap())(TokenStream::from(args), TokenStream::from(item)) + .unwrap_or_compile_error() } #[proc_macro_attribute] @@ -277,11 +408,8 @@ pub fn optional( args: proc_macro::TokenStream, item: proc_macro::TokenStream, ) -> proc_macro::TokenStream { - optional::rename_hook_tokens(optional::RenameHookTokens { - args: TokenStream::from(args), - expr: TokenStream::from(item), - }) - .unwrap_or_compile_error() + (hook::get("optional").unwrap())(TokenStream::from(args), TokenStream::from(item)) + .unwrap_or_compile_error() } /** @@ -292,13 +420,8 @@ pub fn as_value( args: proc_macro::TokenStream, item: proc_macro::TokenStream, ) -> proc_macro::TokenStream { - capture_as( - "as_value", - TokenStream::from(args), - TokenStream::from(item), - quote!(__private_capture_as_value), - quote!(__private_capture_anon_as_value), - ) + (hook::get("as_value").unwrap())(TokenStream::from(args), TokenStream::from(item)) + .unwrap_or_compile_error() } /** @@ -309,13 +432,8 @@ pub fn as_debug( args: proc_macro::TokenStream, item: proc_macro::TokenStream, ) -> proc_macro::TokenStream { - capture_as( - "as_debug", - TokenStream::from(args), - TokenStream::from(item), - quote!(__private_capture_as_debug), - quote!(__private_capture_anon_as_debug), - ) + (hook::get("as_debug").unwrap())(TokenStream::from(args), TokenStream::from(item)) + .unwrap_or_compile_error() } /** @@ -326,13 +444,8 @@ pub fn as_display( args: proc_macro::TokenStream, item: proc_macro::TokenStream, ) -> proc_macro::TokenStream { - capture_as( - "as_display", - TokenStream::from(args), - TokenStream::from(item), - quote!(__private_capture_as_display), - quote!(__private_capture_anon_as_display), - ) + (hook::get("as_display").unwrap())(TokenStream::from(args), TokenStream::from(item)) + .unwrap_or_compile_error() } /** @@ -343,25 +456,8 @@ pub fn as_sval( args: proc_macro::TokenStream, item: proc_macro::TokenStream, ) -> proc_macro::TokenStream { - #[cfg(feature = "sval")] - { - capture_as( - "as_sval", - TokenStream::from(args), - TokenStream::from(item), - quote!(__private_capture_as_sval), - quote!(__private_capture_anon_as_sval), - ) - } - #[cfg(not(feature = "sval"))] - { - let _ = args; - let _ = item; - - proc_macro::TokenStream::from(quote!(compile_error!( - "capturing with `sval` is only possible when the `sval` Cargo feature is enabled" - ))) - } + (hook::get("as_sval").unwrap())(TokenStream::from(args), TokenStream::from(item)) + .unwrap_or_compile_error() } /** @@ -372,25 +468,8 @@ pub fn as_serde( args: proc_macro::TokenStream, item: proc_macro::TokenStream, ) -> proc_macro::TokenStream { - #[cfg(feature = "serde")] - { - capture_as( - "as_serde", - TokenStream::from(args), - TokenStream::from(item), - quote!(__private_capture_as_serde), - quote!(__private_capture_anon_as_serde), - ) - } - #[cfg(not(feature = "serde"))] - { - let _ = args; - let _ = item; - - proc_macro::TokenStream::from(quote!(compile_error!( - "capturing with `serde` is only possible when the `serde` Cargo feature is enabled" - ))) - } + (hook::get("as_serde").unwrap())(TokenStream::from(args), TokenStream::from(item)) + .unwrap_or_compile_error() } /** @@ -401,25 +480,8 @@ pub fn as_error( args: proc_macro::TokenStream, item: proc_macro::TokenStream, ) -> proc_macro::TokenStream { - #[cfg(feature = "std")] - { - capture_as( - "as_error", - TokenStream::from(args), - TokenStream::from(item), - quote!(__private_capture_as_error), - quote!(__private_capture_as_error), - ) - } - #[cfg(not(feature = "std"))] - { - let _ = args; - let _ = item; - - proc_macro::TokenStream::from(quote!(compile_error!( - "capturing errors is only possible when the `std` Cargo feature is enabled" - ))) - } + (hook::get("as_error").unwrap())(TokenStream::from(args), TokenStream::from(item)) + .unwrap_or_compile_error() } fn base_emit(level: Option, item: TokenStream) -> proc_macro::TokenStream { @@ -448,7 +510,7 @@ fn capture_as( expr: TokenStream, as_fn: TokenStream, as_anon_fn: TokenStream, -) -> proc_macro::TokenStream { +) -> syn::Result { capture::rename_hook_tokens(capture::RenameHookTokens { name, args, @@ -461,5 +523,4 @@ fn capture_as( } }, }) - .unwrap_or_compile_error() } diff --git a/macros/src/props.rs b/macros/src/props.rs index 5877211..b11be00 100644 --- a/macros/src/props.rs +++ b/macros/src/props.rs @@ -121,7 +121,7 @@ impl Props { let key_value_tokens = { let key_value_tokens = - capture::key_value_with_hook(&attrs, &fv, interpolated, captured); + capture::key_value_with_hook(&attrs, &fv, interpolated, captured)?; match cfg_attr { Some(ref cfg_attr) => quote_spanned!(fv.span()=> diff --git a/macros/src/template.rs b/macros/src/template.rs index 71b038f..b8c0c77 100644 --- a/macros/src/template.rs +++ b/macros/src/template.rs @@ -76,10 +76,10 @@ pub fn parse2( let template_tokens = { let mut template_visitor = TemplateVisitor { props: &props, - parts: Vec::new(), + parts: Ok(Vec::new()), }; template.visit_literal(&mut template_visitor); - let template_parts = &template_visitor.parts; + let template_parts = template_visitor.parts?; /* Ideally this would be: @@ -130,11 +130,15 @@ impl Template { struct TemplateVisitor<'a> { props: &'a Props, - parts: Vec, + parts: syn::Result>, } impl<'a> fv_template::LiteralVisitor for TemplateVisitor<'a> { fn visit_hole(&mut self, hole: &FieldValue) { + let Ok(ref mut parts) = self.parts else { + return; + }; + let label = hole.key_name(); let hole = hole.key_expr(); @@ -142,15 +146,22 @@ impl<'a> fv_template::LiteralVisitor for TemplateVisitor<'a> { debug_assert!(field.interpolated); - let hole_tokens = fmt::template_hole_with_hook(&field.attrs, &hole, true, field.captured); - - match field.cfg_attr { - Some(ref cfg_attr) => self.parts.push(quote!(#cfg_attr { #hole_tokens })), - _ => self.parts.push(quote!(#hole_tokens)), + match fmt::template_hole_with_hook(&field.attrs, &hole, true, field.captured) { + Ok(hole_tokens) => match field.cfg_attr { + Some(ref cfg_attr) => parts.push(quote!(#cfg_attr { #hole_tokens })), + _ => parts.push(quote!(#hole_tokens)), + }, + Err(e) => { + self.parts = Err(e); + } } } fn visit_text(&mut self, text: &str) { - self.parts.push(quote!(emit::template::Part::text(#text))); + let Ok(ref mut parts) = self.parts else { + return; + }; + + parts.push(quote!(emit::template::Part::text(#text))); } } diff --git a/src/lib.rs b/src/lib.rs index e1a84ee..259d0d9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,11 +3,11 @@ Structured diagnostics for Rust applications. Emit is a structured logging framework for manually instrumenting Rust applications with an expressive syntax. -# Events +# Data model -All diagnostics in Emit are represented as _events_. An event is a notable change in the state of a system that is broadcast to outside observers. Events carry both a human-readable description of what triggered them as well as a structured payload that can be used to process them. Events are temporal; they may be anchored to a point in time at which they occurred, or may cover a span of time for which they are active. +## Events -## Core data model +All diagnostics in Emit are represented as _events_. An event is a notable change in the state of a system that is broadcast to outside observers. Events carry both a human-readable description of what triggered them as well as a structured payload that can be used to process them. Events are temporal; they may be anchored to a point in time at which they occurred, or may cover a span of time for which they are active. The core event model includes: diff --git a/tests/smoke-test/main.rs b/tests/smoke-test/main.rs index 0eea41d..72571e0 100644 --- a/tests/smoke-test/main.rs +++ b/tests/smoke-test/main.rs @@ -1,5 +1,3 @@ -#![feature(stmt_expr_attributes, proc_macro_hygiene)] - use std::{ io, sync::atomic::{AtomicUsize, Ordering},