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
//! 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::DebuggingRecorder;
//! # use tracing_subscriber::Registry;
//! use metrics_util::layers::Layer;
//! use tracing_subscriber::layer::SubscriberExt;
//! use metrics_tracing_context::{MetricsLayer, TracingContextLayer};
//!
//! // 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_boxed_recorder(Box::new(recorder)).unwrap();
//! ```
//!
//! Then emit some metrics within spans and see the labels being injected!
//!
//! ```rust
//! # use metrics_util::{layers::Layer, DebuggingRecorder};
//! # 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_boxed_recorder(Box::new(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", 1, "service" => "login_service");
//! ```
//!
//! 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 present when a span is created and storing
//! them as an extension to the span. If a metric is emitted while a span is entered, we check that
//! span to see if it has any fields in the extension data, and if it does, we add those fields as
//! labels to the metric key.
//!
//! There are two important behaviors to be aware of:
//! - we only capture the fields present when the span is created
//! - we store all fields that a span has, including the fields of its parent span(s)
//!
//! ## Lack of dynamism
//!
//! This means that if you use [`Span::record`][tracing::Span::record] to add fields to a span after
//! it has been created, those fields will not be captured and added to your metric key.
//!
//! ## 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(broken_intra_doc_links))]
use metrics::{GaugeValue, Key, Label, Recorder, Unit};
use metrics_util::layers::Layer;
pub mod label_filter;
mod tracing_integration;
pub use label_filter::LabelFilter;
use tracing_integration::WithContext;
pub use tracing_integration::{Labels, MetricsLayer};
/// [`TracingContextLayer`] provides an implementation of a [`Layer`][metrics_util::layers::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<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();
if let Some(id) = current.id() {
// We're currently within a live tracing span, so see if we have an available
// metrics context to grab any fields/labels out of.
if let Some(ctx) = dispatch.downcast_ref::<WithContext>() {
let mut f = |new_labels: &[Label]| {
if !new_labels.is_empty() {
let (name, mut labels) = key.clone().into_parts();
let filtered_labels = new_labels
.iter()
.filter(|label| self.label_filter.should_include_label(label))
.cloned();
labels.extend(filtered_labels);
Some(Key::from_parts(name, labels))
} else {
None
}
};
// Pull in the span's fields/labels if they exist.
return ctx.with_labels(dispatch, id, &mut f);
}
}
None
})
}
}
impl<R, F> Recorder for TracingContext<R, F>
where
R: Recorder,
F: LabelFilter,
{
fn register_counter(&self, key: &Key, unit: Option<Unit>, description: Option<&'static str>) {
self.inner.register_counter(key, unit, description)
}
fn register_gauge(&self, key: &Key, unit: Option<Unit>, description: Option<&'static str>) {
self.inner.register_gauge(key, unit, description)
}
fn register_histogram(&self, key: &Key, unit: Option<Unit>, description: Option<&'static str>) {
self.inner.register_histogram(key, unit, description)
}
fn increment_counter(&self, key: &Key, value: u64) {
let new_key = self.enhance_key(key);
let key = new_key.as_ref().unwrap_or(key);
self.inner.increment_counter(key, value);
}
fn update_gauge(&self, key: &Key, value: GaugeValue) {
let new_key = self.enhance_key(key);
let key = new_key.as_ref().unwrap_or(key);
self.inner.update_gauge(key, value);
}
fn record_histogram(&self, key: &Key, value: f64) {
let new_key = self.enhance_key(key);
let key = new_key.as_ref().unwrap_or(key);
self.inner.record_histogram(key, value);
}
}