Skip to main content

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    #[inline]
171    fn record_float(&mut self, field: &Field, value: f64) {
172        if value.is_finite() {
173            self.record_value(field, value);
174        } else if value.is_nan() {
175            self.record_value(field, "NaN");
176        } else if value == f64::INFINITY {
177            self.record_value(field, "inf");
178        } else if value == f64::NEG_INFINITY {
179            self.record_value(field, "-inf");
180        } else {
181            unreachable!("floats must be finite, NaN, or infinite");
182        }
183    }
184}
185
186// Unlikely serde_json will ever change the default formatter, but 1 object per
187// line is a hard requirement for logging, so it's good to specify.
188type OutputSerializer = serde_json::Serializer<Vec<u8>, serde_json::ser::CompactFormatter>;
189
190// Recorder for events, serializing event fields directly to the output buffer.
191struct FlatJsonEventRecorder<'state: 'state_borrow, 'state_borrow> {
192    json_out: &'state_borrow mut <&'state mut OutputSerializer as Serializer>::SerializeMap,
193    seen_fields: &'state_borrow mut HashSet<&'static str>,
194}
195
196impl FlatJsonRecorder for FlatJsonEventRecorder<'_, '_> {
197    #[inline]
198    fn record_value<T>(&mut self, field: &Field, value: T)
199    where
200        T: Serialize + Into<serde_json::Value>,
201    {
202        if self.seen_fields.insert(field.name()) {
203            self.json_out
204                .serialize_entry(field.name(), &value)
205                .expect_serialization_infallible();
206        }
207    }
208}
209
210// Recorder for spans, storing spans fields in a map to serialize later. Unlike
211// events, span fields are not guaranteed to be recorded in order, and could be
212// recorded more than once, so a map is necessary.
213struct FlatJsonSpanRecorder<'a>(&'a mut HashMap<&'static str, serde_json::Value>);
214
215impl FlatJsonRecorder for FlatJsonSpanRecorder<'_> {
216    #[inline]
217    fn record_value<T>(&mut self, field: &Field, value: T)
218    where
219        T: Serialize + Into<serde_json::Value>,
220    {
221        self.0.insert(field.name(), value.into());
222    }
223}
224
225// Common Visit impl, delegating to either the event or span recorder.
226#[repr(transparent)]
227struct FlatJsonVisitor<T: FlatJsonRecorder>(T);
228
229impl<T: FlatJsonRecorder> Visit for FlatJsonVisitor<T> {
230    #[inline]
231    fn record_f64(&mut self, field: &Field, value: f64) {
232        self.0.record_float(field, value);
233    }
234    #[inline]
235    fn record_i64(&mut self, field: &Field, value: i64) {
236        self.0.record_int(field, value);
237    }
238    #[inline]
239    fn record_u64(&mut self, field: &Field, value: u64) {
240        self.0.record_int(field, value);
241    }
242    #[inline]
243    fn record_i128(&mut self, field: &Field, value: i128) {
244        self.0.record_int(field, value);
245    }
246    #[inline]
247    fn record_u128(&mut self, field: &Field, value: u128) {
248        self.0.record_int(field, value);
249    }
250    #[inline]
251    fn record_bool(&mut self, field: &Field, value: bool) {
252        self.0.record_value(field, value);
253    }
254    #[inline]
255    fn record_str(&mut self, field: &Field, value: &str) {
256        self.0.record_value(field, value);
257    }
258    #[inline]
259    fn record_bytes(&mut self, field: &Field, value: &[u8]) {
260        // Assume raw binary data logged is mostly human readable.
261        self.0.record_value(field, value.escape_ascii().to_string());
262    }
263    #[inline]
264    fn record_error(&mut self, field: &Field, value: &(dyn Error + 'static)) {
265        // Use alternate format to include context chain for anyhow::Error.
266        self.0.record_value(field, format!("{value:#}"));
267    }
268    #[inline]
269    fn record_debug(&mut self, field: &Field, value: &dyn Debug) {
270        self.0.record_value(field, format!("{value:?}"));
271    }
272}
273
274// Only needs to record the "message" field added by tracing-log. The only other
275// fields are created by tracing-log to save metadata, and efficiently exposed
276// by tracing-log via tracing_log::NormalizeEvent.
277#[cfg(feature = "tracing-log")]
278struct TracingLogVisitor<'state: 'state_borrow, 'state_borrow> {
279    json_out: &'state_borrow mut <&'state mut OutputSerializer as Serializer>::SerializeMap,
280    seen_fields: &'state_borrow mut HashSet<&'static str>,
281}
282
283#[cfg(feature = "tracing-log")]
284impl Visit for TracingLogVisitor<'_, '_> {
285    #[inline]
286    fn record_f64(&mut self, field: &Field, _value: f64) {
287        debug_assert!(false, "{}", field.name());
288    }
289    #[inline]
290    fn record_i64(&mut self, field: &Field, _value: i64) {
291        debug_assert!(false, "{}", field.name());
292    }
293    #[inline]
294    fn record_u64(&mut self, field: &Field, _value: u64) {
295        debug_assert_eq!(field.name(), "log.line");
296    }
297    #[inline]
298    fn record_i128(&mut self, field: &Field, _value: i128) {
299        debug_assert!(false, "{}", field.name());
300    }
301    #[inline]
302    fn record_u128(&mut self, field: &Field, _value: u128) {
303        debug_assert!(false, "{}", field.name());
304    }
305    #[inline]
306    fn record_bool(&mut self, field: &Field, _value: bool) {
307        debug_assert!(false, "{}", field.name());
308    }
309    #[inline]
310    fn record_str(&mut self, field: &Field, _value: &str) {
311        let n = field.name();
312        debug_assert!(
313            n == "log.target" || n == "log.module_path" || n == "log.file",
314            "{n}"
315        );
316    }
317    #[inline]
318    fn record_bytes(&mut self, field: &Field, _value: &[u8]) {
319        debug_assert!(false, "{}", field.name());
320    }
321    #[inline]
322    fn record_error(&mut self, field: &Field, _value: &(dyn Error + 'static)) {
323        debug_assert!(false, "{}", field.name());
324    }
325    #[inline]
326    fn record_debug(&mut self, field: &Field, value: &dyn Debug) {
327        const MESSAGE: &str = "message";
328        debug_assert_eq!(field.name(), MESSAGE);
329        if field.name() == MESSAGE {
330            let is_new = self.seen_fields.insert(MESSAGE);
331            debug_assert!(is_new);
332            self.json_out
333                .serialize_entry(MESSAGE, &format!("{value:?}"))
334                .expect_serialization_infallible();
335        }
336    }
337}
338
339/// See the [module-level documentation](crate) for usage.
340pub struct FlatJsonLayer<S, W>
341where
342    W: for<'writer> MakeWriter<'writer> + 'static,
343{
344    make_writer: W,
345    _inner: PhantomData<fn(S)>,
346}
347
348impl<S, W> FlatJsonLayer<S, W>
349where
350    W: for<'writer> MakeWriter<'writer> + 'static,
351{
352    /// Returns a new [`FlatJsonLayer`] using the provided [`MakeWriter`] to
353    /// write events.
354    #[inline]
355    pub fn new(make_writer: W) -> Self {
356        Self {
357            make_writer,
358            _inner: PhantomData,
359        }
360    }
361}
362
363// Span extension for storing span fields until they're used emitting events.
364struct FlatJsonSpanData(HashMap<&'static str, serde_json::Value>);
365
366impl<S, W> Layer<S> for FlatJsonLayer<S, W>
367where
368    S: Subscriber + for<'a> LookupSpan<'a>,
369    W: for<'w> MakeWriter<'w> + 'static,
370{
371    fn on_new_span(&self, attrs: &Attributes<'_>, id: &Id, ctx: Context<'_, S>) {
372        if attrs.fields().is_empty() {
373            return;
374        }
375        let span = ctx
376            .span(id)
377            .expect("span not found, this is a subscriber bug");
378        let mut extensions = span.extensions_mut();
379        let mut json_map = HashMap::with_capacity(attrs.fields().len());
380        attrs.record(&mut FlatJsonVisitor(FlatJsonSpanRecorder(&mut json_map)));
381        extensions.insert(FlatJsonSpanData(json_map));
382    }
383
384    fn on_record(&self, id: &Id, values: &Record<'_>, ctx: Context<'_, S>) {
385        let span = ctx
386            .span(id)
387            .expect("span not found, this is a subscriber bug");
388        let mut extensions = span.extensions_mut();
389        // This layer's on_new_span callback always inserts an extension if the
390        // span has any fields, and spans cannot have fields recorded at all if
391        // no fields were initially declared.
392        let FlatJsonSpanData(json_map) = extensions
393            .get_mut::<FlatJsonSpanData>()
394            .expect("tracing-flat-json span extension not found, this is a bug");
395        values.record(&mut FlatJsonVisitor(FlatJsonSpanRecorder(json_map)));
396    }
397
398    #[expect(clippy::too_many_lines)]
399    fn on_event(&self, event: &Event<'_>, ctx: Context<'_, S>) {
400        // The time crate always uses Z for UTC, and will output 9 digits of
401        // nanoseconds without rounding if that resolution is available.
402        // https://github.com/time-rs/time/blob/v0.3.44/time/src/formatting/formattable.rs#L236-L275
403        const MAX_TIME_LEN: usize = "0000-00-00T00:00:00.000000000Z".len();
404        let mut ts_buf = [0u8; MAX_TIME_LEN];
405        let mut ts_cursor = &mut ts_buf[..];
406        UtcDateTime::now()
407            .format_into(&mut ts_cursor, &Rfc3339)
408            .expect("formatting current UTC time should never fail");
409        let ts_len = MAX_TIME_LEN - ts_cursor.len();
410        // SAFETY: In the inconceivable case that time formats a timestamp as
411        // non-utf8, it's better to output non-utf8 than crash or log nothing.
412        let ts = unsafe { std::str::from_utf8_unchecked(&ts_buf[..ts_len]) };
413
414        let mut serializer: OutputSerializer =
415            serde_json::Serializer::new(Vec::with_capacity(1024));
416        // serde_json only uses the size hint when it's 0, for empty maps.
417        let mut json_out = serializer
418            .serialize_map(None)
419            .expect_serialization_infallible();
420
421        #[cfg(feature = "tracing-log")]
422        let log_metadata = {
423            use tracing_log::NormalizeEvent;
424            event.normalized_metadata()
425        };
426        #[cfg(feature = "tracing-log")]
427        let metadata = match &log_metadata {
428            Some(metadata) => metadata,
429            None => event.metadata(),
430        };
431        #[cfg(not(feature = "tracing-log"))]
432        let log_metadata: Option<()> = None;
433        #[cfg(not(feature = "tracing-log"))]
434        let metadata = event.metadata();
435
436        // timestamp + level + file + line + fields.
437        let total_event_fields = 4 + metadata.fields().len();
438        // trace_id.
439        #[cfg(feature = "tracing-opentelemetry")]
440        let total_event_fields = total_event_fields + 1;
441
442        // Avoid outputting the same JSON key twice. +4 bumps a small bucket
443        // count up to the next size class, saving a realloc when parent spans
444        // have fields. Nets a 7-10% speedup in single threaded microbenchmarks,
445        // but mostly it just makes me feel better.
446        let mut seen_fields = HashSet::with_capacity(total_event_fields + 4);
447        macro_rules! serialize_entry {
448            ($k:literal, $v: expr) => {
449                let is_new = seen_fields.insert($k);
450                debug_assert!(is_new, "{}", $k);
451                json_out
452                    .serialize_entry($k, $v)
453                    .expect_serialization_infallible();
454            };
455        }
456
457        // Write timestamp, level, and code location first, like text loggers
458        // typically do (including tracing-subscriber), making the logs easier
459        // to read when looking at the raw JSON output.
460        serialize_entry!("timestamp", &ts);
461        serialize_entry!("level", metadata.level().as_str());
462
463        // Output trace_id before code location, since it has consistent width.
464        #[cfg(feature = "tracing-opentelemetry")]
465        {
466            use opentelemetry::trace::{TraceContextExt, TraceId};
467            use tracing_opentelemetry::OtelData;
468
469            if let Some(span) = ctx.event_span(event) {
470                if let Some(otel_data) = span.extensions().get::<OtelData>() {
471                    let mut trace_id = otel_data.parent_cx.span().span_context().trace_id();
472
473                    // This event's parent span is a root span, with no parent
474                    // context explicitly set. If this span closes without a
475                    // parent context, it will use this trace_id. A parent may
476                    // be set after an event is emitted, making this trace_id
477                    // meaningless, but that future trace_id cannot be known
478                    // from here. Child spans have the same problem, i.e. they
479                    // also inherit this trace_id when created before setting a
480                    // parent context.
481                    // See also: tracing_opentelemetry::PreSampledTracer.
482                    if trace_id == TraceId::INVALID {
483                        if let Some(builder_trace_id) = otel_data.builder.trace_id {
484                            trace_id = builder_trace_id;
485                        }
486                    }
487
488                    // I don't think it's possible to have an invalid trace_id
489                    // at this point. If there's a parent span, and that span
490                    // has OtelData, it should have either a parent context or
491                    // a builder trace_id, even when not sampled.
492                    debug_assert_ne!(trace_id, TraceId::INVALID);
493                    serialize_entry!("trace_id", &format!("{trace_id}"));
494                }
495            }
496        }
497
498        // Use names from opentelemetry semantic conventions.
499        if let Some(file) = metadata.file() {
500            serialize_entry!("code.file.path", file);
501        }
502        if let Some(line) = metadata.line() {
503            serialize_entry!("code.line.number", &line);
504        }
505
506        // Serialize the event before spans, then spans from nearest to root,
507        // putting the most specific information first. This is purely a matter
508        // of taste, tracing-subscriber's text format does the opposite, i.e. it
509        // outputs spans in hierarchical order.
510        if log_metadata.is_some() {
511            #[cfg(feature = "tracing-log")]
512            event.record(&mut TracingLogVisitor {
513                json_out: &mut json_out,
514                seen_fields: &mut seen_fields,
515            });
516            #[cfg(not(feature = "tracing-log"))]
517            unreachable!();
518        } else {
519            event.record(&mut FlatJsonVisitor(FlatJsonEventRecorder {
520                json_out: &mut json_out,
521                seen_fields: &mut seen_fields,
522            }));
523        }
524        if let Some(scope) = ctx.event_scope(event) {
525            for span in scope {
526                // Spans have no extension if the span has no fields.
527                if let Some(FlatJsonSpanData(json_map)) = span.extensions().get() {
528                    // Output fields in the order they appeared on the span.
529                    for field in span.fields() {
530                        let k = field.name();
531                        // Fields initialized as tracing::field::Empty and never
532                        // given a value will not be in the map.
533                        if let Some(v) = json_map.get(k) {
534                            // Only serialize each field once.
535                            if seen_fields.insert(k) {
536                                json_out
537                                    .serialize_entry(k, v)
538                                    .expect_serialization_infallible();
539                            }
540                        }
541                    }
542                }
543            }
544        }
545
546        json_out.end().expect_serialization_infallible();
547        let mut buf = serializer.into_inner();
548        buf.push(b'\n');
549
550        if let Err(e) = self.make_writer.make_writer_for(metadata).write_all(&buf) {
551            // Avoid spamming stderr when the output is a closed pipe, as that
552            // often happens for benign reasons, e.g. piping stdout to head.
553            if e.kind() != std::io::ErrorKind::BrokenPipe {
554                eprintln!("[tracing-flat-json] write error: {e:?}");
555            }
556        }
557    }
558}
559
560#[cfg(test)]
561mod tests {
562    use std::sync::{Arc, Mutex};
563
564    use tracing::dispatcher::DefaultGuard;
565    use tracing::{debug, error, info, info_span, warn};
566    use tracing_subscriber::layer::SubscriberExt;
567    use tracing_subscriber::registry::SpanData;
568    use tracing_subscriber::util::SubscriberInitExt;
569    use tracing_subscriber::Registry;
570
571    use super::*;
572
573    #[derive(Clone, Default)]
574    struct MockWriter(Arc<Mutex<Vec<u8>>>);
575
576    impl Write for MockWriter {
577        fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
578            // Tests are all single-threaded.
579            self.0.try_lock().unwrap().write(buf)
580        }
581        fn flush(&mut self) -> std::io::Result<()> {
582            // Tests are all single-threaded.
583            self.0.try_lock().unwrap().flush()
584        }
585    }
586
587    impl MockWriter {
588        fn read_json(&self) -> serde_json::Value {
589            let mut buf = self.0.try_lock().unwrap();
590            let json = serde_json::from_slice(&buf).unwrap();
591            buf.clear();
592            json
593        }
594        fn read_string(&self) -> String {
595            let mut buf = self.0.try_lock().unwrap();
596            let vec = std::mem::take(&mut *buf);
597            String::from_utf8(vec).unwrap()
598        }
599    }
600
601    fn mock_subscriber() -> (DefaultGuard, MockWriter) {
602        let writer = MockWriter::default();
603        let make_writer = move || writer.clone();
604        let return_writer = make_writer();
605
606        #[cfg(feature = "tracing-opentelemetry")]
607        let subscriber = {
608            use opentelemetry::trace::TracerProvider;
609            let exporter = opentelemetry_stdout::SpanExporter::default();
610            let provider = opentelemetry_sdk::trace::SdkTracerProvider::builder()
611                .with_simple_exporter(exporter)
612                .build();
613            let tracer = provider.tracer(env!("CARGO_PKG_NAME"));
614            let layer = tracing_opentelemetry::layer().with_tracer(tracer);
615            Registry::default().with(layer)
616        };
617
618        #[cfg(not(feature = "tracing-opentelemetry"))]
619        let subscriber = Registry::default();
620
621        let subscriber = subscriber.with(FlatJsonLayer::new(make_writer));
622        (subscriber.set_default(), return_writer)
623    }
624
625    macro_rules! pline {
626        ($n:literal) => {
627            i64::from(line!()) - $n
628        };
629    }
630
631    #[test]
632    fn field_values() {
633        let (_dispatch, writer) = mock_subscriber();
634
635        info!("test message");
636        let json = writer.read_json();
637        println!("{json}");
638        assert!(json["timestamp"].as_str().unwrap().ends_with('Z'));
639        assert_eq!(json["level"].as_str().unwrap(), "INFO");
640        assert_eq!(json["code.file.path"].as_str().unwrap(), "src/lib.rs");
641        assert_eq!(json["code.line.number"].as_i64().unwrap(), pline!(6));
642        assert_eq!(json["message"].as_str().unwrap(), "test message");
643        assert_eq!(json.as_object().unwrap().len(), 5);
644
645        warn!(a = 1, b = "two", "test fields");
646        let json = writer.read_json();
647        println!("{json}");
648        assert!(json["timestamp"].as_str().unwrap().ends_with('Z'));
649        assert_eq!(json["level"].as_str().unwrap(), "WARN");
650        assert_eq!(json["code.file.path"].as_str().unwrap(), "src/lib.rs");
651        assert_eq!(json["code.line.number"].as_i64().unwrap(), pline!(6));
652        assert_eq!(json["message"].as_str().unwrap(), "test fields");
653        assert_eq!(json["a"].as_i64().unwrap(), 1);
654        assert_eq!(json["b"].as_str().unwrap(), "two");
655        assert_eq!(json.as_object().unwrap().len(), 7);
656
657        // trace_id
658        #[cfg(feature = "tracing-opentelemetry")]
659        let extra_fields = 1;
660        #[cfg(not(feature = "tracing-opentelemetry"))]
661        let extra_fields = 0;
662
663        let span1 = info_span!("span1", c = "three");
664        let _g1 = span1.enter();
665        error!("test span1");
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(), "ERROR");
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 span1");
675        assert_eq!(json["c"].as_str().unwrap(), "three");
676        assert_eq!(json.as_object().unwrap().len(), 6 + extra_fields);
677
678        let span2 = info_span!("span2", d = 4.0);
679        let _g2 = span2.enter();
680        debug!("test span2");
681        let json = writer.read_json();
682        println!("{json}");
683        assert!(json["timestamp"].as_str().unwrap().ends_with('Z'));
684        assert_eq!(json["level"].as_str().unwrap(), "DEBUG");
685        assert_eq!(json["code.file.path"].as_str().unwrap(), "src/lib.rs");
686        assert_eq!(json["code.line.number"].as_i64().unwrap(), pline!(6));
687        #[cfg(feature = "tracing-opentelemetry")]
688        assert_eq!(json["trace_id"].as_str().unwrap().len(), 32);
689        assert_eq!(json["message"].as_str().unwrap(), "test span2");
690        assert_eq!(json["c"].as_str().unwrap(), "three");
691        assert_eq!(json["d"].as_f64().unwrap(), 4.0);
692        assert_eq!(json.as_object().unwrap().len(), 7 + extra_fields);
693    }
694
695    #[test]
696    fn no_dup_fields() {
697        let (_dispatch, writer) = mock_subscriber();
698
699        // Baseline, there is obviously no duplicate field here.
700        let span1 = info_span!("span1", f = "a");
701        let _g1 = span1.enter();
702        info!("e");
703        let s = writer.read_string();
704        println!("{s}");
705        let json: serde_json::Value = serde_json::from_str(&s).unwrap();
706        println!("{json}");
707        assert_eq!(json["f"].as_str().unwrap(), "a");
708        let f_count = s.split(',').filter(|s| s.starts_with("\"f\":")).count();
709        assert_eq!(f_count, 1);
710
711        // Duplicated "f", latest span should take precedent.
712        let span2 = info_span!("span2", f = "b");
713        let _g2 = span2.enter();
714        info!("e");
715        let s = writer.read_string();
716        println!("{s}");
717        let json: serde_json::Value = serde_json::from_str(&s).unwrap();
718        println!("{json}");
719        assert_eq!(json["f"].as_str().unwrap(), "b");
720        let f_count = s.split(',').filter(|s| s.starts_with("\"f\":")).count();
721        assert_eq!(f_count, 1);
722
723        // Duplicated "f", event should take precedent.
724        info!(f = "c");
725        let s = writer.read_string();
726        println!("{s}");
727        let json: serde_json::Value = serde_json::from_str(&s).unwrap();
728        println!("{json}");
729        assert_eq!(json["f"].as_str().unwrap(), "c");
730        let f_count = s.split(',').filter(|s| s.starts_with("\"f\":")).count();
731        assert_eq!(f_count, 1);
732    }
733
734    #[test]
735    fn record_fields() {
736        let (_dispatch, writer) = mock_subscriber();
737
738        let span = info_span!(
739            "span",
740            f1 = tracing::field::Empty,
741            f2 = "a",
742            f3 = tracing::field::Empty
743        );
744        let _g = span.enter();
745
746        // Baseline, "f1" and "f3" should have no value.
747        info!("e");
748        let s = writer.read_string();
749        println!("{s}");
750        let json: serde_json::Value = serde_json::from_str(&s).unwrap();
751        println!("{json}");
752        assert_eq!(json["f1"], serde_json::Value::Null);
753        assert_eq!(json["f2"].as_str().unwrap(), "a");
754        assert_eq!(json["f3"], serde_json::Value::Null);
755
756        // Record new value to "f1".
757        span.record("f1", "b");
758        info!("e");
759        let s = writer.read_string();
760        println!("{s}");
761        let json: serde_json::Value = serde_json::from_str(&s).unwrap();
762        println!("{json}");
763        assert_eq!(json["f1"].as_str().unwrap(), "b");
764        assert_eq!(json["f2"].as_str().unwrap(), "a");
765        assert_eq!(json["f3"], serde_json::Value::Null);
766
767        // Re-record new value to "f1", and ensure last value wins, as duplicate
768        // keys in the output would be invalid JSON.
769        // Interestingly, tracing-opentelemetry doesn't seem to prevent this,
770        // generating spans with duplicate attributes, even though the otel spec
771        // says last value wins.
772        span.record("f1", "c");
773        span.record("f4", "invalid, not initially declared");
774        info!("e");
775        let s = writer.read_string();
776        println!("{s}");
777        let json: serde_json::Value = serde_json::from_str(&s).unwrap();
778        println!("{json}");
779        assert_eq!(json["f1"].as_str().unwrap(), "c");
780        assert_eq!(json["f2"].as_str().unwrap(), "a");
781        assert_eq!(json["f3"], serde_json::Value::Null);
782        assert_eq!(json["f4"], serde_json::Value::Null);
783
784        // Setting f1 twice should not result in more than 1 output.
785        let f1_count = s.split(',').filter(|s| s.starts_with("\"f1\":")).count();
786        assert_eq!(f1_count, 1);
787
788        // f1 set after f2, but should still be first in output.
789        assert!(s.contains(r#""f1":"c","f2":"a""#));
790    }
791
792    #[test]
793    fn no_extension() {
794        let (_dispatch, writer) = mock_subscriber();
795
796        let span = info_span!("span2");
797        let _g = span.enter();
798
799        // A span with no initially declared fields should not have an extension
800        // inserted by FlatJsonLayer.
801        span.with_subscriber(|(id, subscriber)| {
802            let registry = subscriber.downcast_ref::<Registry>().unwrap();
803            let span_data = registry.span_data(id).unwrap();
804            assert!(span_data.extensions().get::<FlatJsonSpanData>().is_none());
805        });
806
807        // Recording fields with no extension shouldn't crash when the span had
808        // no initial fields.
809        span.record("f", 1);
810        info!("e");
811        let json = writer.read_json();
812        println!("{json}");
813        assert_eq!(json["f"], serde_json::Value::Null);
814    }
815
816    #[test]
817    fn field_order() {
818        let (_dispatch, writer) = mock_subscriber();
819
820        let span1 = info_span!("span1", b = "b", c = "c");
821        let _g1 = span1.enter();
822        let span2 = info_span!("span2", d = "d", a = "a");
823        let _g2 = span2.enter();
824        info!(e = "e", "event1");
825        let s = writer.read_string();
826        let s = s.trim_ascii_end();
827        println!("{s}");
828
829        let mut field_names = Vec::new();
830        let field_str = s.strip_prefix("{").unwrap().strip_suffix("}").unwrap();
831        for field in field_str.split(',') {
832            let field_key_str = field.split(':').next().unwrap();
833            field_names.push(&field_key_str[1..field_key_str.len() - 1]);
834        }
835        assert_eq!(
836            field_names,
837            &[
838                "timestamp",
839                "level",
840                #[cfg(feature = "tracing-opentelemetry")]
841                "trace_id",
842                "code.file.path",
843                "code.line.number",
844                "message",
845                "e",
846                "d",
847                "a",
848                "b",
849                "c"
850            ]
851        );
852    }
853
854    #[cfg(feature = "tracing-log")]
855    #[test]
856    fn tracing_log() {
857        let (_dispatch, writer) = mock_subscriber();
858
859        log::warn!("test log");
860        let json = writer.read_json();
861        println!("{json}");
862        assert!(json["timestamp"].as_str().unwrap().ends_with('Z'));
863        assert_eq!(json["level"].as_str().unwrap(), "WARN");
864        assert_eq!(json["code.file.path"].as_str().unwrap(), "src/lib.rs");
865        assert_eq!(json["code.line.number"].as_i64().unwrap(), pline!(6));
866        assert_eq!(json["message"].as_str().unwrap(), "test log");
867        assert_eq!(json.as_object().unwrap().len(), 5);
868
869        #[cfg(feature = "tracing-opentelemetry")]
870        let extra_fields = 1;
871        #[cfg(not(feature = "tracing-opentelemetry"))]
872        let extra_fields = 0;
873
874        let span1 = info_span!("span1", c = "three");
875        let _g1 = span1.enter();
876        log::error!("test log span1");
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(), "ERROR");
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 span1");
886        assert_eq!(json["c"].as_str().unwrap(), "three");
887        assert_eq!(json.as_object().unwrap().len(), 6 + extra_fields);
888
889        let span2 = info_span!("span2", d = 4.0);
890        let _g2 = span2.enter();
891        log::debug!("test log span2");
892        let json = writer.read_json();
893        println!("{json}");
894        assert!(json["timestamp"].as_str().unwrap().ends_with('Z'));
895        assert_eq!(json["level"].as_str().unwrap(), "DEBUG");
896        assert_eq!(json["code.file.path"].as_str().unwrap(), "src/lib.rs");
897        assert_eq!(json["code.line.number"].as_i64().unwrap(), pline!(6));
898        #[cfg(feature = "tracing-opentelemetry")]
899        assert_eq!(json["trace_id"].as_str().unwrap().len(), 32);
900        assert_eq!(json["message"].as_str().unwrap(), "test log span2");
901        assert_eq!(json["c"].as_str().unwrap(), "three");
902        assert_eq!(json["d"].as_f64().unwrap(), 4.0);
903        assert_eq!(json.as_object().unwrap().len(), 7 + extra_fields);
904    }
905
906    #[cfg(feature = "tracing-opentelemetry")]
907    #[test]
908    fn tracing_opentelemetry() {
909        use std::collections::HashMap;
910
911        use opentelemetry::propagation::TextMapPropagator;
912        use opentelemetry::trace::TraceContextExt;
913        use opentelemetry_sdk::propagation::TraceContextPropagator;
914        use tracing_opentelemetry::OpenTelemetrySpanExt;
915
916        let (_dispatch, writer) = mock_subscriber();
917
918        // Test auto-generated trace_id.
919        {
920            // No trace_id without a span.
921            warn!("test event");
922            let json = writer.read_json();
923            println!("{json}");
924            assert_eq!(json["trace_id"], serde_json::Value::Null);
925
926            let span1 = info_span!("span1");
927            // Uses a generated trace_id when the parent context is not set.
928            let generated_trace_id =
929                format!("{}", span1.context().span().span_context().trace_id());
930
931            let _g1 = span1.enter();
932            error!("test span1");
933            let json = writer.read_json();
934            println!("{json}");
935            assert_eq!(json["trace_id"].as_str().unwrap(), generated_trace_id);
936
937            let span2 = info_span!("span2");
938            let _g2 = span2.enter();
939            debug!("test span2");
940            let json = writer.read_json();
941            println!("{json}");
942            assert_eq!(json["trace_id"].as_str().unwrap(), generated_trace_id);
943        }
944
945        // Test an explicitly set trace_id.
946        {
947            let fixed_trace_id = "09f91f229168786afbed35fa3a98d86a".to_owned();
948            let mut m = HashMap::with_capacity(1);
949            m.insert(
950                "traceparent".to_owned(),
951                format!("00-{fixed_trace_id}-ce812d502e8f1299-01"),
952            );
953            let cx = TraceContextPropagator::new().extract(&m);
954
955            // No trace_id without a span.
956            warn!("test event");
957            let json = writer.read_json();
958            println!("{json}");
959            assert_eq!(json["trace_id"], serde_json::Value::Null);
960
961            let span1 = info_span!("span1");
962            span1.set_parent(cx);
963
964            let _g1 = span1.enter();
965            error!("test span1");
966            let json = writer.read_json();
967            println!("{json}");
968            assert_eq!(json["trace_id"].as_str().unwrap(), fixed_trace_id);
969
970            let span2 = info_span!("span2");
971            let _g2 = span2.enter();
972            debug!("test span2");
973            let json = writer.read_json();
974            println!("{json}");
975            assert_eq!(json["trace_id"].as_str().unwrap(), fixed_trace_id);
976        }
977    }
978
979    #[test]
980    fn float_nan_inf() {
981        let (_dispatch, writer) = mock_subscriber();
982        let nan32 = f32::NAN;
983        let nan64 = f64::NAN;
984        let inf32 = f32::INFINITY;
985        let inf64 = f64::INFINITY;
986        let ninf32 = f32::NEG_INFINITY;
987        let ninf64 = f64::NEG_INFINITY;
988        info!(nan32, nan64, inf32, inf64, ninf32, ninf64);
989        let json = writer.read_json();
990        assert_eq!(json["nan32"].as_str().unwrap(), "NaN");
991        assert_eq!(json["nan64"].as_str().unwrap(), "NaN");
992        assert_eq!(json["inf32"].as_str().unwrap(), "inf");
993        assert_eq!(json["inf64"].as_str().unwrap(), "inf");
994        assert_eq!(json["ninf32"].as_str().unwrap(), "-inf");
995        assert_eq!(json["ninf64"].as_str().unwrap(), "-inf");
996    }
997
998    #[test]
999    fn large_integer() {
1000        let (_dispatch, writer) = mock_subscriber();
1001        let max_safe = MAX_SAFE_INTEGER;
1002        let min_safe = MIN_SAFE_INTEGER;
1003        let max_representable = max_safe + 1;
1004        let min_representable = min_safe - 1;
1005        info!(max_safe, min_safe, max_representable, min_representable);
1006        let json = writer.read_json();
1007        assert_eq!(json["max_safe"].as_i64().unwrap(), max_safe);
1008        assert_eq!(json["min_safe"].as_i64().unwrap(), min_safe);
1009        assert_eq!(
1010            json["max_representable"].as_str().unwrap(),
1011            max_representable.to_string()
1012        );
1013        assert_eq!(
1014            json["min_representable"].as_str().unwrap(),
1015            min_representable.to_string()
1016        );
1017    }
1018}