#![allow(dead_code)]
use std::io::{self, BufRead, Write};
use std::marker::PhantomData;
use serde::{Deserialize, Serialize, de::DeserializeOwned};
mod integer;
mod value;
pub use value::AnyValue;
fn is_default<T: Default + PartialEq>(value: &T) -> bool {
value == &T::default()
}
macro_rules! message {
($name:ident { $($(#[$attr:meta])* $field:ident: $ty:ty),* $(,)? }) => {
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
#[serde(default, rename_all = "camelCase")]
pub struct $name {
$(#[serde(skip_serializing_if = "is_default")]
$(#[$attr])*
pub $field: $ty,)*
}
};
}
message!(Logs { resource_logs: Vec<ResourceLogs> });
message!(Spans { resource_spans: Vec<ResourceSpans> });
message!(ResourceSpans {
resource: Option<Resource>,
scope_spans: Vec<ScopeSpans>,
schema_url: String,
});
message!(ScopeSpans {
scope: Option<Scope>,
spans: Vec<Span>,
schema_url: String,
});
message!(ResourceLogs {
resource: Option<Resource>,
scope_logs: Vec<ScopeLogs>,
schema_url: String,
});
message!(ScopeLogs {
scope: Option<Scope>,
log_records: Vec<LogRecord>,
schema_url: String,
});
message!(Resource {
attributes: Vec<KeyValue>,
#[serde(deserialize_with = "integer::deserialize")]
dropped_attributes_count: u32,
});
message!(Scope {
name: String,
version: String,
attributes: Vec<KeyValue>,
#[serde(deserialize_with = "integer::deserialize")]
dropped_attributes_count: u32,
});
message!(KeyValue { key: String, value: Option<AnyValue> });
message!(LogRecord {
#[serde(with = "integer")]
time_unix_nano: u64,
#[serde(with = "integer")]
observed_time_unix_nano: u64,
severity_number: i32,
severity_text: String,
body: Option<AnyValue>,
attributes: Vec<KeyValue>,
#[serde(deserialize_with = "integer::deserialize")]
dropped_attributes_count: u32,
#[serde(deserialize_with = "integer::deserialize")]
flags: u32,
#[serde(deserialize_with = "optional_id")]
trace_id: Option<TraceId>,
#[serde(deserialize_with = "optional_id")]
span_id: Option<SpanId>,
event_name: String,
});
message!(Span {
#[serde(deserialize_with = "optional_id")]
trace_id: Option<TraceId>,
#[serde(deserialize_with = "optional_id")]
span_id: Option<SpanId>,
trace_state: String,
#[serde(deserialize_with = "optional_id")]
parent_span_id: Option<SpanId>,
#[serde(deserialize_with = "integer::deserialize")]
flags: u32,
name: String,
kind: i32,
#[serde(with = "integer")]
start_time_unix_nano: u64,
#[serde(with = "integer")]
end_time_unix_nano: u64,
attributes: Vec<KeyValue>,
#[serde(deserialize_with = "integer::deserialize")]
dropped_attributes_count: u32,
events: Vec<SpanEvent>,
#[serde(deserialize_with = "integer::deserialize")]
dropped_events_count: u32,
links: Vec<SpanLink>,
#[serde(deserialize_with = "integer::deserialize")]
dropped_links_count: u32,
status: Option<Status>,
});
message!(SpanEvent {
#[serde(with = "integer")]
time_unix_nano: u64,
name: String,
attributes: Vec<KeyValue>,
#[serde(deserialize_with = "integer::deserialize")]
dropped_attributes_count: u32,
});
message!(SpanLink {
#[serde(deserialize_with = "optional_id")]
trace_id: Option<TraceId>,
#[serde(deserialize_with = "optional_id")]
span_id: Option<SpanId>,
trace_state: String,
attributes: Vec<KeyValue>,
#[serde(deserialize_with = "integer::deserialize")]
dropped_attributes_count: u32,
#[serde(deserialize_with = "integer::deserialize")]
flags: u32,
});
message!(Status {
message: String,
code: i32,
});
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Id<const N: usize>(pub [u8; N]);
pub type TraceId = Id<16>;
pub type SpanId = Id<8>;
impl<const N: usize> Serialize for Id<N> {
fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
let hex: String = self.0.iter().map(|byte| format!("{byte:02x}")).collect();
s.serialize_str(&hex)
}
}
impl<'de, const N: usize> Deserialize<'de> for Id<N> {
fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
use serde::de::Error;
let hex = String::deserialize(d)?;
if hex.len() != N * 2 {
return Err(D::Error::custom(format!("expected {} hex digits", N * 2)));
}
let mut bytes = [0; N];
for (byte, pair) in bytes.iter_mut().zip(hex.as_bytes().as_chunks::<2>().0) {
let high = (pair[0] as char).to_digit(16);
let low = (pair[1] as char).to_digit(16);
match (high, low) {
(Some(h), Some(l)) => *byte = (h * 16 + l) as u8,
_ => return Err(D::Error::custom("invalid hex ID")),
}
}
Ok(Id(bytes))
}
}
fn optional_id<'de, D: serde::Deserializer<'de>, const N: usize>(
d: D,
) -> Result<Option<Id<N>>, D::Error> {
use serde::de::IntoDeserializer;
match Option::<String>::deserialize(d)? {
Some(s) if !s.is_empty() => Id::deserialize(s.into_deserializer()).map(Some),
_ => Ok(None),
}
}
mod sealed {
pub trait Signal {
const KEY: &'static str;
fn is_empty(&self) -> bool;
}
impl Signal for super::Logs {
const KEY: &'static str = "resourceLogs";
fn is_empty(&self) -> bool {
self.resource_logs
.iter()
.flat_map(|resource| &resource.scope_logs)
.all(|scope| scope.log_records.is_empty())
}
}
impl Signal for super::Spans {
const KEY: &'static str = "resourceSpans";
fn is_empty(&self) -> bool {
self.resource_spans
.iter()
.flat_map(|resource| &resource.scope_spans)
.all(|scope| scope.spans.is_empty())
}
}
}
pub trait Request: sealed::Signal + Serialize + DeserializeOwned {}
impl Request for Logs {}
impl Request for Spans {}
pub struct Writer<W, R> {
output: W,
request: PhantomData<R>,
}
impl<W: Write, R: Request> Writer<W, R> {
pub fn new(output: W) -> Self {
Self {
output,
request: PhantomData,
}
}
pub fn write(&mut self, request: &R) -> io::Result<()> {
if request.is_empty() {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"empty OTLP export request",
));
}
let mut line = serde_json::to_vec(request)?;
line.push(b'\n');
self.output.write_all(&line)?;
self.output.flush()
}
}
pub struct Reader<B, R> {
input: B,
line: String,
request: PhantomData<R>,
}
impl<B: BufRead, R: Request> Reader<B, R> {
pub fn new(input: B) -> Self {
Self {
input,
line: String::new(),
request: PhantomData,
}
}
}
impl<B: BufRead, R: Request> Iterator for Reader<B, R> {
type Item = io::Result<R>;
fn next(&mut self) -> Option<Self::Item> {
self.line.clear();
match self.input.read_line(&mut self.line) {
Ok(0) => None,
Ok(_) => Some(
(|| {
use serde::de::Error;
let mut value = serde_json::from_str(&self.line)?;
let fields = match &value {
serde_json::Value::Object(fields) => fields,
_ => {
return Err(serde_json::Error::custom(
"expected OTLP export request object",
));
}
};
if [
"resourceLogs",
"resourceSpans",
"resourceMetrics",
"resourceProfiles",
]
.iter()
.any(|key| *key != R::KEY && fields.contains_key(*key))
{
return Err(serde_json::Error::custom("wrong signal in OTLP file"));
}
remove_null_fields(&mut value);
serde_json::from_value(value)
})()
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e)),
),
Err(e) => Some(Err(e)),
}
}
}
fn remove_null_fields(value: &mut serde_json::Value) {
match value {
serde_json::Value::Object(fields) => {
fields.retain(|_, value| !value.is_null());
for value in fields.values_mut() {
remove_null_fields(value);
}
}
serde_json::Value::Array(values) => {
for value in values {
remove_null_fields(value);
}
}
_ => {}
}
}
#[cfg(test)]
mod tests;