datadog_formatting_layer/
layer.rs

1use crate::{
2    datadog_ids,
3    event_sink::{EventSink, StdoutSink},
4    fields::{self, FieldPair, FieldStore},
5    formatting::DatadogLog,
6};
7use chrono::Utc;
8use tracing::{span::Attributes, Event, Id, Subscriber};
9use tracing_subscriber::{layer::Context, registry::LookupSpan, Layer};
10
11/// The layer responsible for formatting tracing events in a way datadog can parse them
12#[non_exhaustive]
13#[derive(Debug, Clone)]
14pub struct DatadogFormattingLayer<Sink: EventSink + 'static> {
15    event_sink: Sink,
16}
17
18impl<S: EventSink + 'static> DatadogFormattingLayer<S> {
19    /// Create a new `DatadogFormattingLayer` with the provided event sink
20    ///
21    /// # Example
22    /// ```
23    /// use datadog_formatting_layer::{DatadogFormattingLayer, EventSink, StdoutSink};
24    ///
25    /// let layer: DatadogFormattingLayer<StdoutSink> =
26    ///     DatadogFormattingLayer::with_sink(StdoutSink::default());
27    /// ```
28    pub const fn with_sink(sink: S) -> Self {
29        Self { event_sink: sink }
30    }
31}
32
33impl Default for DatadogFormattingLayer<StdoutSink> {
34    fn default() -> Self {
35        Self::with_sink(StdoutSink::default())
36    }
37}
38
39impl<S: Subscriber + for<'a> LookupSpan<'a>, Sink: EventSink + 'static> Layer<S>
40    for DatadogFormattingLayer<Sink>
41{
42    fn on_new_span(&self, span_attrs: &Attributes<'_>, id: &Id, ctx: Context<'_, S>) {
43        #[allow(clippy::expect_used)]
44        let span = ctx.span(id).expect("Span not found, this is a bug");
45
46        let mut extensions = span.extensions_mut();
47
48        let fields = fields::from_attributes(span_attrs);
49
50        // insert fields from new span e.g #[instrument(fields(hello = "world"))]
51        if extensions.get_mut::<FieldStore>().is_none() {
52            extensions.insert(FieldStore { fields });
53        }
54    }
55
56    // IDEA: maybe an on record implementation is required here
57
58    fn on_event(&self, event: &Event<'_>, ctx: Context<'_, S>) {
59        let event_fields = fields::from_event(event);
60
61        // find message if present in event fields
62        let message = event_fields
63            .iter()
64            .find(|pair| pair.name == "message")
65            .map(|pair| pair.value.clone())
66            .unwrap_or_default();
67
68        let all_fields: Vec<FieldPair> = Vec::default()
69            .into_iter()
70            .chain(fields::from_spans(&ctx, event))
71            .chain(event_fields)
72            .collect();
73
74        // look for datadog trace- and span-id
75        let datadog_ids = datadog_ids::read_from_context(&ctx);
76
77        let log = DatadogLog {
78            timestamp: Utc::now(),
79            level: event.metadata().level().to_owned(),
80            message,
81            fields: all_fields,
82            target: event.metadata().target().to_string(),
83            datadog_ids,
84        };
85
86        let serialized_event = log.format();
87
88        self.event_sink.write(serialized_event);
89    }
90}