use crate::log::fmt::formatter::LogFormatter;
use crate::log::fmt::log_layer::{LogLayer, Type};
use crate::log::fmt::log_value::LogValue;
use crate::log::fmt::storage::Storage;
use crate::time::{DateTime, SecondsFormat};
use hashbrown::HashMap;
use serde::ser::{SerializeMap, Serializer};
use tracing::{Event, Id, Level, Subscriber};
use tracing_subscriber::fmt::MakeWriter;
use tracing_subscriber::registry::{LookupSpan, SpanRef};
const BUNYAN_VERSION: &str = "v";
const LEVEL: &str = "level";
const NAME: &str = "name";
const HOSTNAME: &str = "hostname";
const PID: &str = "pid";
const TIME: &str = "time";
const MESSAGE: &str = "msg";
const _SOURCE: &str = "src";
const BUNYAN_RESERVED_FIELDS: [&str; 7] =
[BUNYAN_VERSION, LEVEL, NAME, HOSTNAME, PID, TIME, MESSAGE];
fn to_bunyan_level(level: &tracing::Level) -> u16 {
match *level {
tracing::Level::ERROR => 50,
tracing::Level::WARN => 40,
tracing::Level::INFO => 30,
tracing::Level::DEBUG => 20,
tracing::Level::TRACE => 10,
}
}
pub struct Bunyan {
pid: u32,
hostname: String,
bunyan_version: u8,
name: String,
default_fields: HashMap<String, LogValue>,
}
impl Bunyan {
pub fn new(name: String) -> Self {
Self::with_default_fields(name, HashMap::new())
}
pub fn with_default_fields(name: String, default_fields: HashMap<String, LogValue>) -> Self {
Self {
name,
pid: crate::common::process::id(),
hostname: crate::net::hostname::gethostname()
.to_string_lossy()
.into_owned(),
bunyan_version: 0,
default_fields,
}
}
fn serialize_bunyan_core_fields(
&self,
map_serializer: &mut impl SerializeMap<Error = serde_json::Error>,
message: &str,
level: &Level,
) -> Result<(), std::io::Error> {
map_serializer.serialize_entry(BUNYAN_VERSION, &self.bunyan_version)?;
map_serializer.serialize_entry(NAME, &self.name)?;
map_serializer.serialize_entry(MESSAGE, &message)?;
map_serializer.serialize_entry(LEVEL, &to_bunyan_level(level))?;
map_serializer.serialize_entry(HOSTNAME, &self.hostname)?;
map_serializer.serialize_entry(PID, &self.pid)?;
map_serializer.serialize_entry(
TIME,
&DateTime::now_or_zero().to_rfc3339_opts(SecondsFormat::Millis, true),
)?;
Ok(())
}
fn serialize_span<S: Subscriber + for<'a> tracing_subscriber::registry::LookupSpan<'a>>(
&self,
span: &SpanRef<S>,
ty: Type,
) -> Result<Vec<u8>, std::io::Error> {
let mut buffer = Vec::new();
let mut serializer = serde_json::Serializer::new(&mut buffer);
let mut map_serializer = serializer.serialize_map(None)?;
let message = format_span_context(span, ty);
self.serialize_bunyan_core_fields(&mut map_serializer, &message, span.metadata().level())?;
map_serializer.serialize_entry("target", span.metadata().target())?;
map_serializer.serialize_entry("line", &span.metadata().line())?;
map_serializer.serialize_entry("file", &span.metadata().file())?;
for (key, value) in self.default_fields.iter() {
if !BUNYAN_RESERVED_FIELDS.contains(&key.as_str()) {
map_serializer.serialize_entry(key, &serde_json::Value::from(value))?;
} else {
tracing::debug!(
"{} is a reserved field in the bunyan log format. Skipping it.",
key
);
}
}
let extensions = span.extensions();
if let Some(visitor) = extensions.get::<Storage>() {
for (key, value) in visitor.values() {
if !BUNYAN_RESERVED_FIELDS.contains(key) {
map_serializer.serialize_entry(key, &serde_json::Value::from(value))?;
} else {
tracing::debug!(
"{} is a reserved field in the bunyan log format. Skipping it.",
key
);
}
}
}
map_serializer.end()?;
Ok(buffer)
}
}
impl Default for Bunyan {
fn default() -> Self {
Self {
pid: 0,
hostname: "".to_string(),
bunyan_version: 0,
name: "".to_string(),
default_fields: Default::default(),
}
}
}
fn format_span_context<S: Subscriber + for<'a> tracing_subscriber::registry::LookupSpan<'a>>(
span: &SpanRef<S>,
ty: Type,
) -> String {
format!("[{} - {}]", span.metadata().name().to_uppercase(), ty)
}
fn format_event_message<S: Subscriber + for<'a> tracing_subscriber::registry::LookupSpan<'a>>(
current_span: &Option<SpanRef<S>>,
event: &Event,
event_visitor: &Storage<'_>,
) -> String {
let mut message = event_visitor
.values()
.get("message")
.map(|v| match v {
LogValue::String(s) => Some(s.as_str()),
_ => None,
})
.flatten()
.unwrap_or_else(|| event.metadata().target())
.to_owned();
if let Some(span) = ¤t_span {
message = format!("{} {}", format_span_context(span, Type::Event), message);
}
message
}
impl LogFormatter for Bunyan {
fn from_log_layer<W: for<'a> MakeWriter<'a> + 'static, F: LogFormatter + Default>(
layer: &LogLayer<W, F>,
) -> Self {
Self {
pid: layer.proc_id().clone().unwrap_or(0),
hostname: layer.hostname().clone().unwrap_or("localhost".to_string()),
bunyan_version: 0,
name: layer.application().clone().unwrap_or("app".to_string()),
default_fields: Default::default(),
}
}
fn format_event<S: Subscriber + for<'a> LookupSpan<'a>>(
&mut self,
current_span: &Option<SpanRef<S>>,
event: &Event,
event_visitor: &Storage<'_>,
) -> String {
let mut event_visitor = Storage::default();
event.record(&mut event_visitor);
let format = || {
let mut buffer = Vec::new();
let mut serializer = serde_json::Serializer::new(&mut buffer);
let mut map_serializer = serializer.serialize_map(None)?;
let message = format_event_message(¤t_span, event, &event_visitor);
self.serialize_bunyan_core_fields(
&mut map_serializer,
&message,
event.metadata().level(),
)?;
map_serializer.serialize_entry("target", event.metadata().target())?;
map_serializer.serialize_entry("line", &event.metadata().line())?;
map_serializer.serialize_entry("file", &event.metadata().file())?;
for (key, value) in self.default_fields.iter().filter(|(key, _)| {
key.as_str() != "message" && !BUNYAN_RESERVED_FIELDS.contains(&key.as_str())
}) {
map_serializer.serialize_entry(key, &serde_json::Value::from(value))?;
}
for (key, value) in event_visitor
.values()
.iter()
.filter(|(&key, _)| key != "message" && !BUNYAN_RESERVED_FIELDS.contains(&key))
{
map_serializer.serialize_entry(key, &serde_json::Value::from(value))?;
}
if let Some(span) = ¤t_span {
let extensions = span.extensions();
if let Some(visitor) = extensions.get::<Storage>() {
for (key, value) in visitor.values() {
if !BUNYAN_RESERVED_FIELDS.contains(key) {
map_serializer.serialize_entry(key, &serde_json::Value::from(value))?;
} else {
tracing::debug!(
"{} is a reserved field in the bunyan log format. Skipping it.",
key
);
}
}
}
}
map_serializer.end()?;
Ok(buffer)
};
format()
.map_err(|e: std::io::Error| e.to_string())
.and_then(|t| String::from_utf8(t).map_err(|e| e.to_string()))
.unwrap_or_else(|e| e)
}
fn format_span<S: Subscriber + for<'a> LookupSpan<'a>>(
&mut self,
span: &SpanRef<S>,
ty: Type,
) -> String {
self.serialize_span(&span, ty)
.map_err(|e: std::io::Error| e.to_string())
.and_then(|t| String::from_utf8(t).map_err(|e| e.to_string()))
.unwrap_or_else(|e| e)
}
}