Skip to content

Commit ebed80e

Browse files
authored
feat(layer): simple renames of flattened event fields (#23)
## Motivation Closes #19. Users sometimes want to rename the fields in events and spans. This allows that specifically for flattened events. This should be done more generally somehow. ## Solution Accept a closure and a state object, calling the closure for every field to assess whether to rename or not. This will most likely be a lookup in a map.
1 parent 7491bd3 commit ebed80e

3 files changed

Lines changed: 215 additions & 0 deletions

File tree

src/layer/mod.rs

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ use uuid::Uuid;
3030
use crate::{
3131
cached::Cached,
3232
fields::{JsonFields, JsonFieldsInner},
33+
serde::RenamedFields,
3334
visitor::JsonVisitor,
3435
};
3536

@@ -790,6 +791,55 @@ where
790791
self
791792
}
792793

794+
/// Print all event fields, each as its own top level member of the JSON. This also allows the
795+
/// fields to have different keys than the field names used in tracing.
796+
///
797+
/// Only field names can be renamed this way, not other fields such as `timestamp` or `target`
798+
/// which usually take the key as their parameter (such as `Self::with_target`).
799+
///
800+
/// The renames are realized by a provided function that will be passed the original field name
801+
/// and a user-defined context and it must produce the new name.
802+
///
803+
/// For example when simple static renames are needed the following can work:
804+
///
805+
/// ```rust
806+
/// # use std::collections::HashMap;
807+
/// # use tracing_subscriber::prelude::*;
808+
///
809+
/// let mut layer = json_subscriber::JsonLayer::stdout();
810+
///
811+
/// let renames = HashMap::from([
812+
/// ("message".to_owned(), "msg".to_owned()),
813+
/// ("foo".to_owned(), "bar".to_owned()),
814+
/// ]);
815+
/// layer.with_flattened_event_with_renames(
816+
/// move |name, map| map.get(name).map_or(name, String::as_str),
817+
/// renames,
818+
/// );
819+
/// # tracing_subscriber::registry().with(layer);
820+
///
821+
/// // This will produce something like `{"bar":3,"msg":"x",...}`
822+
/// tracing::info!(foo = 3, "x");
823+
/// ```
824+
///
825+
/// It is the user's responsibility to make sure that the field names will not clash with other
826+
/// defined members of the output JSON. If they clash, invalid JSON with multiple fields with
827+
/// the same key may be generated.
828+
pub fn with_flattened_event_with_renames<F, T>(&mut self, renames: F, context: T) -> &mut Self
829+
where
830+
F: for<'a> Fn(&'a str, &'a T) -> &'a str + Send + Sync + 'static + Clone,
831+
T: Clone + Send + Sync + 'static,
832+
{
833+
self.flattened_values.insert(
834+
FlatSchemaKey::FlattenedEvent,
835+
JsonValue::DynamicFromEvent(Box::new(move |event| {
836+
serde_json::to_value(RenamedFields::new(event.event(), renames.clone(), &context))
837+
.ok()
838+
})),
839+
);
840+
self
841+
}
842+
793843
/// Sets whether or not the log line will include the current span in formatted events.
794844
pub fn with_current_span(&mut self, key: impl Into<String>) -> &mut Self {
795845
self.keyed_values.insert(
@@ -1108,6 +1158,8 @@ fn write_escaped(writer: &mut dyn fmt::Write, value: &str) -> Result<(), fmt::Er
11081158

11091159
#[cfg(test)]
11101160
mod tests {
1161+
use std::collections::HashMap;
1162+
11111163
use serde_json::json;
11121164
use tracing::subscriber::with_default;
11131165
use tracing_subscriber::{registry, Layer, Registry};
@@ -1164,4 +1216,37 @@ mod tests {
11641216
tracing::info!(does = "not matter", "whatever");
11651217
});
11661218
}
1219+
1220+
#[test]
1221+
fn flattened_event_with_renames() {
1222+
let renames = HashMap::from([
1223+
("message".to_owned(), "msg".to_owned()),
1224+
("msg".to_owned(), "message".to_owned()),
1225+
("same".to_owned(), "same".to_owned()),
1226+
("different".to_owned(), "gone".to_owned()),
1227+
]);
1228+
let mut layer = JsonLayer::stdout();
1229+
layer.with_flattened_event_with_renames(
1230+
move |name, map| map.get(name).map_or(name, String::as_str),
1231+
renames,
1232+
);
1233+
1234+
let expected = json!({
1235+
"message": "msg",
1236+
"msg": "message",
1237+
"same": "same",
1238+
"gone": "different",
1239+
"another": "another",
1240+
});
1241+
1242+
test_json(&expected, layer, || {
1243+
tracing::info!(
1244+
msg = "msg",
1245+
same = "same",
1246+
different = "different",
1247+
another = "another",
1248+
"message"
1249+
);
1250+
});
1251+
}
11671252
}

