tracing_stackdriver/
event_formatter.rs

1use crate::{
2    google::LogSeverity,
3    serializers::{SerializableContext, SerializableSpan, SourceLocation},
4    visitor::Visitor,
5    writer::WriteAdaptor,
6};
7use serde::ser::{SerializeMap, Serializer as _};
8use std::fmt;
9use time::{format_description::well_known::Rfc3339, OffsetDateTime};
10use tracing_core::{Event, Subscriber};
11use tracing_subscriber::{
12    field::VisitOutput,
13    fmt::{
14        format::{self, JsonFields},
15        FmtContext, FormatEvent,
16    },
17    registry::LookupSpan,
18};
19
20#[derive(Debug, thiserror::Error)]
21enum Error {
22    #[error(transparent)]
23    Formatting(#[from] fmt::Error),
24    #[error("JSON serialization error: {0}")]
25    Serialization(#[from] serde_json::Error),
26    #[error(transparent)]
27    Io(#[from] std::io::Error),
28    #[error("Time formatting error: {0}")]
29    Time(#[from] time::error::Format),
30}
31
32impl From<Error> for fmt::Error {
33    fn from(_: Error) -> Self {
34        Self
35    }
36}
37
38/// Tracing Event formatter for Stackdriver layers
39pub struct EventFormatter {
40    pub(crate) include_source_location: bool,
41    #[cfg(feature = "opentelemetry")]
42    pub(crate) cloud_trace_configuration: Option<crate::CloudTraceConfiguration>,
43}
44
45impl EventFormatter {
46    /// Internal event formatting for a given serializer
47    fn format_event<S>(
48        &self,
49        context: &FmtContext<S, JsonFields>,
50        mut serializer: serde_json::Serializer<WriteAdaptor>,
51        event: &Event,
52    ) -> Result<(), Error>
53    where
54        S: Subscriber + for<'span> LookupSpan<'span>,
55    {
56        let time = OffsetDateTime::now_utc().format(&Rfc3339)?;
57        let meta = event.metadata();
58        let severity = LogSeverity::from(meta.level());
59
60        let span = event
61            .parent()
62            .and_then(|id| context.span(id))
63            .or_else(|| context.lookup_current());
64
65        // FIXME: derive an accurate entry count ahead of time
66        let mut map = serializer.serialize_map(None)?;
67
68        // serialize custom fields
69        map.serialize_entry("time", &time)?;
70        map.serialize_entry("target", &meta.target())?;
71
72        if self.include_source_location {
73            if let Some(file) = meta.file() {
74                map.serialize_entry(
75                    "logging.googleapis.com/sourceLocation",
76                    &SourceLocation {
77                        file,
78                        line: meta.line(),
79                    },
80                )?;
81            }
82        }
83
84        // serialize the current span and its leaves
85        if let Some(span) = span {
86            map.serialize_entry("span", &SerializableSpan::new(&span))?;
87            map.serialize_entry("spans", &SerializableContext::new(context))?;
88
89            #[cfg(feature = "opentelemetry")]
90            if let (Some(crate::CloudTraceConfiguration { project_id }), Some(otel_data)) = (
91                self.cloud_trace_configuration.as_ref(),
92                span.extensions().get::<tracing_opentelemetry::OtelData>(),
93            ) {
94                use opentelemetry::trace::TraceContextExt;
95
96                let builder = &otel_data.builder;
97
98                if let Some(span_id) = builder.span_id {
99                    map.serialize_entry("logging.googleapis.com/spanId", &span_id.to_string())?;
100                }
101
102                let (trace_id, trace_sampled) = if otel_data.parent_cx.has_active_span() {
103                    let span_ref = otel_data.parent_cx.span();
104                    let span_context = span_ref.span_context();
105
106                    (Some(span_context.trace_id()), span_context.is_sampled())
107                } else {
108                    (builder.trace_id, false)
109                };
110
111                if let Some(trace_id) = trace_id {
112                    map.serialize_entry(
113                        "logging.googleapis.com/trace",
114                        &format!("projects/{project_id}/traces/{trace_id}",),
115                    )?;
116                }
117
118                if trace_sampled {
119                    map.serialize_entry("logging.googleapis.com/trace_sampled", &true)?;
120                }
121            }
122        }
123
124        // serialize the stackdriver-specific fields with a visitor
125        let mut visitor = Visitor::new(severity, map);
126        event.record(&mut visitor);
127        visitor.finish().map_err(Error::from)?;
128        Ok(())
129    }
130}
131
132impl<S> FormatEvent<S, JsonFields> for EventFormatter
133where
134    S: Subscriber + for<'span> LookupSpan<'span>,
135{
136    fn format_event(
137        &self,
138        context: &FmtContext<S, JsonFields>,
139        mut writer: format::Writer,
140        event: &Event,
141    ) -> fmt::Result
142    where
143        S: Subscriber + for<'span> LookupSpan<'span>,
144    {
145        let serializer = serde_json::Serializer::new(WriteAdaptor::new(&mut writer));
146        self.format_event(context, serializer, event)?;
147        writeln!(writer)
148    }
149}
150
151impl Default for EventFormatter {
152    fn default() -> Self {
153        Self {
154            include_source_location: true,
155            #[cfg(feature = "opentelemetry")]
156            cloud_trace_configuration: None,
157        }
158    }
159}