rustic_rs/metrics/
opentelemetry.rs

1use std::{collections::BTreeMap, time::Duration};
2
3use opentelemetry_otlp::{MetricExporter, Protocol, WithExportConfig};
4use opentelemetry_sdk::{
5    Resource,
6    metrics::{PeriodicReader, SdkMeterProvider},
7};
8
9use anyhow::Result;
10use opentelemetry::{KeyValue, metrics::MeterProvider};
11use reqwest::Url;
12
13use super::{Metric, MetricValue, MetricsExporter};
14
15pub struct OpentelemetryExporter {
16    pub endpoint: Url,
17    pub service_name: String,
18    pub labels: BTreeMap<String, String>,
19}
20
21impl MetricsExporter for OpentelemetryExporter {
22    fn push_metrics(&self, metrics: &[Metric]) -> Result<()> {
23        let exporter = MetricExporter::builder()
24            .with_http()
25            .with_protocol(Protocol::HttpBinary)
26            .with_endpoint(self.endpoint.to_string())
27            .build()?;
28
29        // ManualReader is not stable yet, so we use PeriodicReader
30        let reader = PeriodicReader::builder(exporter)
31            .with_interval(Duration::from_secs(u64::MAX))
32            .build();
33
34        let attributes = self
35            .labels
36            .iter()
37            .map(|(k, v)| KeyValue::new(k.clone(), v.clone()));
38
39        let resource = Resource::builder()
40            .with_service_name(self.service_name.clone())
41            .with_attributes(attributes)
42            .build();
43
44        let meter_provider = SdkMeterProvider::builder()
45            .with_reader(reader)
46            .with_resource(resource)
47            .build();
48
49        let meter = meter_provider.meter("rustic");
50
51        for metric in metrics {
52            match metric.value {
53                MetricValue::Int(value) => {
54                    let gauge = &meter
55                        .u64_gauge(metric.name)
56                        .with_description(metric.description)
57                        .build();
58
59                    gauge.record(value, &[]);
60                }
61                MetricValue::Float(value) => {
62                    let gauge = &meter
63                        .f64_gauge(metric.name)
64                        .with_description(metric.description)
65                        .build();
66
67                    gauge.record(value, &[]);
68                }
69            };
70        }
71
72        meter_provider.shutdown()?;
73        Ok(())
74    }
75}