metrics_tracing_context/
lib.rs

1//! Use [`tracing::span!`] fields as [`metrics`] labels.
2//!
3//! The `metrics-tracing-context` crate provides tools to enable injecting the
4//! contextual data maintained via `span!` macro from the [`tracing`] crate
5//! into the metrics.
6//!
7//! # Usage
8//!
9//! First, set up `tracing` and `metrics` crates:
10//!
11//! ```rust
12//! # use metrics_util::debugging::DebuggingRecorder;
13//! # use tracing_subscriber::Registry;
14//! use metrics_tracing_context::{MetricsLayer, TracingContextLayer};
15//! use metrics_util::layers::Layer;
16//! use tracing_subscriber::layer::SubscriberExt;
17//!
18//! // Prepare tracing.
19//! # let my_subscriber = Registry::default();
20//! let subscriber = my_subscriber.with(MetricsLayer::new());
21//! tracing::subscriber::set_global_default(subscriber).unwrap();
22//!
23//! // Prepare metrics.
24//! # let my_recorder = DebuggingRecorder::new();
25//! let recorder = TracingContextLayer::all().layer(my_recorder);
26//! metrics::set_global_recorder(recorder).unwrap();
27//! ```
28//!
29//! Then emit some metrics within spans and see the labels being injected!
30//!
31//! ```rust
32//! # use metrics_util::{debugging::DebuggingRecorder, layers::Layer};
33//! # use tracing_subscriber::{layer::SubscriberExt, Registry};
34//! # use metrics_tracing_context::{MetricsLayer, TracingContextLayer};
35//! # let mysubscriber = Registry::default();
36//! # let subscriber = mysubscriber.with(MetricsLayer::new());
37//! # tracing::subscriber::set_global_default(subscriber).unwrap();
38//! # let myrecorder = DebuggingRecorder::new();
39//! # let recorder = TracingContextLayer::all().layer(myrecorder);
40//! # metrics::set_global_recorder(recorder).unwrap();
41//! use tracing::{span, Level};
42//! use metrics::counter;
43//!
44//! let user = "ferris";
45//! let span = span!(Level::TRACE, "login", user);
46//! let _guard = span.enter();
47//!
48//! counter!("login_attempts", "service" => "login_service").increment(1);
49//! ```
50//!
51//! The code above will emit a increment for a `login_attempts` counter with
52//! the following labels:
53//! - `service=login_service`
54//! - `user=ferris`
55//!
56//! # Implementation
57//!
58//! The integration layer works by capturing all fields that are present when a span is created,
59//! as well as fields recorded after the fact, and storing them as an extension to the span. If
60//! a metric is emitted while a span is entered, any fields captured for that span will be added
61//! to the metric as additional labels.
62//!
63//! Be aware that we recursively capture the fields of a span, including fields from
64//! parent spans, and use them when generating metric labels. This means that if a metric is being
65//! emitted in span B, which is a child of span A, and span A has field X, and span B has field Y,
66//! then the metric labels will include both field X and Y. This applies regardless of how many
67//! nested spans are currently entered.
68//!
69//! ## Duplicate span fields
70//!
71//! When span fields are captured, they are deduplicated such that only the most recent value is kept.
72//! For merging parent span fields into the current span fields, the fields from the current span have
73//! the highest priority. Additionally, when using [`Span::record`][tracing::Span::record] to add fields
74//! to a span after it has been created, the same behavior applies. This means that recording a field
75//! multiple times only keeps the most recently recorded value, including if a field was already present
76//! from a parent span and is then recorded dynamically in the current span.
77//!
78//! ## Span fields and ancestry
79//!
80//! Likewise, we capture the sum of all fields for a span and its parent span(s), meaning that if you have the
81//! following span stack:
82//!
83//! ```text
84//! root span        (fieldA => valueA)
85//!  ⤷ mid-tier span (fieldB => valueB)
86//!     ⤷ leaf span  (fieldC => valueC)
87//! ```
88//!
89//! Then a metric emitted while within the leaf span would get, as labels, all three fields: A, B,
90//! and C.  As well, this layer does _not_ deduplicate the fields.  If you have two instance of the
91//! same field name, both versions will be included in your metric labels.  Whether or not those are
92//! deduplicated, and how they're deduplicated, is an exporter-specific implementation detail.
93//!
94//! In addition, for performance purposes, span fields are held in pooled storage, and additionally
95//! will copy the fields of parent spans.  Following the example span stack from above, the mid-tier
96//! span would hold both field A and B, while the leaf span would hold fields A, B, and C.
97//!
98//! In practice, these extra memory consumption used by these techniques should not matter for
99//! modern systems, but may represent an unacceptable amount of memory usage on constrained systems
100//! such as embedded platforms, etc.
101#![deny(missing_docs)]
102#![cfg_attr(docsrs, feature(doc_cfg), deny(rustdoc::broken_intra_doc_links))]
103
104use metrics::{
105    Counter, Gauge, Histogram, Key, KeyName, Label, Metadata, Recorder, SharedString, Unit,
106};
107use metrics_util::layers::Layer;
108
109pub mod label_filter;
110mod tracing_integration;
111
112pub use label_filter::LabelFilter;
113use tracing_integration::Map;
114pub use tracing_integration::{Labels, MetricsLayer};
115
116/// [`TracingContextLayer`] provides an implementation of a [`Layer`] for [`TracingContext`].
117#[derive(Debug)]
118pub struct TracingContextLayer<F> {
119    label_filter: F,
120}
121
122impl<F> TracingContextLayer<F> {
123    /// Creates a new [`TracingContextLayer`].
124    pub fn new(label_filter: F) -> Self {
125        Self { label_filter }
126    }
127}
128
129impl TracingContextLayer<label_filter::IncludeAll> {
130    /// Creates a new [`TracingContextLayer`].
131    pub fn all() -> Self {
132        Self { label_filter: label_filter::IncludeAll }
133    }
134}
135
136impl TracingContextLayer<label_filter::Allowlist> {
137    /// Creates a new [`TracingContextLayer`] that only allows labels contained
138    /// in a predefined list.
139    pub fn only_allow<I, S>(allowed: I) -> Self
140    where
141        I: IntoIterator<Item = S>,
142        S: AsRef<str>,
143    {
144        Self { label_filter: label_filter::Allowlist::new(allowed) }
145    }
146}
147
148impl<R, F> Layer<R> for TracingContextLayer<F>
149where
150    F: Clone,
151{
152    type Output = TracingContext<R, F>;
153
154    fn layer(&self, inner: R) -> Self::Output {
155        TracingContext { inner, label_filter: self.label_filter.clone() }
156    }
157}
158
159/// [`TracingContext`] is a [`metrics::Recorder`] that injects labels from [`tracing::Span`]s.
160#[derive(Debug)]
161pub struct TracingContext<R, F> {
162    inner: R,
163    label_filter: F,
164}
165
166impl<R, F> TracingContext<R, F>
167where
168    F: LabelFilter,
169{
170    fn enhance_key(&self, key: &Key) -> Option<Key> {
171        // Care is taken to only create a new key if absolutely necessary, which means avoiding
172        // creating a new key if there are no tracing fields at all.
173        //
174        // Technically speaking, we would also want to avoid creating a new key if all the tracing
175        // fields ended up being filtered out, but we don't have a great way of doing that without
176        // first scanning the tracing fields to see if one of them would match, where the cost of
177        // doing the iteration would likely exceed the cost of simply constructing the new key.
178        tracing::dispatcher::get_default(|dispatch| {
179            let current = dispatch.current_span();
180            let id = current.id()?;
181            let ctx = dispatch.downcast_ref::<MetricsLayer>()?;
182
183            let mut f = |mut span_labels: Map| {
184                (!span_labels.is_empty()).then(|| {
185                    let (name, labels) = key.clone().into_parts();
186
187                    // Filter only span labels
188                    span_labels.retain(|key: &SharedString, value: &mut SharedString| {
189                        let label = Label::new(key.clone(), value.clone());
190                        self.label_filter.should_include_label(&name, &label)
191                    });
192
193                    // Overwrites labels from spans
194                    span_labels.extend(labels.into_iter().map(Label::into_parts));
195
196                    let labels = span_labels
197                        .into_iter()
198                        .map(|(key, value)| Label::new(key, value))
199                        .collect::<Vec<_>>();
200
201                    Key::from_parts(name, labels)
202                })
203            };
204
205            // Pull in the span's fields/labels if they exist.
206            ctx.with_labels(dispatch, id, &mut f)
207        })
208    }
209}
210
211impl<R, F> Recorder for TracingContext<R, F>
212where
213    R: Recorder,
214    F: LabelFilter,
215{
216    fn describe_counter(&self, key_name: KeyName, unit: Option<Unit>, description: SharedString) {
217        self.inner.describe_counter(key_name, unit, description)
218    }
219
220    fn describe_gauge(&self, key_name: KeyName, unit: Option<Unit>, description: SharedString) {
221        self.inner.describe_gauge(key_name, unit, description)
222    }
223
224    fn describe_histogram(&self, key_name: KeyName, unit: Option<Unit>, description: SharedString) {
225        self.inner.describe_histogram(key_name, unit, description)
226    }
227
228    fn register_counter(&self, key: &Key, metadata: &Metadata<'_>) -> Counter {
229        let new_key = self.enhance_key(key);
230        let key = new_key.as_ref().unwrap_or(key);
231        self.inner.register_counter(key, metadata)
232    }
233
234    fn register_gauge(&self, key: &Key, metadata: &Metadata<'_>) -> Gauge {
235        let new_key = self.enhance_key(key);
236        let key = new_key.as_ref().unwrap_or(key);
237        self.inner.register_gauge(key, metadata)
238    }
239
240    fn register_histogram(&self, key: &Key, metadata: &Metadata<'_>) -> Histogram {
241        let new_key = self.enhance_key(key);
242        let key = new_key.as_ref().unwrap_or(key);
243        self.inner.register_histogram(key, metadata)
244    }
245}