1use chrono::{DateTime, Utc};
2use opentelemetry_sdk::error::{OTelSdkError, OTelSdkResult};
3use opentelemetry_sdk::metrics::Temporality;
4use opentelemetry_sdk::metrics::data::{
5 AggregatedMetrics, Gauge, Histogram, Metric, MetricData, ResourceMetrics, Sum,
6};
7use opentelemetry_sdk::metrics::exporter::PushMetricExporter;
8use serde_json::{Map, Value};
9use std::fs::OpenOptions;
10use std::io::Write;
11use std::path::PathBuf;
12use std::result::Result;
13use std::sync::atomic::{AtomicBool, Ordering};
14use std::time::Duration;
15
16pub struct FileMetricExporter {
18 is_shutdown: AtomicBool,
19 path: PathBuf,
20}
21
22impl FileMetricExporter {
23 pub fn new(path: PathBuf) -> Result<Self, OTelSdkError> {
43 Ok(Self {
45 is_shutdown: AtomicBool::new(false),
46 path,
47 })
48 }
49}
50
51impl PushMetricExporter for FileMetricExporter {
52 fn temporality(&self) -> Temporality {
53 Temporality::Cumulative
54 }
55
56 async fn export(&self, metrics: &ResourceMetrics) -> OTelSdkResult {
57 if self.is_shutdown.load(Ordering::SeqCst) {
58 return Err(OTelSdkError::AlreadyShutdown);
59 }
60
61 let mut file = OpenOptions::new()
63 .create(true)
64 .append(true)
65 .open(&self.path)
66 .map_err(|err| {
67 OTelSdkError::InternalFailure(format!("Unable to open exporter file: {err}"))
68 })?;
69
70 let metrics_json = to_json(metrics).map_err(|e| {
71 OTelSdkError::InternalFailure(format!("Failed to serialize metrics to JSON: {e}"))
72 })?;
73 let json_string = serde_json::to_string(&metrics_json).map_err(|e| {
74 OTelSdkError::InternalFailure(format!("Failed to serialize metrics to JSON: {e}"))
75 })?;
76
77 writeln!(file, "{}", json_string)
78 .map_err(|e| OTelSdkError::InternalFailure(format!("File write error: {e}")))?;
79
80 Ok(())
81 }
82
83 fn force_flush(&self) -> OTelSdkResult {
85 Ok(())
86 }
87
88 fn shutdown_with_timeout(&self, _timeout: Duration) -> OTelSdkResult {
89 self.is_shutdown.store(true, Ordering::SeqCst);
90 Ok(())
91 }
92}
93
94fn to_json(metrics: &ResourceMetrics) -> Result<Value, OTelSdkError> {
95 let mut root = Map::new();
96
97 let mut resource_attrs = Map::new();
99 for (key, value) in metrics.resource().iter() {
100 resource_attrs.insert(key.to_string(), Value::String(value.to_string()));
101 }
102 root.insert("resource".to_owned(), Value::Object(resource_attrs));
103
104 let mut scope_metrics = Vec::new();
106 for scope_metric in metrics.scope_metrics() {
107 let mut scope = Map::new();
108 let mut scope_info = Map::new();
109 scope_info.insert(
110 "name".to_owned(),
111 Value::String(scope_metric.scope().name().to_string()),
112 );
113 if let Some(version) = scope_metric.scope().version() {
114 scope_info.insert("version".to_owned(), Value::String(version.to_string()));
115 }
116 if let Some(schema_url) = scope_metric.scope().schema_url() {
117 scope_info.insert(
118 "schema_url".to_owned(),
119 Value::String(schema_url.to_string()),
120 );
121 }
122 scope.insert("scope".to_owned(), Value::Object(scope_info));
123
124 let mut metrics_array = Vec::new();
126 for metric in scope_metric.metrics() {
127 let mut metric_obj = Map::new();
128 metric_obj.insert("name".to_owned(), Value::String(metric.name().to_string()));
129 metric_obj.insert(
130 "description".to_owned(),
131 Value::String(metric.description().to_string()),
132 );
133 metric_obj.insert("unit".to_owned(), Value::String(metric.unit().to_string()));
134
135 let data_points = serialize_metric_data(metric)?;
137 metric_obj.insert("data_points".to_owned(), Value::Array(data_points));
138 metrics_array.push(Value::Object(metric_obj));
139 }
140 scope.insert("metrics".to_owned(), Value::Array(metrics_array));
141 scope_metrics.push(Value::Object(scope));
142 }
143 root.insert("scope_metrics".to_owned(), Value::Array(scope_metrics));
144
145 Ok(Value::Object(root))
146}
147
148fn serialize_metric_data(metric: &Metric) -> Result<Vec<Value>, OTelSdkError> {
156 match metric.data() {
157 AggregatedMetrics::U64(data) => {
158 serialize_metric_data_typed(metric, data, |v: u64| Value::Number(v.into()))
159 }
160 AggregatedMetrics::I64(data) => {
161 serialize_metric_data_typed(metric, data, |v: i64| Value::Number(v.into()))
162 }
163 AggregatedMetrics::F64(data) => {
164 serialize_metric_data_typed(metric, data, |v: f64| Value::String(v.to_string()))
165 }
166 }
167}
168
169fn serialize_metric_data_typed<T>(
171 metric: &Metric,
172 data: &MetricData<T>,
173 value_serializer: impl Fn(T) -> Value,
174) -> Result<Vec<Value>, OTelSdkError>
175where
176 T: Copy,
177{
178 match data {
179 MetricData::Sum(sum) => serialize_sum_data_points(sum, value_serializer),
180 MetricData::Gauge(gauge) => serialize_gauge_data_points(gauge, value_serializer),
181 MetricData::Histogram(histogram) => {
182 serialize_histogram_data_points(histogram, value_serializer)
183 }
184 MetricData::ExponentialHistogram(_) => Err(OTelSdkError::InternalFailure(format!(
185 "Unsupported metric type (ExponentialHistogram) for metric '{}'",
186 metric.name()
187 ))),
188 }
189}
190
191fn serialize_sum_data_points<T>(
193 sum: &Sum<T>,
194 value_serializer: impl Fn(T) -> Value,
195) -> Result<Vec<Value>, OTelSdkError>
196where
197 T: Copy,
198{
199 let start_time = sum.start_time();
200 let time = sum.time();
201 let mut data_points = Vec::new();
202 for point in sum.data_points() {
203 let mut dp = Map::new();
204 dp.insert("value".to_owned(), value_serializer(point.value()));
205
206 let start_time: DateTime<Utc> = start_time.into();
207 dp.insert(
208 "start_time".to_owned(),
209 Value::String(start_time.timestamp_micros().to_string()),
210 );
211
212 let time: DateTime<Utc> = time.into();
213 dp.insert(
214 "time".to_owned(),
215 Value::String(time.timestamp_micros().to_string()),
216 );
217
218 dp.insert(
219 "attributes".to_owned(),
220 attributes_to_json(point.attributes()),
221 );
222 data_points.push(Value::Object(dp));
223 }
224 Ok(data_points)
225}
226
227fn serialize_gauge_data_points<T>(
229 gauge: &Gauge<T>,
230 value_serializer: impl Fn(T) -> Value,
231) -> Result<Vec<Value>, OTelSdkError>
232where
233 T: Copy,
234{
235 let time = gauge.time();
236 let mut data_points = Vec::new();
237 for point in gauge.data_points() {
238 let mut dp = Map::new();
239 dp.insert("value".to_owned(), value_serializer(point.value()));
240
241 let time: DateTime<Utc> = time.into();
242 dp.insert(
243 "time".to_owned(),
244 Value::String(time.timestamp_micros().to_string()),
245 );
246
247 dp.insert(
248 "attributes".to_owned(),
249 attributes_to_json(point.attributes()),
250 );
251 data_points.push(Value::Object(dp));
252 }
253 Ok(data_points)
254}
255
256fn serialize_histogram_data_points<T>(
258 histogram: &Histogram<T>,
259 value_serializer: impl Fn(T) -> Value,
260) -> Result<Vec<Value>, OTelSdkError>
261where
262 T: Copy,
263{
264 let start_time = histogram.start_time();
265 let time = histogram.time();
266 let mut data_points = Vec::new();
267 for point in histogram.data_points() {
268 let mut dp = Map::new();
269 dp.insert("count".to_owned(), Value::Number(point.count().into()));
270 dp.insert("sum".to_owned(), value_serializer(point.sum()));
271
272 let bucket_counts: Vec<Value> = point
274 .bucket_counts()
275 .map(|count| Value::Number(count.into()))
276 .collect();
277 dp.insert("bucket_counts".to_owned(), Value::Array(bucket_counts));
278
279 let bounds: Vec<Value> = point
281 .bounds()
282 .map(|bound| Value::String(bound.to_string()))
283 .collect();
284 dp.insert("bounds".to_owned(), Value::Array(bounds));
285
286 let start_time: DateTime<Utc> = start_time.into();
287 dp.insert(
288 "start_time".to_owned(),
289 Value::String(start_time.timestamp_micros().to_string()),
290 );
291 let time: DateTime<Utc> = time.into();
292 dp.insert(
293 "time".to_owned(),
294 Value::String(time.timestamp_micros().to_string()),
295 );
296
297 dp.insert(
298 "attributes".to_owned(),
299 attributes_to_json(point.attributes()),
300 );
301 data_points.push(Value::Object(dp));
302 }
303 Ok(data_points)
304}
305
306fn attributes_to_json<'a>(attributes: impl Iterator<Item = &'a opentelemetry::KeyValue>) -> Value {
308 let mut json_attributes = Map::new();
309 for kv in attributes {
310 json_attributes.insert(kv.key.to_string(), Value::String(kv.value.to_string()));
311 }
312 Value::Object(json_attributes)
313}