src/serde/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1+
mod tracing_serde;
2+
13
use serde_json::ser::Formatter;
4+
pub(crate) use tracing_serde::RenamedFields;
25

36
pub(crate) struct JsonSubscriberFormatter;
47

src/serde/tracing_serde.rs

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
use std::{fmt, mem::transmute};
2+
3+
use serde::{ser::SerializeMap, Serialize, Serializer};
4+
use tracing::{field::Visit, Event};
5+
use tracing_core::Field;
6+
7+
pub(crate) struct RenamedFields<'a, F, C> {
8+
event: &'a Event<'a>,
9+
renames: F,
10+
context: &'a C,
11+
}
12+
13+
impl<'a, F, C> RenamedFields<'a, F, C> {
14+
pub(crate) fn new(event: &'a Event<'a>, renames: F, context: &'a C) -> Self {
15+
Self {
16+
event,
17+
renames,
18+
context,
19+
}
20+
}
21+
}
22+
23+
impl<F, C> Serialize for RenamedFields<'_, F, C>
24+
where
25+
F: for<'a> Fn(&'a str, &'a C) -> &'a str + Send + Sync + 'static,
26+
{
27+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
28+
where
29+
S: Serializer,
30+
{
31+
let len = self.event.fields().count();
32+
let serializer = serializer.serialize_map(Some(len))?;
33+
let renames: &'static F = unsafe { transmute(&self.renames) };
34+
let mut visitor = SerdeMapVisitor::new(serializer, renames, self.context);
35+
self.event.record(&mut visitor);
36+
visitor.finish()
37+
}
38+
}
39+
40+
/// Implements `tracing_core::field::Visit` for some `serde::ser::SerializeMap`.
41+
#[derive(Debug)]
42+
pub struct SerdeMapVisitor<'a, S: SerializeMap, F, C> {
43+
serializer: S,
44+
renames: F,
45+
context: &'a C,
46+
state: Result<(), S::Error>,
47+
}
48+
49+
impl<'a, S, F, C> SerdeMapVisitor<'a, S, F, C>
50+
where
51+
S: SerializeMap,
52+
{
53+
/// Create a new map visitor.
54+
pub fn new(serializer: S, renames: F, context: &'a C) -> Self {
55+
Self {
56+
serializer,
57+
renames,
58+
context,
59+
state: Ok(()),
60+
}
61+
}
62+
63+
/// Completes serializing the visited object, returning `Ok(())` if all
64+
/// fields were serialized correctly, or `Error(S::Error)` if a field could
65+
/// not be serialized.
66+
pub fn finish(self) -> Result<S::Ok, S::Error> {
67+
self.state?;
68+
self.serializer.end()
69+
}
70+
}
71+
72+
impl<S, F, C> Visit for SerdeMapVisitor<'_, S, F, C>
73+
where
74+
S: SerializeMap,
75+
F: for<'a> Fn(&'a str, &'a C) -> &'a str + Send + Sync + 'static,
76+
{
77+
fn record_bool(&mut self, field: &Field, value: bool) {
78+
// If previous fields serialized successfully, continue serializing,
79+
// otherwise, short-circuit and do nothing.
80+
if self.state.is_ok() {
81+
self.state = self
82+
.serializer
83+
.serialize_entry((self.renames)(field.name(), self.context), &value);
84+
}
85+
}
86+
87+
fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) {
88+
if self.state.is_ok() {
89+
self.state = self.serializer.serialize_entry(
90+
(self.renames)(field.name(), self.context),
91+
&format_args!("{value:?}"),
92+
);
93+
}
94+
}
95+
96+
fn record_u64(&mut self, field: &Field, value: u64) {
97+
if self.state.is_ok() {
98+
self.state = self
99+
.serializer
100+
.serialize_entry((self.renames)(field.name(), self.context), &value);
101+
}
102+
}
103+
104+
fn record_i64(&mut self, field: &Field, value: i64) {
105+
if self.state.is_ok() {
106+
self.state = self
107+
.serializer
108+
.serialize_entry((self.renames)(field.name(), self.context), &value);
109+
}
110+
}
111+
112+
fn record_f64(&mut self, field: &Field, value: f64) {
113+
if self.state.is_ok() {
114+
self.state = self
115+
.serializer
116+
.serialize_entry((self.renames)(field.name(), self.context), &value);
117+
}
118+
}
119+
120+
fn record_str(&mut self, field: &Field, value: &str) {
121+
if self.state.is_ok() {
122+
self.state = self
123+
.serializer
124+
.serialize_entry((self.renames)(field.name(), self.context), &value);
125+
}
126+
}
127+
}

0 commit comments

Comments
 (0)