use opentelemetry_proto::tonic::{
common::v1::{any_value::Value as OtlpValue, AnyValue, ArrayValue, KeyValue, KeyValueList},
logs::v1::LogRecord,
};
use serde_json::{Map, Value};
use crate::{ParsedLog, Severity};
pub fn log_record(log: &ParsedLog<'_>) -> LogRecord {
let time_unix_nano = log
.timestamp()
.and_then(|time| {
u64::try_from(time.unix_seconds())
.ok()?
.checked_mul(1_000_000_000)?
.checked_add(u64::from(time.nanoseconds()))
})
.unwrap_or(0);
let severity_number = match log.severity() {
None => 0,
Some(Severity::Trace) => 1,
Some(Severity::Debug) => 5,
Some(Severity::Info) => 9,
Some(Severity::Warn) => 13,
Some(Severity::Error) => 17,
Some(Severity::Fatal) => 21,
};
LogRecord {
time_unix_nano,
severity_number,
severity_text: log
.severity()
.map(Severity::as_str)
.unwrap_or("")
.to_owned(),
body: Some(AnyValue {
value: Some(OtlpValue::StringValue(log.message().to_owned())),
}),
attributes: if log.fields().is_empty() {
Vec::new()
} else {
vec![KeyValue {
key: "app".to_owned(),
value: Some(object(log.fields())),
}]
},
..LogRecord::default()
}
}
fn object(fields: &Map<String, Value>) -> AnyValue {
AnyValue {
value: Some(OtlpValue::KvlistValue(KeyValueList {
values: fields
.iter()
.map(|(key, value)| KeyValue {
key: key.clone(),
value: Some(value_to_otlp(value)),
})
.collect(),
})),
}
}
fn value_to_otlp(value: &Value) -> AnyValue {
let value = match value {
Value::Null => None,
Value::Bool(value) => Some(OtlpValue::BoolValue(*value)),
Value::String(value) => Some(OtlpValue::StringValue(value.clone())),
Value::Array(values) => Some(OtlpValue::ArrayValue(ArrayValue {
values: values.iter().map(value_to_otlp).collect(),
})),
Value::Object(fields) => return object(fields),
Value::Number(number) => Some(match (number.as_i64(), number.as_f64()) {
(Some(integer), _) => OtlpValue::IntValue(integer),
(None, Some(double)) if number.is_f64() => OtlpValue::DoubleValue(double),
_ => OtlpValue::StringValue(number.to_string()),
}),
};
AnyValue { value }
}