use std::{borrow::Cow, collections::BTreeMap, env, fmt, io, marker::PhantomData, sync::LazyLock};
use regex::Regex;
use serde::{ser::SerializeMap, Deserialize, Serialize, Serializer};
use serde_json::{Serializer as JsonSerializer, Value};
use tracing::{Event, Subscriber};
use tracing_log::NormalizeEvent;
use tracing_serde::AsSerde;
use tracing_subscriber::{
fmt::{
format::Writer,
time::{FormatTime, SystemTime},
FmtContext, FormatEvent, FormatFields, FormattedFields,
},
registry::{LookupSpan, SpanRef},
};
static ENV_VAR_REGEX: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"\$([A-Za-z_][A-Za-z0-9_]*)").unwrap());
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[non_exhaustive]
pub struct JsonKeyNames {
#[serde(default = "JsonKeyNames::default_timestamp")]
timestamp: Cow<'static, str>,
#[serde(default = "JsonKeyNames::default_level")]
level: Cow<'static, str>,
#[serde(default = "JsonKeyNames::default_fields")]
fields: Cow<'static, str>,
#[serde(default = "JsonKeyNames::default_target")]
target: Cow<'static, str>,
#[serde(default = "JsonKeyNames::default_filename")]
filename: Cow<'static, str>,
#[serde(default = "JsonKeyNames::default_line_number")]
line_number: Cow<'static, str>,
#[serde(default = "JsonKeyNames::default_span")]
span: Cow<'static, str>,
#[serde(default = "JsonKeyNames::default_thread_name")]
thread_name: Cow<'static, str>,
#[serde(default = "JsonKeyNames::default_thread_id")]
thread_id: Cow<'static, str>,
}
impl Default for JsonKeyNames {
fn default() -> Self {
Self {
timestamp: Self::default_timestamp(),
level: Self::default_level(),
fields: Self::default_fields(),
target: Self::default_target(),
filename: Self::default_filename(),
line_number: Self::default_line_number(),
span: Self::default_span(),
thread_name: Self::default_thread_name(),
thread_id: Self::default_thread_id(),
}
}
}
impl JsonKeyNames {
#[must_use]
#[inline]
fn default_timestamp() -> Cow<'static, str> {
Cow::Borrowed("timestamp")
}
#[must_use]
#[inline]
fn default_level() -> Cow<'static, str> {
Cow::Borrowed("level")
}
#[must_use]
#[inline]
fn default_fields() -> Cow<'static, str> {
Cow::Borrowed("fields")
}
#[must_use]
#[inline]
fn default_target() -> Cow<'static, str> {
Cow::Borrowed("target")
}
#[must_use]
#[inline]
fn default_filename() -> Cow<'static, str> {
Cow::Borrowed("filename")
}
#[must_use]
#[inline]
fn default_line_number() -> Cow<'static, str> {
Cow::Borrowed("line_number")
}
#[must_use]
#[inline]
fn default_span() -> Cow<'static, str> {
Cow::Borrowed("span")
}
#[must_use]
#[inline]
fn default_thread_name() -> Cow<'static, str> {
Cow::Borrowed("threadName")
}
#[must_use]
#[inline]
fn default_thread_id() -> Cow<'static, str> {
Cow::Borrowed("threadId")
}
}
#[derive(Clone, Debug)]
pub(crate) struct ExtensibleJsonFormat<T = SystemTime> {
timer: T,
display_timestamp: bool,
display_target: bool,
display_level: bool,
display_thread_id: bool,
display_thread_name: bool,
display_filename: bool,
display_line_number: bool,
flatten_event: bool,
display_current_span: bool,
static_fields: BTreeMap<String, Value>,
key_names: JsonKeyNames,
}
impl Default for ExtensibleJsonFormat {
fn default() -> Self {
Self {
timer: SystemTime,
display_timestamp: true,
display_target: true,
display_level: true,
display_thread_id: false,
display_thread_name: false,
display_filename: false,
display_line_number: false,
flatten_event: false,
display_current_span: true,
static_fields: BTreeMap::new(),
key_names: JsonKeyNames::default(),
}
}
}
impl<S, N, T> FormatEvent<S, N> for ExtensibleJsonFormat<T>
where
S: Subscriber + for<'lookup> LookupSpan<'lookup>,
N: for<'writer> FormatFields<'writer> + 'static,
T: FormatTime,
{
fn format_event(
&self,
ctx: &FmtContext<'_, S, N>,
mut writer: Writer<'_>,
event: &Event<'_>,
) -> fmt::Result {
let mut timestamp = String::new();
self.timer.format_time(&mut Writer::new(&mut timestamp))?;
let meta = event.normalized_metadata();
let meta = meta.as_ref().unwrap_or_else(|| event.metadata());
let mut visit = || {
let mut ser = JsonSerializer::new(JsonWriter::new(&mut writer));
let mut ser = ser.serialize_map(None)?;
if self.display_timestamp {
ser.serialize_entry(self.key_names.timestamp.as_ref(), ×tamp)?;
}
if self.display_level {
ser.serialize_entry(self.key_names.level.as_ref(), &meta.level().as_serde())?;
}
let format_field_marker: PhantomData<N> = PhantomData;
let current_span = if self.display_current_span {
event
.parent()
.and_then(|id| ctx.span(id))
.or_else(|| ctx.lookup_current())
} else {
None
};
if self.flatten_event {
let mut visitor = tracing_serde::SerdeMapVisitor::new(ser);
event.record(&mut visitor);
ser = visitor.take_serializer()?;
} else {
use tracing_serde::fields::AsMap;
ser.serialize_entry(self.key_names.fields.as_ref(), &event.field_map())?;
}
if self.display_target {
ser.serialize_entry(self.key_names.target.as_ref(), meta.target())?;
}
if self.display_filename {
if let Some(filename) = meta.file() {
ser.serialize_entry(self.key_names.filename.as_ref(), filename)?;
}
}
if self.display_line_number {
if let Some(line_number) = meta.line() {
ser.serialize_entry(self.key_names.line_number.as_ref(), &line_number)?;
}
}
if self.display_current_span {
if let Some(ref span) = current_span {
ser.serialize_entry(
self.key_names.span.as_ref(),
&SerializableSpan(span, format_field_marker),
)
.unwrap_or(());
}
}
if self.display_thread_name {
let current_thread = std::thread::current();
match current_thread.name() {
Some(name) => ser.serialize_entry(self.key_names.thread_name.as_ref(), name)?,
None if !self.display_thread_id => {
ser.serialize_entry(
self.key_names.thread_name.as_ref(),
&format!("{:?}", current_thread.id()),
)?;
}
_ => {}
}
}
if self.display_thread_id {
ser.serialize_entry(
self.key_names.thread_id.as_ref(),
&format!("{:?}", std::thread::current().id()),
)?;
}
for (key, val) in &self.static_fields {
ser.serialize_entry(key, val)?;
}
ser.end()
};
visit().map_err(|_| fmt::Error)?;
writeln!(writer)
}
}
impl ExtensibleJsonFormat {
#[must_use]
pub(crate) fn new() -> Self {
Self::default()
}
}
impl<T> ExtensibleJsonFormat<T> {
#[allow(dead_code)]
pub(crate) fn with_timer<T2>(self, timer: T2) -> ExtensibleJsonFormat<T2> {
ExtensibleJsonFormat {
timer,
display_timestamp: self.display_timestamp,
display_target: self.display_target,
display_level: self.display_level,
display_thread_id: self.display_thread_id,
display_thread_name: self.display_thread_name,
display_filename: self.display_filename,
display_line_number: self.display_line_number,
flatten_event: self.flatten_event,
display_current_span: self.display_current_span,
static_fields: self.static_fields,
key_names: self.key_names,
}
}
#[allow(dead_code)]
pub(crate) fn without_time(self) -> ExtensibleJsonFormat<()> {
ExtensibleJsonFormat {
timer: (),
display_timestamp: false,
display_target: self.display_target,
display_level: self.display_level,
display_thread_id: self.display_thread_id,
display_thread_name: self.display_thread_name,
display_filename: self.display_filename,
display_line_number: self.display_line_number,
flatten_event: self.flatten_event,
display_current_span: self.display_current_span,
static_fields: self.static_fields,
key_names: self.key_names,
}
}
pub(crate) fn with_target(self, display_target: bool) -> Self {
Self {
display_target,
..self
}
}
pub(crate) fn with_level(self, display_level: bool) -> Self {
Self {
display_level,
..self
}
}
pub(crate) fn with_thread_ids(self, display_thread_id: bool) -> Self {
Self {
display_thread_id,
..self
}
}
pub(crate) fn with_thread_names(self, display_thread_name: bool) -> Self {
Self {
display_thread_name,
..self
}
}
pub(crate) fn with_file(self, display_filename: bool) -> Self {
Self {
display_filename,
..self
}
}
pub(crate) fn with_line_number(self, display_line_number: bool) -> Self {
Self {
display_line_number,
..self
}
}
pub(crate) fn flatten_event(self, flatten_event: bool) -> Self {
Self {
flatten_event,
..self
}
}
pub(crate) fn with_current_span(self, display_current_span: bool) -> Self {
Self {
display_current_span,
..self
}
}
pub(crate) fn with_static_fields(
self,
mut static_fields: BTreeMap<String, Value>,
parse_env: bool,
) -> Self {
if parse_env {
let expand_env_vars = |input: &str| -> Value {
ENV_VAR_REGEX
.replace_all(input, |caps: ®ex::Captures<'_>| {
let var_name = &caps[1];
env::var(var_name).unwrap_or_else(|_| format!("${}", var_name))
})
.into()
};
for val in static_fields.values_mut() {
if let Some(value) = val.as_str() {
*val = expand_env_vars(value);
}
}
}
Self {
static_fields,
..self
}
}
pub(crate) fn with_key_names(self, key_names: JsonKeyNames) -> Self {
Self { key_names, ..self }
}
}
struct SerializableSpan<'a, 'b, Span, N>(&'b SpanRef<'a, Span>, PhantomData<N>)
where
Span: for<'lookup> LookupSpan<'lookup>,
N: for<'writer> FormatFields<'writer> + 'static;
impl<Span, N> Serialize for SerializableSpan<'_, '_, Span, N>
where
Span: for<'lookup> LookupSpan<'lookup>,
N: for<'writer> FormatFields<'writer> + 'static,
{
fn serialize<Ser>(&self, serializer: Ser) -> Result<Ser::Ok, Ser::Error>
where
Ser: Serializer,
{
let mut serializer = serializer.serialize_map(None)?;
let ext = self.0.extensions();
let data = ext
.get::<FormattedFields<N>>()
.expect("Unable to find FormattedFields in extensions; this is a bug");
match serde_json::from_str::<Value>(data) {
Ok(Value::Object(fields)) => {
for field in fields {
serializer.serialize_entry(&field.0, &field.1)?;
}
}
Ok(_) if cfg!(debug_assertions) => panic!(
"span '{}' had malformed fields! this is a bug.\n error: invalid JSON object\n fields: {:?}",
self.0.metadata().name(),
data
),
Ok(value) => {
serializer.serialize_entry("field", &value)?;
serializer.serialize_entry("field_error", "field was no a valid object")?
}
Err(e) if cfg!(debug_assertions) => panic!(
"span '{}' had malformed fields! this is a bug.\n error: {}\n fields: {:?}",
self.0.metadata().name(),
e,
data
),
Err(e) => serializer.serialize_entry("field_error", &format!("{e}"))?,
};
serializer.serialize_entry("name", self.0.metadata().name())?;
serializer.end()
}
}
pub(crate) struct JsonWriter<'a> {
fmt_write: &'a mut dyn fmt::Write,
}
impl<'a> JsonWriter<'a> {
pub(crate) fn new(fmt_write: &'a mut dyn fmt::Write) -> Self {
Self { fmt_write }
}
}
impl io::Write for JsonWriter<'_> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let s =
std::str::from_utf8(buf).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
self.fmt_write.write_str(s).map_err(io::Error::other)?;
Ok(s.len())
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}