Skip to content

Commit 62e1257

Browse files
authored
feat(layer)!: make adding multiple dynamic fields cheaper (#35)
## Motivation Adding multiple dynamic fields was expensive because it had to always allocate the string for each key on every tracing invocation as well as create a `serde_json::Value`. ## Solution Now the strings may be `&'static str` if known beforehand and the values may be left as `impl Serialize` if they're available. The fields will be serialized into the output string as needed.
1 parent b24ff4a commit 62e1257

4 files changed

Lines changed: 103 additions & 30 deletions

File tree

src/field_writer.rs

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
use crate::cursor::Cursor;
2+
3+
/// A writer passed to closures registered with
4+
/// [`add_multiple_dynamic_fields`](crate::JsonLayer::add_multiple_dynamic_fields).
5+
///
6+
/// Call [`write_field`](FieldWriter::write_field) to add key-value pairs directly to the JSON
7+
/// output. Keys are `&str` (no allocation required for static strings), and values accept any
8+
/// type that implements [`serde::Serialize`].
9+
pub struct FieldWriter<'a> {
10+
writer: &'a mut String,
11+
prefix_comma: bool,
12+
wrote_anything: bool,
13+
}
14+
15+
impl<'a> FieldWriter<'a> {
16+
pub(crate) fn new(writer: &'a mut String, prefix_comma: bool) -> Self {
17+
Self {
18+
writer,
19+
prefix_comma,
20+
wrote_anything: false,
21+
}
22+
}
23+
24+
pub(crate) fn wrote_anything(&self) -> bool {
25+
self.wrote_anything
26+
}
27+
28+
/// Writes a single key-value pair into the JSON output.
29+
///
30+
/// The key is serialized as a quoted, escaped JSON string. The value can be any type
31+
/// implementing [`serde::Serialize`]: strings, numbers, booleans, `Option<T>`, structs
32+
/// with `#[derive(Serialize)]`, etc.
33+
///
34+
/// Returns `Err` if serialization fails. On error the writer state is rolled back so the
35+
/// output remains valid JSON. Errors from this method are rare and indicate a bug in the
36+
/// `Serialize` implementation of the value type.
37+
///
38+
/// # Errors
39+
///
40+
/// This function errors if serialization of the provided value fails. This means that either
41+
/// the implementation of `Serialize` decides to fail, or if the value contains a map with
42+
/// non-string keys.
43+
pub fn write_field(
44+
&mut self,
45+
key: &str,
46+
value: impl serde::Serialize,
47+
) -> serde_json::Result<()> {
48+
let rollback = self.writer.len();
49+
50+
if self.wrote_anything || self.prefix_comma {
51+
self.writer.push(',');
52+
}
53+
54+
if let Err(error) = serde_json::to_writer(&Cursor::new(self.writer), key) {
55+
self.writer.truncate(rollback);
56+
return Err(error);
57+
}
58+
59+
self.writer.push(':');
60+
61+
if let Err(error) = serde_json::to_writer(&Cursor::new(self.writer), &value) {
62+
self.writer.truncate(rollback);
63+
return Err(error);
64+
}
65+
66+
self.wrote_anything = true;
67+
Ok(())
68+
}
69+
}

src/layer/event.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use tracing_subscriber::{
1212
use crate::{
1313
cached::Cached,
1414
cursor::Cursor,
15+
field_writer::FieldWriter,
1516
layer::{JsonLayer, JsonValue, SchemaKey},
1617
serde::JsonSubscriberFormatter,
1718
};
@@ -176,6 +177,16 @@ where
176177
}
177178

178179
for value in self.flattened_values.values() {
180+
if let JsonValue::DynamicFromEventWithWriter(fun) = value {
181+
let mut inner = writer.inner_mut();
182+
let mut field_writer = FieldWriter::new(&mut inner, serialized_anything);
183+
fun(&event_ref, &mut field_writer);
184+
if field_writer.wrote_anything() {
185+
serialized_anything = true;
186+
}
187+
continue;
188+
}
189+
179190
let Some(value) = resolve_json_value(value, &event_ref) else {
180191
continue;
181192
};
@@ -298,6 +309,8 @@ fn resolve_json_value<'a, S: Subscriber + for<'lookup> LookupSpan<'lookup>>(
298309
event.parent_span().and_then(fun).map(MaybeCached::Cached)
299310
},
300311
JsonValue::DynamicRawFromEvent(fun) => Some(MaybeCached::Raw(fun)),
312+
// This cannot be used with a static key so this should never be called
313+
JsonValue::DynamicFromEventWithWriter(_) => None,
301314
}
302315
}
303316

src/layer/mod.rs

Lines changed: 19 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,4 @@
1-
use std::{
2-
borrow::Cow,
3-
cell::RefCell,
4-
collections::{BTreeMap, HashMap},
5-
fmt,
6-
io,
7-
sync::Arc,
8-
};
1+
use std::{borrow::Cow, cell::RefCell, collections::BTreeMap, fmt, io, sync::Arc};
92

