Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Extract exemplars from OpenTelemetry context
  • Loading branch information
emschwartz committed May 26, 2023
commit af732d15b402ac049fb46873b98a5a0bfb1639ab
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ jobs:
- run: cargo test --features=prometheus-exporter,metrics
- run: cargo test --features=prometheus-exporter,prometheus
- run: cargo test --features=prometheus-exporter,prometheus-client,exemplars-tracing
- run: cargo test --features=prometheus-exporter,prometheus-client,exemplars-opentelemetry
# The tests using opentelemetry are currently failing because of this issue:
# https://github.com/open-telemetry/opentelemetry-rust/issues/1070
# We should remove the continue-on-error once that is fixed
Expand Down
9 changes: 6 additions & 3 deletions autometrics/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ readme = "README.md"

[features]
metrics = ["dep:metrics"]
opentelemetry = ["opentelemetry_api"]
opentelemetry = ["opentelemetry_api/metrics"]
prometheus = ["dep:prometheus"]
prometheus-client = ["dep:prometheus-client"]
prometheus-exporter = [
Expand All @@ -24,17 +24,19 @@ prometheus-exporter = [
"dep:prometheus",
"thiserror"
]
exemplars-tracing = ["tracing", "tracing-subscriber"]
exemplars-tracing = ["tracing", "tracing-subscriber", "_exemplars"]
exemplars-opentelemetry = ["opentelemetry_api/trace", "_exemplars"]
custom-objective-percentile = []
custom-objective-latency = []
_exemplars = []

[dependencies]
autometrics-macros = { workspace = true }
spez = "0.1.2"
once_cell = "1.17"

# Used for opentelemetry feature
opentelemetry_api = { version = "0.19.0", default-features = false, features = ["metrics"], optional = true }
opentelemetry_api = { version = "0.19.0", default-features = false, optional = true }

# Use for metrics feature
metrics = { version = "0.21", default-features = false, optional = true }
Expand All @@ -56,6 +58,7 @@ tracing-subscriber = { version = "0.3", default-features = false, features = ["r
[dev-dependencies]
axum = { version = "0.6", features = ["tokio"] }
http = "0.2"
opentelemetry_sdk = "0.19"
tokio = { version = "1", features = ["full"] }
tracing = "0.1"
tracing-subscriber = "0.3"
Expand Down
25 changes: 25 additions & 0 deletions autometrics/src/exemplars/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,30 @@
//! }
//! ```

use std::collections::HashMap;

#[cfg(feature = "exemplars-opentelemetry")]
mod opentelemetry;
#[cfg(feature = "exemplars-tracing")]
pub mod tracing;

#[cfg(all(feature = "exemplars-opentelemetry", feature = "exemplars-tracing"))]
compile_error!("Only one of the exemplars-opentelemetry and exemplars-tracing features can be enabled at a time");

#[cfg(not(any(feature = "exemplars-opentelemetry", feature = "exemplars-tracing")))]
compile_error!("One of the exemplars-opentelemetry or exemplars-tracing features must be enabled");

#[cfg(not(feature = "prometheus-client"))]
compile_error!("Exemplars can only be used with the `prometheus-client` metrics library because that is the only one that currently supports producing metrics with exemplars");

pub(crate) type TraceLabels = HashMap<&'static str, String>;
pub(crate) fn get_exemplar() -> Option<TraceLabels> {
#[cfg(feature = "exemplars-opentelemetry")]
{
opentelemetry::get_exemplar()
}
#[cfg(feature = "exemplars-tracing")]
{
tracing::get_exemplar()
}
}
18 changes: 18 additions & 0 deletions autometrics/src/exemplars/opentelemetry.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
use super::TraceLabels;
use opentelemetry_api::{trace::TraceContextExt, Context};
use std::iter::FromIterator;

pub fn get_exemplar() -> Option<TraceLabels> {
let context = Context::current();
let span = context.span();
let span_context = span.span_context();

if span_context.is_valid() {
Some(TraceLabels::from_iter([
("trace_id", span_context.trace_id().to_string()),
("span_id", span_context.span_id().to_string()),
]))
} else {
None
}
}
6 changes: 2 additions & 4 deletions autometrics/src/exemplars/tracing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,12 @@
//!
//! [`Span`]: tracing::Span

use std::collections::HashMap;
use super::TraceLabels;
use tracing::field::{Field, Visit};
use tracing::{span::Attributes, Id, Subscriber};
use tracing_subscriber::layer::{Context, Layer};
use tracing_subscriber::registry::{LookupSpan, Registry};

pub(crate) type TraceLabels = HashMap<&'static str, String>;

/// Get the exemplar from the current tracing span
pub(crate) fn get_exemplar() -> Option<TraceLabels> {
let span = tracing::span::Span::current();
Expand Down Expand Up @@ -103,7 +101,7 @@ impl TraceLabelVisitor {
fn new(fields: &'static [&'static str]) -> Self {
Self {
fields,
labels: HashMap::with_capacity(fields.len()),
labels: TraceLabels::with_capacity(fields.len()),
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion autometrics/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
#![doc = include_str!("../README.md")]

mod constants;
#[cfg(feature = "exemplars-tracing")]
#[cfg(feature = "_exemplars")]
pub mod exemplars;
pub mod integrations;
mod labels;
Expand Down
3 changes: 0 additions & 3 deletions autometrics/src/tracker/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,6 @@ pub use self::prometheus_client::PrometheusClientTracker;
))]
compile_error!("Only one of the metrics, opentelemetry, prometheus, or prometheus-client features can be enabled at a time");

#[cfg(all(feature = "exemplars-tracing", not(feature = "prometheus-client")))]
compile_error!("The exemplars-tracing feature can only be used with the `prometheus-client` metrics library because that is the only one that currently supports exemplars");

pub trait TrackMetrics {
fn set_build_info(build_info_labels: &BuildInfoLabels);
fn start(gauge_labels: Option<&GaugeLabels>) -> Self;
Expand Down
32 changes: 15 additions & 17 deletions autometrics/src/tracker/prometheus_client.rs
Original file line number Diff line number Diff line change
@@ -1,26 +1,24 @@
use super::TrackMetrics;
#[cfg(feature = "exemplars-tracing")]
use crate::exemplars::tracing::get_exemplar;
#[cfg(feature = "_exemplars")]
use crate::exemplars::get_exemplar;
use crate::labels::{BuildInfoLabels, CounterLabels, GaugeLabels, HistogramLabels};
use crate::{constants::*, HISTOGRAM_BUCKETS};
use once_cell::sync::Lazy;
#[cfg(feature = "exemplars-tracing")]
use prometheus_client::metrics::exemplar::{CounterWithExemplar, HistogramWithExemplars};
#[cfg(not(feature = "exemplars-tracing"))]
use prometheus_client::metrics::{counter::Counter, histogram::Histogram};
use prometheus_client::metrics::{family::Family, gauge::Gauge};
use prometheus_client::registry::Registry;
use std::time::Instant;

#[cfg(feature = "exemplars-tracing")]
type CounterType = CounterWithExemplar<Vec<(&'static str, String)>>;
#[cfg(not(feature = "exemplars-tracing"))]
type CounterType = Counter;
#[cfg(feature = "_exemplars")]
type CounterType =
prometheus_client::metrics::exemplar::CounterWithExemplar<Vec<(&'static str, String)>>;
#[cfg(not(feature = "_exemplars"))]
type CounterType = prometheus_client::metrics::counter::Counter;

#[cfg(feature = "exemplars-tracing")]
type HistogramType = HistogramWithExemplars<Vec<(&'static str, String)>>;
#[cfg(not(feature = "exemplars-tracing"))]
type HistogramType = Histogram;
#[cfg(feature = "_exemplars")]
type HistogramType =
prometheus_client::metrics::exemplar::HistogramWithExemplars<Vec<(&'static str, String)>>;
#[cfg(not(feature = "_exemplars"))]
type HistogramType = prometheus_client::metrics::histogram::Histogram;

static REGISTRY_AND_METRICS: Lazy<(Registry, Metrics)> = Lazy::new(|| {
let mut registry = <Registry>::default();
Expand Down Expand Up @@ -89,18 +87,18 @@ impl TrackMetrics for PrometheusClientTracker {
}

fn finish(self, counter_labels: &CounterLabels, histogram_labels: &HistogramLabels) {
#[cfg(feature = "exemplars-tracing")]
#[cfg(feature = "_exemplars")]
let exemplar = get_exemplar().map(|exemplar| exemplar.into_iter().collect::<Vec<_>>());

METRICS.counter.get_or_create(&counter_labels).inc_by(
1,
#[cfg(feature = "exemplars-tracing")]
#[cfg(feature = "_exemplars")]
exemplar.clone(),
);

METRICS.histogram.get_or_create(&histogram_labels).observe(
self.start_time.elapsed().as_secs_f64(),
#[cfg(feature = "exemplars-tracing")]
#[cfg(feature = "_exemplars")]
exemplar,
);

Expand Down
38 changes: 27 additions & 11 deletions autometrics/tests/exemplars_test.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
#![cfg(all(feature = "exemplars-tracing", feature = "prometheus-exporter"))]
#![cfg(feature = "prometheus-exporter")]

use autometrics::exemplars::tracing::AutometricsExemplarExtractor;
use autometrics::{autometrics, encode_global_metrics, global_metrics_exporter};
use tracing::instrument;
use tracing_subscriber::prelude::*;

#[cfg(feature = "exemplars-tracing")]
#[test]
fn single_field() {
let _ = global_metrics_exporter();
Expand All @@ -13,9 +13,9 @@ fn single_field() {
#[instrument(fields(trace_id = "test_trace_id"))]
fn single_field_fn() {}

let subscriber = tracing_subscriber::fmt::fmt()
.finish()
.with(AutometricsExemplarExtractor::from_fields(&["trace_id"]));
let subscriber = tracing_subscriber::fmt::fmt().finish().with(
autometrics::exemplars::tracing::AutometricsExemplarExtractor::from_fields(&["trace_id"]),
);
tracing::subscriber::with_default(subscriber, || single_field_fn());

let metrics = encode_global_metrics().unwrap();
Expand All @@ -26,6 +26,7 @@ fn single_field() {
}))
}

#[cfg(feature = "exemplars-tracing")]
#[test]
fn multiple_fields() {
let _ = global_metrics_exporter();
Expand All @@ -34,12 +35,11 @@ fn multiple_fields() {
#[instrument(fields(trace_id = "test_trace_id", foo = 99))]
fn multiple_fields_fn() {}

let subscriber =
tracing_subscriber::fmt::fmt()
.finish()
.with(AutometricsExemplarExtractor::from_fields(&[
"trace_id", "foo",
]));
let subscriber = tracing_subscriber::fmt::fmt().finish().with(
autometrics::exemplars::tracing::AutometricsExemplarExtractor::from_fields(&[
"trace_id", "foo",
]),
);
tracing::subscriber::with_default(subscriber, || multiple_fields_fn());

let metrics = encode_global_metrics().unwrap();
Expand All @@ -51,3 +51,19 @@ fn multiple_fields() {
|| line.ends_with(r#"} 1 # {foo="99",trace_id="test_trace_id"} 1.0"#))
}))
}

#[cfg(feature = "exemplars-opentelemetry")]
#[test]
fn opentelemetry_context() {
use opentelemetry_api::trace::Tracer;
let _ = global_metrics_exporter();

#[autometrics]
fn opentelemetry_context_fn() {}

let tracer = opentelemetry_sdk::export::trace::stdout::new_pipeline().install_simple();
tracer.in_span("my_span", |_cx| opentelemetry_context_fn());

let metrics = encode_global_metrics().unwrap();
println!("{}", metrics);
}