use chrono::{DateTime, Utc};
use core::fmt;
use opentelemetry_sdk::error::{OTelSdkError, OTelSdkResult};
use opentelemetry_sdk::trace::SpanData;
use serde_json::{Map, Value};
use std::fs::OpenOptions;
use std::io::Write;
use std::path::PathBuf;
use std::sync::atomic;
use opentelemetry_sdk::Resource;
pub struct SpanExporterFile {
resource: Resource,
is_shutdown: atomic::AtomicBool,
path: PathBuf,
}
impl fmt::Debug for SpanExporterFile {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("SpanExporterFile")
}
}
impl SpanExporterFile {
pub fn new(path: PathBuf) -> Result<Self, OTelSdkError> {
Ok(Self {
resource: Resource::builder_empty().build(),
is_shutdown: atomic::AtomicBool::new(false),
path,
})
}
}
impl opentelemetry_sdk::trace::SpanExporter for SpanExporterFile {
async fn export(&self, batch: Vec<SpanData>) -> OTelSdkResult {
if self.is_shutdown.load(atomic::Ordering::SeqCst) {
return Err(OTelSdkError::AlreadyShutdown);
}
let Ok(mut file) = OpenOptions::new()
.create(true)
.append(true)
.open(&self.path)
else {
return Err(OTelSdkError::InternalFailure(format!(
"Unable to open exporter file: {} for append.",
self.path.display()
)));
};
let spans = to_jsons(batch);
for span in &spans {
if let Ok(s) = serde_json::to_string(&span)
&& let Err(e) = writeln!(file, "{}", s)
{
return Err(OTelSdkError::InternalFailure(format!(
"File write error. {e}",
)));
}
}
Ok(())
}
fn shutdown(&self) -> OTelSdkResult {
self.is_shutdown.store(true, atomic::Ordering::SeqCst);
Ok(())
}
fn set_resource(&mut self, res: &Resource) {
self.resource = res.clone();
}
}
fn to_jsons(batch: Vec<SpanData>) -> Vec<Value> {
let mut spans = Vec::<Value>::new();
for span in &batch {
let mut map = Map::new();
map.insert(
"scope".to_owned(),
Value::String(span.instrumentation_scope.name().to_string()),
);
if let Some(version) = &span.instrumentation_scope.version() {
map.insert("version".to_owned(), Value::String(version.to_string()));
}
if let Some(schema_url) = &span.instrumentation_scope.schema_url() {
map.insert(
"schema_url".to_owned(),
Value::String(schema_url.to_string()),
);
}
let mut scope_attributes = Vec::<Value>::new();
for kv in span.instrumentation_scope.attributes() {
let mut attr = Map::new();
attr.insert(kv.key.to_string(), Value::String(kv.value.to_string()));
scope_attributes.push(Value::Object(attr));
}
map.insert(
"scope_attributes".to_owned(),
Value::Array(scope_attributes),
);
map.insert("name".to_owned(), Value::String(span.name.to_string()));
map.insert(
"span_id".to_owned(),
Value::String(span.span_context.span_id().to_string()),
);
map.insert(
"parent_span_id".to_owned(),
Value::String(span.parent_span_id.to_string()),
);
map.insert(
"trace_id".to_owned(),
Value::String(span.span_context.trace_id().to_string()),
);
map.insert(
"kind".to_owned(),
Value::String(format!("{:?}", span.span_kind)),
);
let datetime: DateTime<Utc> = span.start_time.into();
map.insert(
"start_time".to_owned(),
Value::String(datetime.timestamp_micros().to_string()),
);
let datetime: DateTime<Utc> = span.end_time.into();
map.insert(
"end_time".to_owned(),
Value::String(datetime.timestamp_micros().to_string()),
);
map.insert(
"status".to_owned(),
Value::String(format!("{:?}", span.status)),
);
let mut span_attributes = Vec::<Value>::new();
for kv in span.attributes.iter() {
let mut attr = Map::new();
attr.insert(kv.key.to_string(), Value::String(kv.value.to_string()));
span_attributes.push(Value::Object(attr));
}
map.insert("span_attributes".to_owned(), Value::Array(span_attributes));
let mut events = Vec::<Value>::new();
for event in span.events.iter() {
let mut evt = Map::new();
evt.insert("name".to_owned(), Value::String(event.name.to_string()));
let datetime: DateTime<Utc> = event.timestamp.into();
evt.insert(
"timestamp".to_owned(),
Value::String(datetime.format("%Y-%m-%d %H:%M:%S%.6f").to_string()),
);
let mut event_attributes = Vec::<Value>::new();
for kv in event.attributes.iter() {
let mut attr = Map::new();
attr.insert(kv.key.to_string(), Value::String(kv.value.to_string()));
event_attributes.push(Value::Object(attr));
}
evt.insert(
"event_attributes".to_owned(),
Value::Array(event_attributes),
);
events.push(Value::Object(evt));
}
map.insert("events".to_owned(), Value::Array(events));
let mut links = Vec::<Value>::new();
for link in span.links.iter() {
let mut lk = Map::new();
lk.insert(
"trace_id".to_owned(),
Value::String(link.span_context.trace_id().to_string()),
);
lk.insert(
"span_id".to_owned(),
Value::String(link.span_context.span_id().to_string()),
);
links.push(Value::Object(lk));
}
map.insert("links".to_owned(), Value::Array(links));
spans.push(Value::Object(map));
}
spans
}