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