103
use serde::Serialize;
114
use tracing_core::{
@@ -29,6 +22,7 @@ use uuid::Uuid;
2922

3023
use crate::{
3124
cached::Cached,
25+
field_writer::FieldWriter,
3226
fields::{JsonFields, JsonFieldsInner},
3327
serde::RenamedFields,
3428
visitor::JsonVisitor,
@@ -94,6 +88,9 @@ pub(crate) enum JsonValue<S: for<'lookup> LookupSpan<'lookup>> {
9488
DynamicRawFromEvent(
9589
Box<dyn Fn(&EventRef<'_, '_, '_, S>, &mut dyn fmt::Write) -> fmt::Result + Send + Sync>,
9690
),
91+
DynamicFromEventWithWriter(
92+
Box<dyn Fn(&EventRef<'_, '_, '_, S>, &mut FieldWriter<'_>) + Send + Sync>,
93+
),
9794
}
9895

9996
impl<S, W> Layer<S> for JsonLayer<S, W>
@@ -451,52 +448,44 @@ where
451448
);
452449
}
453450

454-
/// Adds multiple new dynamic field where the keys may not be known when calling this method.
451+
/// Adds multiple new dynamic fields where the keys may not be known when calling this method.
455452
///
456-
/// This method takes a closure argument that will be called with the event and tracing context.
457-
/// Through these, the parent span can be accessed among other things. This closure returns a
458-
/// value which can be iterated over to return a tuple of a `String` which will be used as a
459-
/// JSON key and a `serde_json::Value` which will be used as a value. In most cases returning
460-
/// `HashMap<String, serde_json::Value>` should be sufficient.
453+
/// This method takes a closure argument that will be called with the event and tracing context
454+
/// along with a [`FieldWriter`]. Call [`FieldWriter::write_field`] to write key-value pairs
455+
/// directly into the JSON output. Values accept any type implementing [`serde::Serialize`].
461456
///
462457
/// It is the user's responsibility to make sure that no two keys clash as that would create an
463458
/// invalid JSON. It's generally better to use [`add_dynamic_field`](Self::add_dynamic_field)
464459
/// instead if the field names are known.
465460
///
466461
/// # Examples
467462
///
468-
/// Print either a question or an answer:
463+
/// Print a question or an answer:
469464
///
470465
/// ```rust
471466
/// # use tracing_subscriber::prelude::*;
472467
///
473468
/// let mut layer = json_subscriber::JsonLayer::stdout();
474469
/// layer.add_multiple_dynamic_fields(
475-
/// |_event, _context| {
470+
/// |_event, _context, writer| {
476471
/// # let condition = true;
477472
/// if condition {
478-
/// [("question".to_owned(), serde_json::Value::String("What?".to_owned()))]
473+
/// _ = writer.write_field("question", "What?");
479474
/// } else {
480-
/// [("answer".to_owned(), serde_json::Value::Number(42.into()))]
475+
/// _ = writer.write_field("answer", 42u64);
481476
/// }
482-
/// });
477+
/// }
478+
/// );
483479
/// # tracing_subscriber::registry().with(layer);
484-
/// # fn get_hostname() -> &'static str { "localhost" }
485480
/// ```
486-
pub fn add_multiple_dynamic_fields<Fun, Res>(&mut self, mapper: Fun)
481+
pub fn add_multiple_dynamic_fields<Fun>(&mut self, mapper: Fun)
487482
where
488-
for<'a> Fun: Fn(&'a Event<'_>, &Context<'_, S>) -> Res + Send + Sync + 'a,
489-
Res: IntoIterator<Item = (String, serde_json::Value)>,
483+
Fun: Fn(&Event<'_>, &Context<'_, S>, &mut FieldWriter<'_>) + Send + Sync + 'static,
490484
{
491485
self.flattened_values.insert(
492486
FlatSchemaKey::new_uuid(),
493-
JsonValue::DynamicFromEvent(Box::new(move |event| {
494-
serde_json::to_value(
495-
mapper(event.event(), event.context())
496-
.into_iter()
497-
.collect::<HashMap<_, _>>(),
498-
)
499-
.ok()
487+
JsonValue::DynamicFromEventWithWriter(Box::new(move |event, writer| {
488+
mapper(event.event(), event.context(), writer);
500489
})),
501490
);
502491
}

src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@
105105

106106
mod cached;
107107
mod cursor;
108+
mod field_writer;
108109
mod fields;
109110
pub mod fmt;
110111
mod layer;
@@ -114,5 +115,6 @@ mod visitor;
114115
#[cfg(test)]
115116
mod tests;
116117

118+
pub use field_writer::FieldWriter;
117119
pub use fmt::{fmt, layer};
118120
pub use layer::JsonLayer;

0 commit comments

Comments
 (0)