Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support structured logging (KV) #77

Draft
wants to merge 8 commits into
base: main-dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions spdlog-internal/src/pattern_parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ pub enum BuiltInFormatterInner {
LoggerName,
#[strum(serialize = "payload")]
Payload,
#[strum(serialize = "kv")]
KV,
#[strum(serialize = "pid")]
ProcessId,
#[strum(serialize = "tid")]
Expand Down
41 changes: 36 additions & 5 deletions spdlog-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,17 @@
//!
//! [`spdlog-rs`]: https://crates.io/crates/spdlog-rs

mod normalize_forward;
mod pattern;

use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use spdlog_internal::pattern_parser::Result;
use quote::quote;

#[proc_macro]
pub fn pattern(input: TokenStream) -> TokenStream {
let pattern = syn::parse_macro_input!(input);
into_or_error(pattern::pattern_impl(pattern))
into_or_error(pattern::pattern_impl(pattern).map_err(Error::PatternParser))
}

#[proc_macro]
Expand All @@ -23,7 +24,7 @@ pub fn runtime_pattern(input: TokenStream) -> TokenStream {
// token which is used in the custom patterns.

let runtime_pattern = syn::parse_macro_input!(input);
into_or_error(pattern::runtime_pattern_impl(runtime_pattern))
into_or_error(pattern::runtime_pattern_impl(runtime_pattern).map_err(Error::PatternParser))
}

#[proc_macro]
Expand All @@ -33,9 +34,39 @@ pub fn runtime_pattern_disabled(_: TokenStream) -> TokenStream {
);
}

