opentelemetry_stdout/metrics/
exporter.rs

1use chrono::{DateTime, Utc};
2use core::{f64, fmt};
3use opentelemetry_sdk::metrics::Temporality;
4use opentelemetry_sdk::{
5    error::OTelSdkResult,
6    metrics::{
7        data::{
8            ExponentialHistogram, Gauge, GaugeDataPoint, Histogram, HistogramDataPoint,
9            ResourceMetrics, ScopeMetrics, Sum, SumDataPoint,
10        },
11        exporter::PushMetricExporter,
12    },
13};
14use std::fmt::Debug;
15use std::sync::atomic;
16
17/// An OpenTelemetry exporter that writes to stdout on export.
18pub struct MetricExporter {
19    is_shutdown: atomic::AtomicBool,
20    temporality: Temporality,
21}
22
23impl MetricExporter {
24    /// Create a builder to configure this exporter.
25    pub fn builder() -> MetricExporterBuilder {
26        MetricExporterBuilder::default()
27    }
28}
29impl Default for MetricExporter {
30    fn default() -> Self {
31        MetricExporterBuilder::default().build()
32    }
33}
34
35impl fmt::Debug for MetricExporter {
36    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
37        f.write_str("MetricExporter")
38    }
39}
40
41impl PushMetricExporter for MetricExporter {
42    /// Write Metrics to stdout
43    async fn export(&self, metrics: &mut ResourceMetrics) -> OTelSdkResult {
44        if self.is_shutdown.load(atomic::Ordering::SeqCst) {
45            Err(opentelemetry_sdk::error::OTelSdkError::AlreadyShutdown)
46        } else {
47            println!("Metrics");
48            println!("Resource");
49            if let Some(schema_url) = metrics.resource.schema_url() {
50                println!("\tResource SchemaUrl: {:?}", schema_url);
51            }
52
53            metrics.resource.iter().for_each(|(k, v)| {
54                println!("\t ->  {}={:?}", k, v);
55            });
56            print_metrics(&metrics.scope_metrics);
57            Ok(())
58        }
59    }
60
61    fn force_flush(&self) -> OTelSdkResult {
62        // exporter holds no state, nothing to flush
63        Ok(())
64    }
65
66    fn shutdown(&self) -> OTelSdkResult {
67        self.is_shutdown.store(true, atomic::Ordering::SeqCst);
68        Ok(())
69    }
70
71    fn temporality(&self) -> Temporality {
72        self.temporality
73    }
74}
75
76fn print_metrics(metrics: &[ScopeMetrics]) {
77    for (i, metric) in metrics.iter().enumerate() {
78        println!("\tInstrumentation Scope #{}", i);
79        println!("\t\tName         : {}", &metric.scope.name());
80        if let Some(version) = &metric.scope.version() {
81            println!("\t\tVersion  : {:?}", version);
82        }
83        if let Some(schema_url) = &metric.scope.schema_url() {
84            println!("\t\tSchemaUrl: {:?}", schema_url);
85        }
86        metric
87            .scope
88            .attributes()
89            .enumerate()
90            .for_each(|(index, kv)| {
91                if index == 0 {
92                    println!("\t\tScope Attributes:");
93                }
94                println!("\t\t\t ->  {}: {}", kv.key, kv.value);
95            });
96
97        metric.metrics.iter().enumerate().for_each(|(i, metric)| {
98            println!("Metric #{}", i);
99            println!("\t\tName         : {}", &metric.name);
100            println!("\t\tDescription  : {}", &metric.description);
101            println!("\t\tUnit         : {}", &metric.unit);
102
103            let data = metric.data.as_any();
104            if let Some(hist) = data.downcast_ref::<Histogram<u64>>() {
105                println!("\t\tType         : Histogram");
106                print_histogram(hist);
107            } else if let Some(hist) = data.downcast_ref::<Histogram<f64>>() {
108                println!("\t\tType         : Histogram");
109                print_histogram(hist);
110            } else if let Some(_hist) = data.downcast_ref::<ExponentialHistogram<u64>>() {
111                println!("\t\tType         : Exponential Histogram");
112                // TODO
113            } else if let Some(_hist) = data.downcast_ref::<ExponentialHistogram<f64>>() {
114                println!("\t\tType         : Exponential Histogram");
115                // TODO
116            } else if let Some(sum) = data.downcast_ref::<Sum<u64>>() {
117                println!("\t\tType         : Sum");
118                print_sum(sum);
119            } else if let Some(sum) = data.downcast_ref::<Sum<i64>>() {
120                println!("\t\tType         : Sum");
121                print_sum(sum);
122            } else if let Some(sum) = data.downcast_ref::<Sum<f64>>() {
123                println!("\t\tType         : Sum");
124                print_sum(sum);
125            } else if let Some(gauge) = data.downcast_ref::<Gauge<u64>>() {
126                println!("\t\tType         : Gauge");
127                print_gauge(gauge);
128            } else if let Some(gauge) = data.downcast_ref::<Gauge<i64>>() {
129                println!("\t\tType         : Gauge");
130                print_gauge(gauge);
131            } else if let Some(gauge) = data.downcast_ref::<Gauge<f64>>() {
132                println!("\t\tType         : Gauge");
133                print_gauge(gauge);
134            } else {
135                println!("Unsupported data type");
136            }
137        });
138    }
139}
140
141fn print_sum<T: Debug>(sum: &Sum<T>) {
142    println!("\t\tSum DataPoints");
143    println!("\t\tMonotonic    : {}", sum.is_monotonic);
144    if sum.temporality == Temporality::Cumulative {
145        println!("\t\tTemporality  : Cumulative");
146    } else {
147        println!("\t\tTemporality  : Delta");
148    }
149    let datetime: DateTime<Utc> = sum.start_time.into();
150    println!(
151        "\t\tStartTime    : {}",
152        datetime.format("%Y-%m-%d %H:%M:%S%.6f")
153    );
154    let datetime: DateTime<Utc> = sum.time.into();
155    println!(
156        "\t\tEndTime      : {}",
157        datetime.format("%Y-%m-%d %H:%M:%S%.6f")
158    );
159    print_sum_data_points(&sum.data_points);
160}
161
162fn print_gauge<T: Debug>(gauge: &Gauge<T>) {
163    println!("\t\tGauge DataPoints");
164    if let Some(start_time) = gauge.start_time {
165        let datetime: DateTime<Utc> = start_time.into();
166        println!(
167            "\t\tStartTime    : {}",
168            datetime.format("%Y-%m-%d %H:%M:%S%.6f")
169        );
170    }
171    let datetime: DateTime<Utc> = gauge.time.into();
172    println!(
173        "\t\tEndTime      : {}",
174        datetime.format("%Y-%m-%d %H:%M:%S%.6f")
175    );
176    print_gauge_data_points(&gauge.data_points);
177}
178
179fn print_histogram<T: Debug>(histogram: &Histogram<T>) {
180    if histogram.temporality == Temporality::Cumulative {
181        println!("\t\tTemporality  : Cumulative");
182    } else {
183        println!("\t\tTemporality  : Delta");
184    }
185    let datetime: DateTime<Utc> = histogram.start_time.into();
186    println!(
187        "\t\tStartTime    : {}",
188        datetime.format("%Y-%m-%d %H:%M:%S%.6f")
189    );
190    let datetime: DateTime<Utc> = histogram.time.into();
191    println!(
192        "\t\tEndTime      : {}",
193        datetime.format("%Y-%m-%d %H:%M:%S%.6f")
194    );
195    println!("\t\tHistogram DataPoints");
196    print_hist_data_points(&histogram.data_points);
197}
198
199fn print_sum_data_points<T: Debug>(data_points: &[SumDataPoint<T>]) {
200    for (i, data_point) in data_points.iter().enumerate() {
201        println!("\t\tDataPoint #{}", i);
202        println!("\t\t\tValue        : {:#?}", data_point.value);
203        println!("\t\t\tAttributes   :");
204        for kv in data_point.attributes.iter() {
205            println!("\t\t\t\t ->  {}: {}", kv.key, kv.value.as_str());
206        }
207    }
208}
209
210fn print_gauge_data_points<T: Debug>(data_points: &[GaugeDataPoint<T>]) {
211    for (i, data_point) in data_points.iter().enumerate() {
212        println!("\t\tDataPoint #{}", i);
213        println!("\t\t\tValue        : {:#?}", data_point.value);
214        println!("\t\t\tAttributes   :");
215        for kv in data_point.attributes.iter() {
216            println!("\t\t\t\t ->  {}: {}", kv.key, kv.value.as_str());
217        }
218    }
219}
220
221fn print_hist_data_points<T: Debug>(data_points: &[HistogramDataPoint<T>]) {
222    for (i, data_point) in data_points.iter().enumerate() {
223        println!("\t\tDataPoint #{}", i);
224        println!("\t\t\tCount        : {}", data_point.count);
225        println!("\t\t\tSum          : {:?}", data_point.sum);
226        if let Some(min) = &data_point.min {
227            println!("\t\t\tMin          : {:?}", min);
228        }
229
230        if let Some(max) = &data_point.max {
231            println!("\t\t\tMax          : {:?}", max);
232        }
233
234        println!("\t\t\tAttributes   :");
235        for kv in data_point.attributes.iter() {
236            println!("\t\t\t\t ->  {}: {}", kv.key, kv.value.as_str());
237        }
238
239        println!("\t\t\tBuckets");
240        let mut lower_bound = f64::NEG_INFINITY;
241        for (i, &upper_bound) in data_point.bounds.iter().enumerate() {
242            let count = data_point.bucket_counts.get(i).unwrap_or(&0);
243            println!("\t\t\t\t {} to {} : {}", lower_bound, upper_bound, count);
244            lower_bound = upper_bound;
245        }
246
247        let last_count = data_point
248            .bucket_counts
249            .get(data_point.bounds.len())
250            .unwrap_or(&0);
251        println!("\t\t\t\t{} to +Infinity : {}", lower_bound, last_count);
252    }
253}
254
255/// Configuration for the stdout metrics exporter
256#[derive(Default)]
257pub struct MetricExporterBuilder {
258    temporality: Option<Temporality>,
259}
260
261impl MetricExporterBuilder {
262    /// Set the [Temporality] of the exporter.
263    pub fn with_temporality(mut self, temporality: Temporality) -> Self {
264        self.temporality = Some(temporality);
265        self
266    }
267
268    /// Create a metrics exporter with the current configuration
269    pub fn build(self) -> MetricExporter {
270        MetricExporter {
271            temporality: self.temporality.unwrap_or_default(),
272            is_shutdown: atomic::AtomicBool::new(false),
273        }
274    }
275}
276
277impl fmt::Debug for MetricExporterBuilder {
278    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
279        f.write_str("MetricExporterBuilder")
280    }
281}