use serde_json::{Map, Value};
use std::fmt;
use tracing::field::{Field, Visit};
use tracing::{Event, Subscriber};
use tracing_subscriber::{
fmt::{format::Writer, FmtContext, FormatEvent, FormatFields},
registry::LookupSpan,
};
pub struct BatchJsonFormatter {
static_fields: Vec<(&'static str, String)>,
}
impl BatchJsonFormatter {
pub const fn new(static_fields: Vec<(&'static str, String)>) -> Self {
Self { static_fields }
}
}
fn timestamp_ms() -> u64 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map_or(0, |d| u64::try_from(d.as_millis()).unwrap_or(u64::MAX))
}
struct JsonVisitor<'a>(&'a mut Map<String, Value>);
impl Visit for JsonVisitor<'_> {
fn record_f64(&mut self, field: &Field, value: f64) {
let json_value = serde_json::Number::from_f64(value)
.map_or_else(|| Value::String(value.to_string()), Value::Number);
self.0.insert(field.name().to_owned(), json_value);
}
fn record_i64(&mut self, field: &Field, value: i64) {
self.0
.insert(field.name().to_owned(), Value::Number(value.into()));
}
fn record_u64(&mut self, field: &Field, value: u64) {
self.0
.insert(field.name().to_owned(), Value::Number(value.into()));
}
fn record_bool(&mut self, field: &Field, value: bool) {
self.0.insert(field.name().to_owned(), Value::Bool(value));
}
fn record_str(&mut self, field: &Field, value: &str) {
self.0
.insert(field.name().to_owned(), Value::String(value.to_owned()));
}
fn record_error(&mut self, field: &Field, value: &(dyn std::error::Error + 'static)) {
self.0
.insert(field.name().to_owned(), Value::String(value.to_string()));
}
fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) {
self.0
.insert(field.name().to_owned(), Value::String(format!("{value:?}")));
}
}
impl<S, N> FormatEvent<S, N> for BatchJsonFormatter
where
S: Subscriber + for<'a> LookupSpan<'a>,
N: for<'a> FormatFields<'a> + 'static,
{
fn format_event(
&self,
_ctx: &FmtContext<'_, S, N>,
mut writer: Writer<'_>,
event: &Event<'_>,
) -> fmt::Result {
let mut map = Map::new();
map.insert("timestamp".to_owned(), Value::Number(timestamp_ms().into()));
map.insert(
"level".to_owned(),
Value::String(event.metadata().level().to_string()),
);
map.insert(
"name".to_owned(),
Value::String(event.metadata().target().to_owned()),
);
if let Some(file) = event.metadata().file() {
if let Some(line) = event.metadata().line() {
map.insert(
"file_location".to_owned(),
Value::String(format!("{file}:{line}")),
);
}
}
for (key, value) in &self.static_fields {
map.insert((*key).to_owned(), Value::String(value.clone()));
}
event.record(&mut JsonVisitor(&mut map));
let json = serde_json::to_string(&Value::Object(map)).unwrap_or_else(|_| {
r#"{"level":"ERROR","message":"log serialization failed"}"#.to_owned()
});
writeln!(writer, "{json}")
}
}