tracing_flat_json/
lib.rs

1//! A simple [`tracing_subscriber::Layer`] emitting newline-delimited JSON, with
2//! all event and span fields flattened onto the top level JSON object. Similar
3//! to combining the [`json-subscriber`] options [`with_flattened_event`] and
4//! [`with_top_level_flattened_span_list`], without the caveats about duplicate
5//! fields noted in the documentation for those options.
6//!
7//! [`tracing_subscriber::Layer`]: https://docs.rs/tracing-subscriber/0.3.20/tracing_subscriber/layer/trait.Layer.html
8//! [`json-subscriber`]: https://crates.io/crates/json-subscriber
9//! [`with_flattened_event`]: https://docs.rs/json-subscriber/0.2.6/json_subscriber/struct.JsonLayer.html#method.with_flattened_event
10//! [`with_top_level_flattened_span_list`]: https://docs.rs/json-subscriber/0.2.6/json_subscriber/struct.JsonLayer.html#method.with_top_level_flattened_span_list
11//!
12//! ## Usage
13//!
14//! ```rust
15//! use tracing_subscriber::prelude::*;
16//!
17//! tracing_subscriber::Registry::default()
18//!     .with(tracing_flat_json::FlatJsonLayer::new(std::io::stdout))
19//!     .init();
20//! ```
21//!
22//! ## Feature Flags
23//!
24//! - `tracing-log`: Handle [`log`] events emitted by [`tracing-log`]. Enabled
25//!   by default.
26//! - `tracing-opentelemetry`: Outputs the `trace_id` added to spans by
27//!   [`tracing-opentelemetry`]. May not work when compiled with multiple
28//!   versions of [`tracing-opentelemetry`] in the same executable.
29//!
30//! [`log`]: https://crates.io/crates/log
31//! [`tracing-log`]: https://crates.io/crates/tracing-log
32//! [`tracing-opentelemetry`]: https://crates.io/crates/tracing-opentelemetry
33//!
34//! ## Output Format
35//!
36//! Included:
37//!
38//! - timestamp (RFC3339 UTC)
39//! - level
40//! - `trace_id` (with feature `tracing-opentelemetry`)
41//! - `code.file.path`
42//! - `code.line.number`
43//! - event message
44//! - event fields, source code order
45//! - parent span fields, source code order, latest span first, root span last
46//!
47//! Excluded:
48//!
49//! - span names
50//! - `target`
51//! - `span_id`
52//! - duplicate fields (only latest value for each field name)
53
54use std::collections::{HashMap, HashSet};
55use std::error::Error;
56use std::fmt::Debug;
57use std::io::Write;
58use std::marker::PhantomData;
59
60use serde::ser::{SerializeMap, Serializer};
61use serde::Serialize;
62use time::format_description::well_known::Rfc3339;
63use time::UtcDateTime;
64use tracing::field::Field;
65use tracing::span::{Attributes, Record};
66use tracing::{Event, Id, Subscriber};
67use tracing_core::field::Visit;
68use tracing_subscriber::fmt::MakeWriter;
69use tracing_subscriber::layer::{Context, Layer};
70use tracing_subscriber::registry::LookupSpan;
71
72trait SerializationInfallibleExt<T> {
73    fn expect_serialization_infallible(self) -> T;
74}
75impl<T, E> SerializationInfallibleExt<T> for Result<T, E>
76where
77    E: Debug,
78{
79    #[inline]
80    fn expect_serialization_infallible(self: Result<T, E>) -> T {
81        self.expect("serialization to buffer should never fail")
82    }
83}
84
85// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER
86const MAX_SAFE_INTEGER: i64 = (1i64 << 53) - 1;
87const MIN_SAFE_INTEGER: i64 = -MAX_SAFE_INTEGER;
88
89// Casts an integer to i64 if JavaScript / JSON can precisely represent it.
90trait TryIntoSafeInteger
91where
92    Self: itoa::Integer,
93{
94    fn try_into_safe_integer(self) -> Option<i64>;
95}
96
97impl TryIntoSafeInteger for i64 {
98    #[inline]
99    fn try_into_safe_integer(self) -> Option<i64> {
100        #[allow(clippy::manual_range_contains)]
101        if self <= MAX_SAFE_INTEGER && self >= MIN_SAFE_INTEGER {
102            Some(self)
103        } else {
104            None
105        }
106    }
107}
108
109impl TryIntoSafeInteger for u64 {
110    #[inline]
111    fn try_into_safe_integer(self) -> Option<i64> {
112        if self <= MAX_SAFE_INTEGER as u64 {
113            #[expect(clippy::cast_possible_wrap, reason = "explicitly checked")]
114            Some(self as i64)
115        } else {
116            None
117        }
118    }
119}
120
121impl TryIntoSafeInteger for i128 {
122    #[inline]
123    fn try_into_safe_integer(self) -> Option<i64> {
124        if self <= i128::from(MAX_SAFE_INTEGER) && self >= i128::from(MIN_SAFE_INTEGER) {
125            #[expect(clippy::cast_possible_truncation, reason = "explicitly checked")]
126            Some(self as i64)
127        } else {
128            None
129        }
130    }
131}
132
133impl TryIntoSafeInteger for u128 {
134    #[inline]
135    fn try_into_safe_integer(self) -> Option<i64> {
136        if self <= MAX_SAFE_INTEGER as u128 {
137            #[expect(clippy::cast_possible_truncation, reason = "explicitly checked")]
138            Some(self as i64)
139        } else {
140            None
141        }
142    }
143}
144
145// This tomfoolery allows using the same Visit implementation for event fields
146// and span fields. They only differ in how they are recorded. Events write to
147// the JSON serializer directly, as there's no need to store values. Spans write
148// to a map, as the values must be stored until an event occurs, and may be used
149// multiple times for multiple events.
150trait FlatJsonRecorder {
151    fn record_value<T>(&mut self, field: &Field, value: T)
152    where
153        T: Serialize + Into<serde_json::Value>;
154
155    // Many JSON parsers treat all numbers as floats. Serialize int types as
156    // strings if they're not exactly representable as 64-bit floats. For log
157    // purposes, all fields will eventually be displayed as strings anyway, so
158    // changing the field type should be harmless.
159    #[inline]
160    fn record_int(&mut self, field: &Field, value: impl TryIntoSafeInteger) {
161        if let Some(i) = value.try_into_safe_integer() {
162            self.record_value(field, i);
163        } else {
164            let mut b = itoa::Buffer::new();
165            let s = b.format(value);
166            self.record_value(field, s);
167        }
168    }
169}
170
171// Unlikely serde_json will ever change the default formatter, but 1 object per
172// line is a hard requirement for logging, so it's good to specify.
173type OutputSerializer = serde_json::Serializer<Vec<u8>, serde_json::ser::CompactFormatter>;
174
175// Recorder for events, serializing event fields directly to the output buffer.
176struct FlatJsonEventRecorder<'state: 'state_borrow, 'state_borrow> {
177    json_out: &'state_borrow mut <&'state mut OutputSerializer as Serializer>::SerializeMap,
178    seen_fields: &'state_borrow mut HashSet<&'static str>,
179}
180
181impl FlatJsonRecorder for FlatJsonEventRecorder<'_, '_> {
182    #[inline]
183    fn record_value<T>(&mut self, field: &Field, value: T)
184    where
185        T: Serialize + Into<serde_json::Value>,
186    {
187        if self.seen_fields.insert(field.name()) {
188            self.json_out
189                .serialize_entry(field.name(), &value)
190                .expect_serialization_infallible();
191        }
192    }
193}
194
195// Recorder for spans, storing spans fields in a map to serialize later. Unlike
196// events, span fields are not guaranteed to be recorded in order, and could be
197// recorded more than once, so a map is necessary.
198struct FlatJsonSpanRecorder<'a>(&'a mut HashMap<&'static str, serde_json::Value>);
199
200impl FlatJsonRecorder for FlatJsonSpanRecorder<'_> {
201    #[inline]
202    fn record_value<T>(&mut self, field: &Field, value: T)
203    where
204        T: Serialize + Into<serde_json::Value>,
205    {
206        self.0.insert(field.name(), value.into());
207    }
208}
209
210// Common Visit impl, delegating to either the event or span recorder.
211#[repr(transparent)]
212struct FlatJsonVisitor<T: FlatJsonRecorder>(T);
213
214impl<T: FlatJsonRecorder> Visit for FlatJsonVisitor<T> {
215    #[inline]
216    fn record_f64(&mut self, field: &Field, value: f64) {
217        self.0.record_value(field, value);
218    }
219    #[inline]
220    fn record_i64(&mut self, field: &Field, value: i64) {
221        self.0.record_int(field, value);
222    }
223    #[inline]
224    fn record_u64(&mut self, field: &Field, value: u64) {
225        self.0.record_int(field, value);
226    }
227    #[inline]
228    fn record_i128(&mut self, field: &Field, value: i128) {
229        self.0.record_int(field, value);
230    }
231    #[inline]
232    fn record_u128(&mut self, field: &Field, value: u128) {
233        self.0.record_int(field, value);
234    }
235    #[inline]
236    fn record_bool(&mut self, field: &Field, value: bool) {
237        self.0.record_value(field, value);
238    }
239    #[inline]
240    fn record_str(&mut self, field: &Field, value: &str) {
241        self.0.record_value(field, value);
242    }
243    #[inline]
244    fn record_bytes(&mut self, field: &Field, value: &[u8]) {
245        // Assume raw binary data logged is mostly human readable.
246        self.0.record_value(field, value.escape_ascii().to_string());
247    }
248    #[inline]
249    fn record_error(&mut self, field: &Field, value: &(dyn Error + 'static)) {
250        // Use alternate format to include context chain for anyhow::Error.
251        self.0.record_value(field, format!("{value:#}"));
252    }
253    #[inline]
254    fn record_debug(&mut self, field: &Field, value: &dyn Debug) {
255        self.0.record_value(field, format!("{value:?}"));
256    }
257}
258
259// Only needs to record the "message" field added by tracing-log. The only other
260// fields are created by tracing-log to save metadata, and efficiently exposed
261// by tracing-log via tracing_log::NormalizeEvent.
262#[cfg(feature = "tracing-log")]
263struct TracingLogVisitor<'state: 'state_borrow, 'state_borrow> {
264    json_out: &'state_borrow mut <&'state mut OutputSerializer as Serializer>::SerializeMap,
265    seen_fields: &'state_borrow mut HashSet<&'static str>,
266}
267
268#[cfg(feature = "tracing-log")]
269impl Visit for TracingLogVisitor<'_, '_> {
270    #[inline]
271    fn record_f64(&mut self, field: &Field, _value: f64) {
272        debug_assert!(false, "{}", field.name());
273    }
274    #[inline]
275    fn record_i64(&mut self, field: &Field, _value: i64) {
276        debug_assert!(false, "{}", field.name());
277    }
278    #[inline]
279    fn record_u64(&mut self, field: &Field, _value: u64) {
280        debug_assert_eq!(field.name(), "log.line");
281    }
282    #[inline]
283    fn record_i128(&mut self, field: &Field, _value: i128) {
284        debug_assert!(false, "{}", field.name());
285    }
286    #[inline]
287    fn record_u128(&mut self, field: &Field, _value: u128) {
288        debug_assert!(false, "{}", field.name());
289    }
290    #[inline]
291    fn record_bool(&mut self, field: &Field, _value: bool) {
292        debug_assert!(false, "{}", field.name());
293    }
294    #[inline]
295    fn record_str(&mut self, field: &Field, _value: &str) {
296        let n = field.name();
297        debug_assert!(
298            n == "log.target" || n == "log.module_path" || n == "log.file",
299            "{n}"
300        );
301    }
302    #[inline]
303    fn record_bytes(&mut self, field: &Field, _value: &[u8]) {
304        debug_assert!(false, "{}", field.name());
305    }
306    #[inline]
307    fn record_error(&mut self, field: &Field, _value: &(dyn Error + 'static)) {
308        debug_assert!(false, "{}", field.name());
309    }
310    #[inline]
311    fn record_debug(&mut self, field: &Field, value: &dyn Debug) {
312        const MESSAGE: &str = "message";
313        debug_assert_eq!(field.name(), MESSAGE);
314        if field.name() == MESSAGE {
315            let is_new = self.seen_fields.insert(MESSAGE);
316            debug_assert!(is_new);
317            self.json_out
318                .serialize_entry(MESSAGE, &format!("{value:?}"))
319                .expect_serialization_infallible();
320        }
321    }
322}
323
324/// See the [module-level documentation](crate) for usage.
325pub struct FlatJsonLayer<S, W>
326where
327    W: for<'writer> MakeWriter<'writer> + 'static,
328{
329    make_writer: W,
330    _inner: PhantomData<fn(S)>,
331}
332
333impl<S, W> FlatJsonLayer<S, W>
334where
335    W: for<'writer> MakeWriter<'writer> + 'static,
336{
337    /// Returns a new [`FlatJsonLayer`] using the provided [`MakeWriter`] to
338    /// write events.
339    #[inline]
340    pub fn new(make_writer: W) -> Self {
341        Self {
342            make_writer,
343            _inner: PhantomData,
344        }
345    }
346}
347
348// Span extension for storing span fields until they're used emitting events.
349struct FlatJsonSpanData(HashMap<&'static str, serde_json::Value>);
350
351impl<S, W> Layer<S> for FlatJsonLayer<S, W>
352where
353    S: Subscriber + for<'a> LookupSpan<'a>,
354    W: for<'w> MakeWriter<'w> + 'static,
355{
356    fn on_new_span(&self, attrs: &Attributes<'_>, id: &Id, ctx: Context<'_, S>) {
357        if attrs.fields().is_empty() {
358            return;
359        }
360        let span = ctx
361            .span(id)
362            .expect("span not found, this is a subscriber bug");
363        let mut extensions = span.extensions_mut();
364        let mut json_map = HashMap::with_capacity(attrs.fields().len());
365        attrs.record(&mut FlatJsonVisitor(FlatJsonSpanRecorder(&mut json_map)));
366        extensions.insert(FlatJsonSpanData(json_map));
367    }
368
369    fn on_record(&self, id: &Id, values: &Record<'_>, ctx: Context<'_, S>) {
370        let span = ctx
371            .span(id)
372            .expect("span not found, this is a subscriber bug");
373        let mut extensions = span.extensions_mut();
374        // This layer's on_new_span callback always inserts an extension if the
375        // span has any fields, and spans cannot have fields recorded at all if
376        // no fields were initially declared.
377        let FlatJsonSpanData(json_map) = extensions
378            .get_mut::<FlatJsonSpanData>()
379            .expect("tracing-flat-json span extension not found, this is a bug");
380        values.record(&mut FlatJsonVisitor(FlatJsonSpanRecorder(json_map)));
381    }
382
383    #[expect(clippy::too_many_lines)]
384    fn on_event(&self, event: &Event<'_>, ctx: Context<'_, S>) {
385        // The time crate always uses Z for UTC, and will output 9 digits of
386        // nanoseconds without rounding if that resolution is available.
387        // https://github.com/time-rs/time/blob/v0.3.44/time/src/formatting/formattable.rs#L236-L275
388        const MAX_TIME_LEN: usize = "0000-00-00T00:00:00.000000000Z".len();
389        let mut ts_buf = [0u8; MAX_TIME_LEN];
390        let mut ts_cursor = &mut ts_buf[..];
391        UtcDateTime::now()
392            .format_into(&mut ts_cursor, &Rfc3339)
393            .expect("formatting current UTC time should never fail");
394        let ts_len = MAX_TIME_LEN - ts_cursor.len();
395        // SAFETY: In the inconceivable case that time formats a timestamp as
396        // non-utf8, it's better to output non-utf8 than crash or log nothing.
397        let ts = unsafe { std::str::from_utf8_unchecked(&ts_buf[..ts_len]) };
398
399        let mut serializer: OutputSerializer =
400            serde_json::Serializer::new(Vec::with_capacity(1024));
401        // serde_json only uses the size hint when it's 0, for empty maps.
402        let mut json_out = serializer
403            .serialize_map(None)
404            .expect_serialization_infallible();
405
406        #[cfg(feature = "tracing-log")]
407        let log_metadata = {
408            use tracing_log::NormalizeEvent;
409            event.normalized_metadata()
410        };
411        #[cfg(feature = "tracing-log")]
412        let metadata = match &log_metadata {
413            Some(metadata) => metadata,
414            None => event.metadata(),
415        };
416        #[cfg(not(feature = "tracing-log"))]
417        let log_metadata: Option<()> = None;
418        #[cfg(not(feature = "tracing-log"))]
419        let metadata = event.metadata();
420
421        // timestamp + level + file + line + fields.
422        let total_event_fields = 4 + metadata.fields().len();
423        // trace_id.
424        #[cfg(feature = "tracing-opentelemetry")]
425        let total_event_fields = total_event_fields + 1;
426
427        // Avoid outputting the same JSON key twice. +4 bumps a small bucket
428        // count up to the next size class, saving a realloc when parent spans
429        // have fields. Nets a 7-10% speedup in single threaded microbenchmarks,
430        // but mostly it just makes me feel better.
431        let mut seen_fields = HashSet::with_capacity(total_event_fields + 4);
432        macro_rules! serialize_entry {
433            ($k:literal, $v: expr) => {
434                let is_new = seen_fields.insert($k);
435                debug_assert!(is_new, "{}", $k);
436                json_out
437                    .serialize_entry($k, $v)
438                    .expect_serialization_infallible();
439            };
440        }
441
442        // Write timestamp, level, and code location first, like text loggers
443        // typically do (including tracing-subscriber), making the logs easier
444        // to read when looking at the raw JSON output.
445        serialize_entry!("timestamp", &ts);
446        serialize_entry!("level", metadata.level().as_str());
447
448        // Output trace_id before code location, since it has consistent width.
449        #[cfg(feature = "tracing-opentelemetry")]
450        {
451            use opentelemetry::trace::{TraceContextExt, TraceId};
452            use tracing_opentelemetry::OtelData;
453
454            if let Some(span) = ctx.event_span(event) {
455                if let Some(otel_data) = span.extensions().get::<OtelData>() {
456                    let mut trace_id = otel_data.parent_cx.span().span_context().trace_id();
457
458                    // This event's parent span is a root span, with no parent
459                    // context explicitly set. If this span closes without a
460                    // parent context, it will use this trace_id. A parent may
461                    // be set after an event is emitted, making this trace_id
462                    // meaningless, but that future trace_id cannot be known
463                    // from here. Child spans have the same problem, i.e. they
464                    // also inherit this trace_id when created before setting a
465                    // parent context.
466                    // See also: tracing_opentelemetry::PreSampledTracer.
467                    if trace_id == TraceId::INVALID {
468                        if let Some(builder_trace_id) = otel_data.builder.trace_id {
469                            trace_id = builder_trace_id;
470                        }
471                    }
472
473                    // I don't think it's possible to have an invalid trace_id
474                    // at this point. If there's a parent span, and that span
475                    // has OtelData, it should have either a parent context or
476                    // a builder trace_id, even when not sampled.
477                    debug_assert_ne!(trace_id, TraceId::INVALID);
478                    serialize_entry!("trace_id", &format!("{trace_id}"));
479                }
480            }
481        }
482
483        // Use names from opentelemetry semantic conventions.
484        if let Some(file) = metadata.file() {
485            serialize_entry!("code.file.path", file);
486        }
487        if let Some(line) = metadata.line() {
488            serialize_entry!("code.line.number", &line);
489        }
490
491        // Serialize the event before spans, then spans from nearest to root,
492        // putting the most specific information first. This is purely a matter
493        // of taste, tracing-subscriber's text format does the opposite, i.e. it
494        // outputs spans in hierarchical order.
495        if log_metadata.is_some() {
496            #[cfg(feature = "tracing-log")]
497            event.record(&mut TracingLogVisitor {
498                json_out: &mut json_out,
499                seen_fields: &mut seen_fields,
500            });
501            #[cfg(not(feature = "tracing-log"))]
502            unreachable!();
503        } else {
504            event.record(&mut FlatJsonVisitor(FlatJsonEventRecorder {
505                json_out: &mut json_out,
506                seen_fields: &mut seen_fields,
507            }));
508        }
509        if let Some(scope) = ctx.event_scope(event) {
510            for span in scope {
511                // Spans have no extension if the span has no fields.
512                if let Some(FlatJsonSpanData(json_map)) = span.extensions().get() {
513                    // Output fields in the order they appeared on the span.
514                    for field in span.fields() {
515                        let k = field.name();
516                        // Fields initialized as tracing::field::Empty and never
517                        // given a value will not be in the map.
518                        if let Some(v) = json_map.get(k) {
519                            // Only serialize each field once.
520                            if seen_fields.insert(k) {
521                                json_out
522                                    .serialize_entry(k, v)
523                                    .expect_serialization_infallible();
524                            }
525                        }
526                    }
527                }
528            }
529        }
530
531        json_out.end().expect_serialization_infallible();
532        let mut buf = serializer.into_inner();
533        buf.push(b'\n');
534
535        if let Err(e) = self.make_writer.make_writer_for(metadata).write_all(&buf) {
536            // Avoid spamming stderr when the output is a closed pipe, as that
537            // often happens for benign reasons, e.g. piping stdout to head.
538            if e.kind() != std::io::ErrorKind::BrokenPipe {
539                eprintln!("[tracing-flat-json] write error: {e:?}");
540            }
541        }
542    }
543}
544
545#[cfg(test)]
546mod tests {
547    use std::sync::{Arc, Mutex};
548
549    use tracing::dispatcher::DefaultGuard;
550    use tracing::{debug, error, info, info_span, warn};
551    use tracing_subscriber::layer::SubscriberExt;
552    use tracing_subscriber::registry::SpanData;
553    use tracing_subscriber::util::SubscriberInitExt;
554    use tracing_subscriber::Registry;
555
556    use super::*;
557
558    #[derive(Clone, Default)]
559    struct MockWriter(Arc<Mutex<Vec<u8>>>);
560
561    impl Write for MockWriter {
562        fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
563            // Tests are all single-threaded.
564            self.0.try_lock().unwrap().write(buf)
565        }
566        fn flush(&mut self) -> std::io::Result<()> {
567            // Tests are all single-threaded.
568            self.0.try_lock().unwrap().flush()
569        }
570    }
571
572    impl MockWriter {
573        fn read_json(&self) -> serde_json::Value {
574            let mut buf = self.0.try_lock().unwrap();
575            let json = serde_json::from_slice(&buf).unwrap();
576            buf.clear();
577            json
578        }
579        fn read_string(&self) -> String {
580            let mut buf = self.0.try_lock().unwrap();
581            let vec = std::mem::take(&mut *buf);
582            String::from_utf8(vec).unwrap()
583        }
584    }
585
586    fn mock_subscriber() -> (DefaultGuard, MockWriter) {
587        let writer = MockWriter::default();
588        let make_writer = move || writer.clone();
589        let return_writer = make_writer();
590
591        #[cfg(feature = "tracing-opentelemetry")]
592        let subscriber = {
593            use opentelemetry::trace::TracerProvider;
594            let exporter = opentelemetry_stdout::SpanExporter::default();
595            let provider = opentelemetry_sdk::trace::SdkTracerProvider::builder()
596                .with_simple_exporter(exporter)
597                .build();
598            let tracer = provider.tracer(env!("CARGO_PKG_NAME"));
599            let layer = tracing_opentelemetry::layer().with_tracer(tracer);
600            Registry::default().with(layer)
601        };
602
603        #[cfg(not(feature = "tracing-opentelemetry"))]
604        let subscriber = Registry::default();
605
606        let subscriber = subscriber.with(FlatJsonLayer::new(make_writer));
607        (subscriber.set_default(), return_writer)
608    }
609
610    macro_rules! pline {
611        ($n:literal) => {
612            i64::from(line!()) - $n
613        };
614    }
615
616    #[test]
617    fn field_values() {
618        let (_dispatch, writer) = mock_subscriber();
619
620        info!("test message");
621        let json = writer.read_json();
622        println!("{json}");
623        assert!(json["timestamp"].as_str().unwrap().ends_with('Z'));
624        assert_eq!(json["level"].as_str().unwrap(), "INFO");
625        assert_eq!(json["code.file.path"].as_str().unwrap(), "src/lib.rs");
626        assert_eq!(json["code.line.number"].as_i64().unwrap(), pline!(6));
627        assert_eq!(json["message"].as_str().unwrap(), "test message");
628        assert_eq!(json.as_object().unwrap().len(), 5);
629
630        warn!(a = 1, b = "two", "test fields");
631        let json = writer.read_json();
632        println!("{json}");
633        assert!(json["timestamp"].as_str().unwrap().ends_with('Z'));
634        assert_eq!(json["level"].as_str().unwrap(), "WARN");
635        assert_eq!(json["code.file.path"].as_str().unwrap(), "src/lib.rs");
636        assert_eq!(json["code.line.number"].as_i64().unwrap(), pline!(6));
637        assert_eq!(json["message"].as_str().unwrap(), "test fields");
638        assert_eq!(json["a"].as_i64().unwrap(), 1);
639        assert_eq!(json["b"].as_str().unwrap(), "two");
640        assert_eq!(json.as_object().unwrap().len(), 7);
641
642        // trace_id
643        #[cfg(feature = "tracing-opentelemetry")]
644        let extra_fields = 1;
645        #[cfg(not(feature = "tracing-opentelemetry"))]
646        let extra_fields = 0;
647
648        let span1 = info_span!("span1", c = "three");
649        let _g1 = span1.enter();
650        error!("test span1");
651        let json = writer.read_json();
652        println!("{json}");
653        assert!(json["timestamp"].as_str().unwrap().ends_with('Z'));
654        assert_eq!(json["level"].as_str().unwrap(), "ERROR");
655        assert_eq!(json["code.file.path"].as_str().unwrap(), "src/lib.rs");
656        assert_eq!(json["code.line.number"].as_i64().unwrap(), pline!(6));
657        #[cfg(feature = "tracing-opentelemetry")]
658        assert_eq!(json["trace_id"].as_str().unwrap().len(), 32);
659        assert_eq!(json["message"].as_str().unwrap(), "test span1");
660        assert_eq!(json["c"].as_str().unwrap(), "three");
661        assert_eq!(json.as_object().unwrap().len(), 6 + extra_fields);
662
663        let span2 = info_span!("span2", d = 4.0);
664        let _g2 = span2.enter();
665        debug!("test span2");
666        let json = writer.read_json();
667        println!("{json}");
668        assert!(json["timestamp"].as_str().unwrap().ends_with('Z'));
669        assert_eq!(json["level"].as_str().unwrap(), "DEBUG");
670        assert_eq!(json["code.file.path"].as_str().unwrap(), "src/lib.rs");
671        assert_eq!(json["code.line.number"].as_i64().unwrap(), pline!(6));
672        #[cfg(feature = "tracing-opentelemetry")]
673        assert_eq!(json["trace_id"].as_str().unwrap().len(), 32);
674        assert_eq!(json["message"].as_str().unwrap(), "test span2");
675        assert_eq!(json["c"].as_str().unwrap(), "three");
676        assert_eq!(json["d"].as_f64().unwrap(), 4.0);
677        assert_eq!(json.as_object().unwrap().len(), 7 + extra_fields);
678    }
679
680    #[test]
681    fn no_dup_fields() {
682        let (_dispatch, writer) = mock_subscriber();
683
684        // Baseline, there is obviously no duplicate field here.
685        let span1 = info_span!("span1", f = "a");
686        let _g1 = span1.enter();
687        info!("e");
688        let s = writer.read_string();
689        println!("{s}");
690        let json: serde_json::Value = serde_json::from_str(&s).unwrap();
691        println!("{json}");
692        assert_eq!(json["f"].as_str().unwrap(), "a");
693        let f_count = s.split(',').filter(|s| s.starts_with("\"f\":")).count();
694        assert_eq!(f_count, 1);
695
696        // Duplicated "f", latest span should take precedent.
697        let span2 = info_span!("span2", f = "b");
698        let _g2 = span2.enter();
699        info!("e");
700        let s = writer.read_string();
701        println!("{s}");
702        let json: serde_json::Value = serde_json::from_str(&s).unwrap();
703        println!("{json}");
704        assert_eq!(json["f"].as_str().unwrap(), "b");
705        let f_count = s.split(',').filter(|s| s.starts_with("\"f\":")).count();
706        assert_eq!(f_count, 1);
707
708        // Duplicated "f", event should take precedent.
709        info!(f = "c");
710        let s = writer.read_string();
711        println!("{s}");
712        let json: serde_json::Value = serde_json::from_str(&s).unwrap();
713        println!("{json}");
714        assert_eq!(json["f"].as_str().unwrap(), "c");
715        let f_count = s.split(',').filter(|s| s.starts_with("\"f\":")).count();
716        assert_eq!(f_count, 1);
717    }
718
719    #[test]
720    fn record_fields() {
721        let (_dispatch, writer) = mock_subscriber();
722
723        let span = info_span!(
724            "span",
725            f1 = tracing::field::Empty,
726            f2 = "a",
727            f3 = tracing::field::Empty
728        );
729        let _g = span.enter();
730
731        // Baseline, "f1" and "f3" should have no value.
732        info!("e");
733        let s = writer.read_string();
734        println!("{s}");
735        let json: serde_json::Value = serde_json::from_str(&s).unwrap();
736        println!("{json}");
737        assert_eq!(json["f1"], serde_json::Value::Null);
738        assert_eq!(json["f2"].as_str().unwrap(), "a");
739        assert_eq!(json["f3"], serde_json::Value::Null);
740
741        // Record new value to "f1".
742        span.record("f1", "b");
743        info!("e");
744        let s = writer.read_string();
745        println!("{s}");
746        let json: serde_json::Value = serde_json::from_str(&s).unwrap();
747        println!("{json}");
748        assert_eq!(json["f1"].as_str().unwrap(), "b");
749        assert_eq!(json["f2"].as_str().unwrap(), "a");
750        assert_eq!(json["f3"], serde_json::Value::Null);
751
752        // Re-record new value to "f1", and ensure last value wins, as duplicate
753        // keys in the output would be invalid JSON.
754        // Interestingly, tracing-opentelemetry doesn't seem to prevent this,
755        // generating spans with duplicate attributes, even though the otel spec
756        // says last value wins.
757        span.record("f1", "c");
758        span.record("f4", "invalid, not initially declared");
759        info!("e");
760        let s = writer.read_string();
761        println!("{s}");
762        let json: serde_json::Value = serde_json::from_str(&s).unwrap();
763        println!("{json}");
764        assert_eq!(json["f1"].as_str().unwrap(), "c");
765        assert_eq!(json["f2"].as_str().unwrap(), "a");
766        assert_eq!(json["f3"], serde_json::Value::Null);
767        assert_eq!(json["f4"], serde_json::Value::Null);
768
769        // Setting f1 twice should not result in more than 1 output.
770        let f1_count = s.split(',').filter(|s| s.starts_with("\"f1\":")).count();
771        assert_eq!(f1_count, 1);
772
773        // f1 set after f2, but should still be first in output.
774        assert!(s.contains(r#""f1":"c","f2":"a""#));
775    }
776
777    #[test]
778    fn no_extension() {
779        let (_dispatch, writer) = mock_subscriber();
780
781        let span = info_span!("span2");
782        let _g = span.enter();
783
784        // A span with no initially declared fields should not have an extension
785        // inserted by FlatJsonLayer.
786        span.with_subscriber(|(id, subscriber)| {
787            let registry = subscriber.downcast_ref::<Registry>().unwrap();
788            let span_data = registry.span_data(id).unwrap();
789            assert!(span_data.extensions().get::<FlatJsonSpanData>().is_none());
790        });
791
792        // Recording fields with no extension shouldn't crash when the span had
793        // no initial fields.
794        span.record("f", 1);
795        info!("e");
796        let json = writer.read_json();
797        println!("{json}");
798        assert_eq!(json["f"], serde_json::Value::Null);
799    }
800
801    #[test]
802    fn field_order() {
803        let (_dispatch, writer) = mock_subscriber();
804
805        let span1 = info_span!("span1", b = "b", c = "c");
806        let _g1 = span1.enter();
807        let span2 = info_span!("span2", d = "d", a = "a");
808        let _g2 = span2.enter();
809        info!(e = "e", "event1");
810        let s = writer.read_string();
811        let s = s.trim_ascii_end();
812        println!("{s}");
813
814        let mut field_names = Vec::new();
815        let field_str = s.strip_prefix("{").unwrap().strip_suffix("}").unwrap();
816        for field in field_str.split(',') {
817            let field_key_str = field.split(':').next().unwrap();
818            field_names.push(&field_key_str[1..field_key_str.len() - 1]);
819        }
820        assert_eq!(
821            field_names,
822            &[
823                "timestamp",
824                "level",
825                #[cfg(feature = "tracing-opentelemetry")]
826                "trace_id",
827                "code.file.path",
828                "code.line.number",
829                "message",
830                "e",
831                "d",
832                "a",
833                "b",
834                "c"
835            ]
836        );
837    }
838
839    #[cfg(feature = "tracing-log")]
840    #[test]
841    fn tracing_log() {
842        let (_dispatch, writer) = mock_subscriber();
843
844        log::warn!("test log");
845        let json = writer.read_json();
846        println!("{json}");
847        assert!(json["timestamp"].as_str().unwrap().ends_with('Z'));
848        assert_eq!(json["level"].as_str().unwrap(), "WARN");
849        assert_eq!(json["code.file.path"].as_str().unwrap(), "src/lib.rs");
850        assert_eq!(json["code.line.number"].as_i64().unwrap(), pline!(6));
851        assert_eq!(json["message"].as_str().unwrap(), "test log");
852        assert_eq!(json.as_object().unwrap().len(), 5);
853
854        #[cfg(feature = "tracing-opentelemetry")]
855        let extra_fields = 1;
856        #[cfg(not(feature = "tracing-opentelemetry"))]
857        let extra_fields = 0;
858
859        let span1 = info_span!("span1", c = "three");
860        let _g1 = span1.enter();
861        log::error!("test log span1");
862        let json = writer.read_json();
863        println!("{json}");
864        assert!(json["timestamp"].as_str().unwrap().ends_with('Z'));
865        assert_eq!(json["level"].as_str().unwrap(), "ERROR");
866        assert_eq!(json["code.file.path"].as_str().unwrap(), "src/lib.rs");
867        assert_eq!(json["code.line.number"].as_i64().unwrap(), pline!(6));
868        #[cfg(feature = "tracing-opentelemetry")]
869        assert_eq!(json["trace_id"].as_str().unwrap().len(), 32);
870        assert_eq!(json["message"].as_str().unwrap(), "test log span1");
871        assert_eq!(json["c"].as_str().unwrap(), "three");
872        assert_eq!(json.as_object().unwrap().len(), 6 + extra_fields);
873
874        let span2 = info_span!("span2", d = 4.0);
875        let _g2 = span2.enter();
876        log::debug!("test log span2");
877        let json = writer.read_json();
878        println!("{json}");
879        assert!(json["timestamp"].as_str().unwrap().ends_with('Z'));
880        assert_eq!(json["level"].as_str().unwrap(), "DEBUG");
881        assert_eq!(json["code.file.path"].as_str().unwrap(), "src/lib.rs");
882        assert_eq!(json["code.line.number"].as_i64().unwrap(), pline!(6));
883        #[cfg(feature = "tracing-opentelemetry")]
884        assert_eq!(json["trace_id"].as_str().unwrap().len(), 32);
885        assert_eq!(json["message"].as_str().unwrap(), "test log span2");
886        assert_eq!(json["c"].as_str().unwrap(), "three");
887        assert_eq!(json["d"].as_f64().unwrap(), 4.0);
888        assert_eq!(json.as_object().unwrap().len(), 7 + extra_fields);
889    }
890
891    #[cfg(feature = "tracing-opentelemetry")]
892    #[test]
893    fn tracing_opentelemetry() {
894        use std::collections::HashMap;
895
896        use opentelemetry::propagation::TextMapPropagator;
897        use opentelemetry::trace::TraceContextExt;
898        use opentelemetry_sdk::propagation::TraceContextPropagator;
899        use tracing_opentelemetry::OpenTelemetrySpanExt;
900
901        let (_dispatch, writer) = mock_subscriber();
902
903        // Test auto-generated trace_id.
904        {
905            // No trace_id without a span.
906            warn!("test event");
907            let json = writer.read_json();
908            println!("{json}");
909            assert_eq!(json["trace_id"], serde_json::Value::Null);
910
911            let span1 = info_span!("span1");
912            // Uses a generated trace_id when the parent context is not set.
913            let generated_trace_id =
914                format!("{}", span1.context().span().span_context().trace_id());
915
916            let _g1 = span1.enter();
917            error!("test span1");
918            let json = writer.read_json();
919            println!("{json}");
920            assert_eq!(json["trace_id"].as_str().unwrap(), generated_trace_id);
921
922            let span2 = info_span!("span2");
923            let _g2 = span2.enter();
924            debug!("test span2");
925            let json = writer.read_json();
926            println!("{json}");
927            assert_eq!(json["trace_id"].as_str().unwrap(), generated_trace_id);
928        }
929
930        // Test an explicitly set trace_id.
931        {
932            let fixed_trace_id = "09f91f229168786afbed35fa3a98d86a".to_owned();
933            let mut m = HashMap::with_capacity(1);
934            m.insert(
935                "traceparent".to_owned(),
936                format!("00-{fixed_trace_id}-ce812d502e8f1299-01"),
937            );
938            let cx = TraceContextPropagator::new().extract(&m);
939
940            // No trace_id without a span.
941            warn!("test event");
942            let json = writer.read_json();
943            println!("{json}");
944            assert_eq!(json["trace_id"], serde_json::Value::Null);
945
946            let span1 = info_span!("span1");
947            span1.set_parent(cx);
948
949            let _g1 = span1.enter();
950            error!("test span1");
951            let json = writer.read_json();
952            println!("{json}");
953            assert_eq!(json["trace_id"].as_str().unwrap(), fixed_trace_id);
954
955            let span2 = info_span!("span2");
956            let _g2 = span2.enter();
957            debug!("test span2");
958            let json = writer.read_json();
959            println!("{json}");
960            assert_eq!(json["trace_id"].as_str().unwrap(), fixed_trace_id);
961        }
962    }
963}