otlp_metrics_exporter/
lib.rs

1use std::sync::Arc;
2
3use metrics::set_global_recorder;
4use otlp_recorder::OtlpRecorder;
5
6mod json;
7mod metric;
8pub mod otlp_recorder;
9mod time;
10pub mod transport;
11
12/// Install recorder globally
13///
14/// # Example
15///
16/// ```rust
17/// use otlp_metrics_exporter::install_recorder;
18/// use metrics::{counter, gauge, histogram};
19///
20/// let recorder = install_recorder(env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION"), "instance1");
21/// counter!("test_counter", "label1" => "label_value1").increment(1);
22/// gauge!("test_gauge", "label2" => "label_value2").set(10);
23/// histogram!("test_histogram", "label3" => "label_value3").record(10);
24/// recorder.to_json(None);
25/// ```
26pub fn install_recorder(
27    name: impl ToString,
28    version: impl ToString,
29    instance_id: impl ToString,
30) -> Arc<OtlpRecorder> {
31    let recorder = Arc::new(OtlpRecorder::new(name, version, instance_id));
32    set_global_recorder(recorder.clone()).expect("Recorder installed");
33    recorder
34}
35
36#[cfg(test)]
37mod tests {
38    use core::time::Duration;
39
40    use metrics::{
41        counter, describe_counter, describe_gauge, describe_histogram, gauge, histogram,
42        set_default_local_recorder, Unit,
43    };
44
45    use crate::time::set_time;
46
47    use super::*;
48
49    #[test]
50    fn test_recorder_to_json() {
51        set_time(1739394449205);
52        let recorder = OtlpRecorder::new("otlp-metrics", "1", "test_recorder_to_json");
53        let _guard = set_default_local_recorder(&recorder);
54        for i in 1..3 {
55            counter!("test_counter", "label1" => "label_value1").increment(1);
56            gauge!("test_gauge", "label2" => "label_value2").set(i * 10);
57            histogram!("test_histogram", "label3" => "label_value3").record(i * 10);
58            histogram!("test_histogram_with_buckets", "buckets" => "10,30").record(i * 10);
59        }
60
61        assert_eq!(
62            recorder.to_json(None),
63            r#"{"resourceMetrics":[{"resource":{"attributes":[{"key":"service.name","value":{"stringValue":"otlp-metrics"}},{"key":"service.version","value":{"stringValue":"1"}},{"key":"service.instance.id","value":{"stringValue":"test_recorder_to_json"}}]},"scopeMetrics":[{"metrics":[{"name":"test_counter","unit":"1","description":"","sum":{"aggregationTemporality":2,"isMonotonic":true,"dataPoints":[{"asInt":2,"startTimeUnixNano":1739394449305000000,"timeUnixNano":1739394450105000000,"attributes":[{"key":"label1","value":{"stringValue":"label_value1"}}]}]}},{"name":"test_gauge","unit":"1","description":"","gauge":{"dataPoints":[{"asDouble":20,"startTimeUnixNano":1739394449505000000,"timeUnixNano":1739394450205000000,"attributes":[{"key":"label2","value":{"stringValue":"label_value2"}}]}]}},{"name":"test_histogram","unit":"1","description":"","histogram":{"aggregationTemporality":2,"dataPoints":[{"startTimeUnixNano":1739394449705000000,"timeUnixNano":1739394450305000000,"count":2,"sum":30,"attributes":[{"key":"label3","value":{"stringValue":"label_value3"}}],"bucketCounts":[],"explicitBounds":[]}]}},{"name":"test_histogram_with_buckets","unit":"1","description":"","histogram":{"aggregationTemporality":2,"dataPoints":[{"startTimeUnixNano":1739394449905000000,"timeUnixNano":1739394450405000000,"count":2,"sum":30,"attributes":[{"key":"buckets","value":{"stringValue":"10,30"}}],"bucketCounts":[1,1,0],"explicitBounds":[10,30]}]}}]}]}]}"#,
64        );
65    }
66
67    #[test]
68    fn test_recorder_with_descriptions_and_units() {
69        set_time(1739394449205);
70        let recorder = OtlpRecorder::new(
71            "otlp-metrics",
72            "1",
73            "test_recorder_with_descriptions_and_units",
74        );
75        let _guard = set_default_local_recorder(&recorder);
76
77        describe_counter!("bytes_total", Unit::Bytes, "Counter for bytes");
78        describe_gauge!("limit_reached", Unit::Percent, "Gauge percent");
79        describe_histogram!(
80            "request_time",
81            Unit::Milliseconds,
82            "Request time in milliseconds"
83        );
84
85        counter!("bytes_total").increment(1);
86        gauge!("limit_reached").set(10);
87        histogram!("request_time").record(10);
88
89        assert_eq!(
90            recorder.to_json(None),
91            r#"{"resourceMetrics":[{"resource":{"attributes":[{"key":"service.name","value":{"stringValue":"otlp-metrics"}},{"key":"service.version","value":{"stringValue":"1"}},{"key":"service.instance.id","value":{"stringValue":"test_recorder_with_descriptions_and_units"}}]},"scopeMetrics":[{"metrics":[{"name":"bytes_total","unit":"B","description":"Counter for bytes","sum":{"aggregationTemporality":2,"isMonotonic":true,"dataPoints":[{"asInt":1,"startTimeUnixNano":1739394449305000000,"timeUnixNano":1739394449405000000,"attributes":[]}]}},{"name":"limit_reached","unit":"%","description":"Gauge percent","gauge":{"dataPoints":[{"asDouble":10,"startTimeUnixNano":1739394449505000000,"timeUnixNano":1739394449605000000,"attributes":[]}]}},{"name":"request_time","unit":"ms","description":"Request time in milliseconds","histogram":{"aggregationTemporality":2,"dataPoints":[{"startTimeUnixNano":1739394449705000000,"timeUnixNano":1739394449805000000,"count":1,"sum":10,"attributes":[],"bucketCounts":[],"explicitBounds":[]}]}}]}]}]}"#,
92        );
93    }
94
95    #[test]
96    fn test_metric_times() {
97        set_time(1739394449205);
98        let recorder = OtlpRecorder::new("otlp-metrics", "1", "test_metric_times");
99        let _guard = set_default_local_recorder(&recorder);
100
101        counter!("test_counter", "label1" => "label_value1").increment(1);
102
103        assert_eq!(
104            recorder.to_json(None),
105            r#"{"resourceMetrics":[{"resource":{"attributes":[{"key":"service.name","value":{"stringValue":"otlp-metrics"}},{"key":"service.version","value":{"stringValue":"1"}},{"key":"service.instance.id","value":{"stringValue":"test_metric_times"}}]},"scopeMetrics":[{"metrics":[{"name":"test_counter","unit":"1","description":"","sum":{"aggregationTemporality":2,"isMonotonic":true,"dataPoints":[{"asInt":1,"startTimeUnixNano":1739394449305000000,"timeUnixNano":1739394449405000000,"attributes":[{"key":"label1","value":{"stringValue":"label_value1"}}]}]}}]}]}]}"#
106        );
107
108        counter!("test_counter", "label1" => "label_value1").increment(1);
109
110        assert_eq!(
111            recorder.to_json(None),
112            r#"{"resourceMetrics":[{"resource":{"attributes":[{"key":"service.name","value":{"stringValue":"otlp-metrics"}},{"key":"service.version","value":{"stringValue":"1"}},{"key":"service.instance.id","value":{"stringValue":"test_metric_times"}}]},"scopeMetrics":[{"metrics":[{"name":"test_counter","unit":"1","description":"","sum":{"aggregationTemporality":2,"isMonotonic":true,"dataPoints":[{"asInt":2,"startTimeUnixNano":1739394449305000000,"timeUnixNano":1739394449505000000,"attributes":[{"key":"label1","value":{"stringValue":"label_value1"}}]}]}}]}]}]}"#
113        );
114    }
115
116    #[test]
117    fn test_output_only_changed_values() {
118        set_time(1739394449205);
119        let recorder = OtlpRecorder::new("otlp-metrics", "1", "test_output_only_changed_values");
120        let _guard = set_default_local_recorder(&recorder);
121
122        counter!("test_counter", "label1" => "label_value1").increment(1);
123
124        assert_eq!(
125            recorder.to_json(None),
126            r#"{"resourceMetrics":[{"resource":{"attributes":[{"key":"service.name","value":{"stringValue":"otlp-metrics"}},{"key":"service.version","value":{"stringValue":"1"}},{"key":"service.instance.id","value":{"stringValue":"test_output_only_changed_values"}}]},"scopeMetrics":[{"metrics":[{"name":"test_counter","unit":"1","description":"","sum":{"aggregationTemporality":2,"isMonotonic":true,"dataPoints":[{"asInt":1,"startTimeUnixNano":1739394449305000000,"timeUnixNano":1739394449405000000,"attributes":[{"key":"label1","value":{"stringValue":"label_value1"}}]}]}}]}]}]}"#
127        );
128
129        assert_eq!(
130            recorder.to_json(Duration::from_millis(101).into()),
131            r#"{"resourceMetrics":[{"resource":{"attributes":[{"key":"service.name","value":{"stringValue":"otlp-metrics"}},{"key":"service.version","value":{"stringValue":"1"}},{"key":"service.instance.id","value":{"stringValue":"test_output_only_changed_values"}}]},"scopeMetrics":[{"metrics":[{"name":"test_counter","unit":"1","description":"","sum":{"aggregationTemporality":2,"isMonotonic":true,"dataPoints":[{"asInt":1,"startTimeUnixNano":1739394449305000000,"timeUnixNano":1739394449405000000,"attributes":[{"key":"label1","value":{"stringValue":"label_value1"}}]}]}}]}]}]}"#
132        );
133
134        assert_eq!(
135            recorder.to_json(Duration::from_millis(99).into()),
136            r#"{"resourceMetrics":[{"resource":{"attributes":[{"key":"service.name","value":{"stringValue":"otlp-metrics"}},{"key":"service.version","value":{"stringValue":"1"}},{"key":"service.instance.id","value":{"stringValue":"test_output_only_changed_values"}}]},"scopeMetrics":[{"metrics":[]}]}]}"#
137        );
138
139        counter!("test_counter", "label1" => "label_value1").increment(1);
140
141        assert_eq!(
142            recorder.to_json(Duration::from_secs(99).into()),
143            r#"{"resourceMetrics":[{"resource":{"attributes":[{"key":"service.name","value":{"stringValue":"otlp-metrics"}},{"key":"service.version","value":{"stringValue":"1"}},{"key":"service.instance.id","value":{"stringValue":"test_output_only_changed_values"}}]},"scopeMetrics":[{"metrics":[{"name":"test_counter","unit":"1","description":"","sum":{"aggregationTemporality":2,"isMonotonic":true,"dataPoints":[{"asInt":2,"startTimeUnixNano":1739394449305000000,"timeUnixNano":1739394449705000000,"attributes":[{"key":"label1","value":{"stringValue":"label_value1"}}]}]}}]}]}]}"#
144        );
145    }
146}