use serde::Serialize;
use crate::{
proto::{
common::v1::{AnyValue, KeyValue, any_value},
trace::v1::Span,
},
trace::SpanValue,
};
fn hex(bytes: &[u8]) -> String {
::hex::encode(bytes)
}
fn any_value_to_serde_value(value: any_value::Value) -> serde_json::Value {
match value {
any_value::Value::StringValue(s) => s.into(),
any_value::Value::BoolValue(b) => b.into(),
any_value::Value::IntValue(i) => i.into(),
any_value::Value::DoubleValue(d) => d.into(),
any_value::Value::ArrayValue(a) => {
let v = a
.values
.into_iter()
.flat_map(|v| v.value.map(any_value_to_serde_value))
.collect::<Vec<_>>();
serde_json::Value::Array(v).to_string().into()
}
any_value::Value::BytesValue(b) => hex(&b).into(),
any_value::Value::KvlistValue(_) => "unsupported".into(),
}
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct JaegerKv {
pub key: String,
pub r#type: &'static str,
pub value: serde_json::Value,
}
impl From<KeyValue> for JaegerKv {
fn from(kv: KeyValue) -> Self {
let key = kv.key;
let Some(AnyValue { value: Some(value) }) = kv.value else {
return Self {
key,
r#type: "string",
value: serde_json::Value::Null,
};
};
let r#type = match value {
any_value::Value::StringValue(_) => "string",
any_value::Value::BoolValue(_) => "bool",
any_value::Value::IntValue(_) => "int64",
any_value::Value::DoubleValue(_) => "float64",
any_value::Value::ArrayValue(_)
| any_value::Value::KvlistValue(_)
| any_value::Value::BytesValue(_) => "string",
};
let value = any_value_to_serde_value(value);
Self { key, r#type, value }
}
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct JaegerProcess {
pub key: String,
pub service_name: String,
pub tags: Vec<JaegerKv>,
}
impl From<&SpanValue> for JaegerProcess {
fn from(value: &SpanValue) -> Self {
let key = value.service_instance_id().to_owned();
let service_name = value.service_name().to_owned();
let tags = (value.resource.attributes)
.iter()
.cloned()
.map(JaegerKv::from)
.collect::<Vec<_>>();
Self {
key,
service_name,
tags,
}
}
}
pub(crate) fn span_to_jaeger_json(span: Span, process: String) -> serde_json::Value {
let logs = span
.events
.into_iter()
.map(|e| {
let fields = e
.attributes
.into_iter()
.map(JaegerKv::from)
.collect::<Vec<_>>();
let timestamp = e.time_unix_nano / 1000;
serde_json::json!({
"timestamp": timestamp,
"fields": fields,
})
})
.collect::<Vec<_>>();
let tags = span
.attributes
.into_iter()
.map(JaegerKv::from)
.collect::<Vec<_>>();
let mut references = if span.links.is_empty() {
None
} else {
Some(
span.links
.into_iter()
.map(|link| {
serde_json::json!({
"refType": "FOLLOWS_FROM",
"traceID": hex(&link.trace_id),
"spanID": hex(&link.span_id)
})
})
.collect::<Vec<_>>(),
)
};
if !span.parent_span_id.is_empty() {
let val = serde_json::json!({
"refType": "CHILD_OF",
"traceID": hex(&span.trace_id),
"spanID": hex(&span.parent_span_id),
});
references.get_or_insert_with(Vec::new).push(val);
}
serde_json::json!({
"traceID": hex(&span.trace_id),
"spanID": hex(&span.span_id),
"startTime": span.start_time_unix_nano / 1000,
"duration": (span.end_time_unix_nano - span.start_time_unix_nano) / 1000,
"operationName": span.name,
"tags": tags,
"logs": logs,
"processID": process,
"warnings": None::<String>,
"references": references,
})
}