From 697c0b3111067f06e00fdb55a96036b3893b2938 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Ml=C3=A1dek?= Date: Wed, 25 Jun 2025 21:48:23 +0200 Subject: [PATCH] feat(layer): simple renames of flattened event fields --- src/layer/mod.rs | 51 +++++++++++++++ src/serde/mod.rs | 3 + src/serde/tracing_serde.rs | 127 +++++++++++++++++++++++++++++++++++++ 3 files changed, 181 insertions(+) create mode 100644 src/serde/tracing_serde.rs diff --git a/src/layer/mod.rs b/src/layer/mod.rs index 46afa16..89559d8 100644 --- a/src/layer/mod.rs +++ b/src/layer/mod.rs @@ -30,6 +30,7 @@ use uuid::Uuid; use crate::{ cached::Cached, fields::{JsonFields, JsonFieldsInner}, + serde::RenamedFields, visitor::JsonVisitor, }; @@ -790,6 +791,21 @@ where self } + pub fn with_flattened_event_with_renames(&mut self, renames: F, context: T) -> &mut Self + where + F: for<'a> Fn(&'a str, &'a T) -> &'a str + Send + Sync + 'static + Clone, + T: Clone + Send + Sync + 'static, + { + self.flattened_values.insert( + FlatSchemaKey::FlattenedEvent, + JsonValue::DynamicFromEvent(Box::new(move |event| { + serde_json::to_value(RenamedFields::new(event.event(), renames.clone(), &context)) + .ok() + })), + ); + self + } + /// Sets whether or not the log line will include the current span in formatted events. pub fn with_current_span(&mut self, key: impl Into) -> &mut Self { self.keyed_values.insert( @@ -1158,6 +1174,8 @@ fn write_escaped(writer: &mut dyn fmt::Write, value: &str) -> Result<(), fmt::Er #[cfg(test)] mod tests { + use std::collections::HashMap; + use serde_json::json; use tracing::subscriber::with_default; use tracing_subscriber::{registry, Layer, Registry}; @@ -1214,4 +1232,37 @@ mod tests { tracing::info!(does = "not matter", "whatever"); }); } + + #[test] + fn flattened_event_with_renames() { + let renames = HashMap::from([ + ("message".to_owned(), "msg".to_owned()), + ("msg".to_owned(), "message".to_owned()), + ("same".to_owned(), "same".to_owned()), + ("different".to_owned(), "gone".to_owned()), + ]); + let mut layer = JsonLayer::stdout(); + layer.with_flattened_event_with_renames( + move |name, map| map.get(name).unwrap().as_str(), + renames, + ); + + let expected = json!({ + "message": "msg", + "msg": "message", + "same": "same", + "gone": "different", + "another": "another", + }); + + test_json(&expected, layer, || { + tracing::info!( + msg = "msg", + same = "same", + different = "different", + another = "another", + "message" + ); + }); + } } diff --git a/src/serde/mod.rs b/src/serde/mod.rs index 5803d71..c986096 100644 --- a/src/serde/mod.rs +++ b/src/serde/mod.rs @@ -1,4 +1,7 @@ +mod tracing_serde; + use serde_json::ser::Formatter; +pub(crate) use tracing_serde::RenamedFields; pub(crate) struct JsonSubscriberFormatter; diff --git a/src/serde/tracing_serde.rs b/src/serde/tracing_serde.rs new file mode 100644 index 0000000..7b328bc --- /dev/null +++ b/src/serde/tracing_serde.rs @@ -0,0 +1,127 @@ +use std::{fmt, mem::transmute}; + +use serde::{ser::SerializeMap, Serialize, Serializer}; +use tracing::{field::Visit, Event}; +use tracing_core::Field; + +pub(crate) struct RenamedFields<'a, F, C> { + event: &'a Event<'a>, + renames: F, + context: &'a C, +} + +impl<'a, F, C> RenamedFields<'a, F, C> { + pub(crate) fn new(event: &'a Event<'a>, renames: F, context: &'a C) -> Self { + Self { + event, + renames, + context, + } + } +} + +impl Serialize for RenamedFields<'_, F, C> +where + F: for<'a> Fn(&'a str, &'a C) -> &'a str + Send + Sync + 'static, +{ + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let len = self.event.fields().count(); + let serializer = serializer.serialize_map(Some(len))?; + let renames: &'static F = unsafe { transmute(&self.renames) }; + let mut visitor = SerdeMapVisitor::new(serializer, renames, self.context); + self.event.record(&mut visitor); + visitor.finish() + } +} + +/// Implements `tracing_core::field::Visit` for some `serde::ser::SerializeMap`. +#[derive(Debug)] +pub struct SerdeMapVisitor<'a, S: SerializeMap, F, C> { + serializer: S, + renames: F, + context: &'a C, + state: Result<(), S::Error>, +} + +impl<'a, S, F, C> SerdeMapVisitor<'a, S, F, C> +where + S: SerializeMap, +{ + /// Create a new map visitor. + pub fn new(serializer: S, renames: F, context: &'a C) -> Self { + Self { + serializer, + renames, + context, + state: Ok(()), + } + } + + /// Completes serializing the visited object, returning `Ok(())` if all + /// fields were serialized correctly, or `Error(S::Error)` if a field could + /// not be serialized. + pub fn finish(self) -> Result { + self.state?; + self.serializer.end() + } +} + +impl Visit for SerdeMapVisitor<'_, S, F, C> +where + S: SerializeMap, + F: for<'a> Fn(&'a str, &'a C) -> &'a str + Send + Sync + 'static, +{ + fn record_bool(&mut self, field: &Field, value: bool) { + // If previous fields serialized successfully, continue serializing, + // otherwise, short-circuit and do nothing. + if self.state.is_ok() { + self.state = self + .serializer + .serialize_entry((self.renames)(field.name(), self.context), &value); + } + } + + fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) { + if self.state.is_ok() { + self.state = self.serializer.serialize_entry( + (self.renames)(field.name(), self.context), + &format_args!("{value:?}"), + ); + } + } + + fn record_u64(&mut self, field: &Field, value: u64) { + if self.state.is_ok() { + self.state = self + .serializer + .serialize_entry((self.renames)(field.name(), self.context), &value); + } + } + + fn record_i64(&mut self, field: &Field, value: i64) { + if self.state.is_ok() { + self.state = self + .serializer + .serialize_entry((self.renames)(field.name(), self.context), &value); + } + } + + fn record_f64(&mut self, field: &Field, value: f64) { + if self.state.is_ok() { + self.state = self + .serializer + .serialize_entry((self.renames)(field.name(), self.context), &value); + } + } + + fn record_str(&mut self, field: &Field, value: &str) { + if self.state.is_ok() { + self.state = self + .serializer + .serialize_entry((self.renames)(field.name(), self.context), &value); + } + } +}