fn into_or_error(result: Result<TokenStream2>) -> TokenStream {
// Example:
//
// ```rust
// normalize_forward!(callback => default[opt1: 1, opt2: {}, opt3: { 3 }, d], opt1: 10, a, b, c, opt3: { 30 });
// // will be converted to
// spdlog::callback!(opt1: 10, opt2: {}, opt3: { 30 }, d, a, b, c);
// ```
#[proc_macro]
pub fn normalize_forward(input: TokenStream) -> TokenStream {
let normalize = syn::parse_macro_input!(input);
into_or_error(normalize_forward::normalize(normalize).map_err(Error::NormalizeForward))
}

enum Error {
PatternParser(spdlog_internal::pattern_parser::Error),
NormalizeForward(syn::Error),
}

impl Error {
fn emit(self) -> TokenStream2 {
match self {
Error::PatternParser(err) => {
let error = err.to_string();
quote!(compile_error!(#error))
}
Error::NormalizeForward(err) => err.to_compile_error(),
}
}
}

fn into_or_error(result: Result<TokenStream2, Error>) -> TokenStream {
match result {
Ok(stream) => stream.into(),
Err(err) => panic!("{}", err),
Err(err) => err.emit().into(),
}
}
233 changes: 233 additions & 0 deletions spdlog-macros/src/normalize_forward.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
use std::collections::HashSet;

use proc_macro2::TokenStream;
use quote::{quote, ToTokens};
use syn::{
braced, bracketed,
parse::{discouraged::Speculative, Parse, ParseStream},
Expr, Ident, Token,
};

pub struct Normalize {
callback: Ident,
default_list: DefaultArgsList,
args: Args,
}

impl Parse for Normalize {
fn parse(input: ParseStream) -> syn::Result<Self> {
let callback = input.parse::<Ident>()?;
input.parse::<Token![=>]>()?;
let default_list = DefaultArgsList::parse(input)?;
input.parse::<Token![,]>()?;
let args = Args::parse(input)?;
Ok(Self {
callback,
default_list,
args,
})
}
}

struct DefaultArgsList(Vec<Arg>);

impl Parse for DefaultArgsList {
fn parse(input: ParseStream) -> syn::Result<Self> {
input.parse::<Token![default]>()?;
let list;
bracketed!(list in input);
let list = list
.parse_terminated(Arg::parse, Token![,])?
.into_iter()
.collect();
Ok(Self(list))
}
}

struct Args(Vec<Arg>);

impl Parse for Args {
fn parse(input: ParseStream) -> syn::Result<Self> {
let args = input.parse_terminated(Arg::parse, Token![,])?;
Ok(Self(args.into_iter().collect()))
}
}

enum Arg {
Optional(OptionalArg),
Other(ArgValue),
}

impl Arg {
fn as_optional(&self) -> Option<&OptionalArg> {
match self {
Self::Optional(arg) => Some(arg),
_ => None,
}
}

fn as_optional_mut(&mut self) -> Option<&mut OptionalArg> {
match self {
Self::Optional(arg) => Some(arg),
_ => None,
}
}
}

impl Parse for Arg {
fn parse(input: ParseStream) -> syn::Result<Self> {
let fork = input.fork();
match OptionalArg::parse(&fork) {
Ok(opt_arg) => {
input.advance_to(&fork);
Ok(Self::Optional(opt_arg))
}
Err(_) => Ok(Self::Other(ArgValue::parse(input)?)),
}
}
}

struct OptionalArg {
name: Ident,
value: ArgValue,
}

impl Parse for OptionalArg {
fn parse(input: ParseStream) -> syn::Result<Self> {
let name = input.parse::<Ident>()?;
input.parse::<Token![:]>()?;
let value = ArgValue::parse(input)?;
Ok(Self { name, value })
}
}

enum ArgValue {
Expr(Expr),
Braced(BraceAny),
}

impl ArgValue {
fn into_token_stream(self) -> TokenStream {
match self {
Self::Expr(expr) => expr.into_token_stream(),
Self::Braced(braced) => braced.0,
}
}
}

impl Parse for ArgValue {
fn parse(input: ParseStream) -> syn::Result<Self> {
let fork = input.fork();

match Expr::parse(&fork) {
Ok(expr) => {
input.advance_to(&fork);
Ok(Self::Expr(expr))
}
Err(_) => Ok(BraceAny::parse(input).map(Self::Braced)?),
}
}
}

struct BraceAny(TokenStream);

impl Parse for BraceAny {
fn parse(input: ParseStream) -> syn::Result<Self> {
let content;
braced!(content in input);
let ts: TokenStream = content.parse()?;
Ok(Self(quote!({#ts})))
}
}

fn check_inputs(normalize: &Normalize) -> syn::Result<()> {
let mut seen_keys = HashSet::new();
for arg in normalize.args.0.iter().filter_map(|arg| arg.as_optional()) {
if !seen_keys.insert(&arg.name) {
return Err(syn::Error::new(
arg.name.span(),
format!("found duplicate optional argument '{}'", arg.name),
));
}
}

let groups = normalize
.args
.0
.split_inclusive(|arg| matches!(arg, Arg::Optional(_)))
.filter(|group| group.iter().any(|arg| !matches!(arg, Arg::Optional(_))))
.collect::<Vec<_>>();
if groups.len() > 1 {
return Err(syn::Error::new(
groups
.first()
.and_then(|group| group.last())
.and_then(|arg| arg.as_optional())
.unwrap()
.name
.span(),
"optional arguments cannot occur in the middle of regular arguments",
));
}

Ok(())
}

pub fn normalize(normalize: Normalize) -> syn::Result<TokenStream> {
check_inputs(&normalize)?;

let mut default_args = normalize.default_list.0;
let mut other_args = vec![];

for input_arg in normalize.args.0 {
match input_arg {
Arg::Optional(input_arg) => {
let stored = default_args
.iter_mut()
.find_map(|allowed| {
allowed
.as_optional_mut()
.and_then(|allowed| (allowed.name == input_arg.name).then(|| allowed))
})
.ok_or_else(|| {
syn::Error::new(
input_arg.name.span(),
format!("unknown optional parameter '{}'", input_arg.name),
)
})?;
stored.value = input_arg.value;
}
Arg::Other(input_arg) => {
other_args.push(input_arg);
}
}
}

let callback = normalize.callback;
let default_args = default_args
.into_iter()
.map(|arg| match arg {
Arg::Optional(arg) => {
let name = arg.name;
let value = arg.value.into_token_stream();
quote!(#name: #value)
}
Arg::Other(arg) => {
let value = arg.into_token_stream();
quote!(#value)
}
})
.collect::<Vec<_>>();
let other_args = other_args
.into_iter()
.map(|arg| {
let ts = arg.into_token_stream();
quote!(#ts)
})
.collect::<Vec<_>>();

let emitted = quote! {
::spdlog::#callback!(#(#default_args),*, #(#other_args),*)
};
Ok(emitted)
}
5 changes: 3 additions & 2 deletions spdlog/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ native = []
libsystemd = ["libsystemd-sys"]
multi-thread = ["crossbeam"]
runtime-pattern = ["spdlog-internal"]
serde_json = ["serde", "dep:serde_json"]
serde_json = ["serde", "dep:serde_json", "value-bag/serde1"]

[dependencies]
arc-swap = "1.5.1"
Expand All @@ -49,14 +49,15 @@ dyn-clone = "1.0.14"
flexible-string = { version = "0.1.0", optional = true }
if_chain = "1.0.2"
is-terminal = "0.4"
log = { version = "0.4.8", optional = true }
log = { version = "0.4.21", optional = true, features = ["kv"] }
once_cell = "1.16.0"
serde = { version = "1.0.163", optional = true, features = ["derive"] }
serde_json = { version = "1.0.120", optional = true }
spdlog-internal = { version = "=0.1.0", path = "../spdlog-internal", optional = true }
spdlog-macros = { version = "=0.2.0", path = "../spdlog-macros" }
spin = "0.9.8"
thiserror = "1.0.37"
value-bag = { version = "1.10.0", features = ["owned"] }

[target.'cfg(windows)'.dependencies]
winapi = { version = "0.3.9", features = ["consoleapi", "debugapi", "handleapi", "processenv", "processthreadsapi", "winbase", "wincon"] }
Expand Down
Loading
Loading