use opentelemetry::KeyValue;
use opentelemetry::metrics::Counter;
#[must_use]
pub struct AddValueGuard<T> {
counter: Counter<T>,
value: Option<T>,
attributes: Vec<KeyValue>,
}
impl<T> AddValueGuard<T> {
pub fn new(counter: Counter<T>, value: T, attributes: impl Into<Vec<KeyValue>>) -> Self {
Self {
counter,
value: Some(value),
attributes: attributes.into(),
}
}
pub fn set(&mut self, kv: KeyValue) {
if let Some(existing) = self.attributes.iter_mut().find(|a| a.key == kv.key) {
*existing = kv;
} else {
self.attributes.push(kv);
}
}
pub fn value(&mut self, value: T) {
self.value = Some(value);
}
pub fn cancel(&mut self) {
self.value = None;
}
}
impl<T> Drop for AddValueGuard<T> {
fn drop(&mut self) {
if let Some(value) = self.value.take() {
self.counter.add(value, &self.attributes);
}
}
}
#[cfg(test)]
mod tests {
use apollo_opentelemetry_test::{TelemetryContext, assert_metrics_snapshot};
use opentelemetry::global;
use super::AddValueGuard;
#[test]
fn adds_value_on_drop() {
let ctx = TelemetryContext::new();
let counter = global::meter_provider()
.meter("test")
.u64_counter("test.completed")
.build();
let guard = AddValueGuard::new(counter, 5, []);
assert_metrics_snapshot!(ctx, @"[]");
drop(guard);
assert_metrics_snapshot!(ctx, @r"
- name: test.completed
data:
type: Sum
data_points:
- value: 5
is_monotonic: true
temporality: Cumulative
");
}
#[test]
fn value_overwrites_before_drop() {
let ctx = TelemetryContext::new();
let counter = global::meter_provider()
.meter("test")
.u64_counter("test.bytes")
.build();
let mut guard = AddValueGuard::new(counter, 100, []);
guard.value(42);
drop(guard);
assert_metrics_snapshot!(ctx, @r"
- name: test.bytes
data:
type: Sum
data_points:
- value: 42
is_monotonic: true
temporality: Cumulative
");
}
#[test]
fn set_deduplicates_attribute_by_key() {
let ctx = TelemetryContext::new();
let counter = global::meter_provider()
.meter("test")
.u64_counter("test.requests")
.build();
let mut guard = AddValueGuard::new(counter, 1, []);
guard.set(opentelemetry::KeyValue::new("route", "/a"));
guard.set(opentelemetry::KeyValue::new("route", "/b"));
drop(guard);
assert_metrics_snapshot!(ctx, @r"
- name: test.requests
data:
type: Sum
data_points:
- attributes:
route: /b
value: 1
is_monotonic: true
temporality: Cumulative
");
}
#[test]
fn cancel_suppresses_add() {
let ctx = TelemetryContext::new();
let counter = global::meter_provider()
.meter("test")
.u64_counter("test.completed")
.build();
let mut guard = AddValueGuard::new(counter, 5, []);
guard.cancel();
drop(guard);
assert_metrics_snapshot!(ctx, @"[]");
}
}