use crate::observability::config::{LogFormat, LogLocalConfig};
use crate::observability::filter::SharedOrderedFilter;
use chrono::Utc;
use chrono_tz::Tz;
use std::fmt::{self, Write};
use tracing::{Event, Subscriber};
use tracing_subscriber::{
fmt::{
format::{FormatEvent, FormatFields, Writer},
time::FormatTime,
FmtContext,
},
registry::LookupSpan,
Layer, Registry,
};
#[derive(Clone, Copy)]
struct TimezoneTimer(Tz);
struct ProcessContextFormat<E> {
inner: E,
pid: u32,
}
impl<E> ProcessContextFormat<E> {
fn new(inner: E) -> Self {
Self {
inner,
pid: std::process::id(),
}
}
}
impl<S, N, E> FormatEvent<S, N> for ProcessContextFormat<E>
where
S: Subscriber + for<'lookup> LookupSpan<'lookup>,
N: for<'writer> FormatFields<'writer> + 'static,
E: FormatEvent<S, N>,
{
fn format_event(
&self,
ctx: &FmtContext<'_, S, N>,
mut writer: Writer<'_>,
event: &Event<'_>,
) -> fmt::Result {
let mut rendered = String::new();
self.inner
.format_event(ctx, Writer::new(&mut rendered), event)?;
let thread = std::thread::current();
let thread_name = thread.name().unwrap_or("unnamed");
let process_context = format!(
"process.pid={} thread.name={thread_name:?} thread.id={:?} ",
self.pid,
thread.id()
);
if let Some(index) = rendered.rfind("log.target") {
rendered.insert_str(index, &process_context);
} else if let Some(index) = rendered.rfind('\n') {
rendered.insert_str(index, &format!(" {process_context}"));
} else {
rendered.push(' ');
rendered.push_str(&process_context);
}
writer.write_str(&rendered)
}
}
impl FormatTime for TimezoneTimer {
fn format_time(&self, writer: &mut Writer<'_>) -> fmt::Result {
write!(writer, "{}", Utc::now().with_timezone(&self.0).to_rfc3339())
}
}
pub fn build_console_layer(
format: LogFormat,
timezone: Tz,
include_process_id: bool,
filter: SharedOrderedFilter,
) -> Box<dyn Layer<Registry> + Send + Sync> {
let base = tracing_subscriber::fmt::layer()
.with_timer(TimezoneTimer(timezone))
.with_target(true)
.with_file(true)
.with_line_number(true)
.with_thread_ids(true)
.with_thread_names(true)
.with_writer(std::io::stdout);
match (format, include_process_id) {
(LogFormat::Json, _) => base
.json()
.with_current_span(true)
.with_span_list(true)
.with_filter(filter)
.boxed(),
(LogFormat::Compact, false) => base.compact().with_filter(filter).boxed(),
(LogFormat::Full, false) => base.with_filter(filter).boxed(),
(LogFormat::Pretty, false) => base.pretty().with_filter(filter).boxed(),
(LogFormat::Compact, true) => base
.event_format(ProcessContextFormat::new(
tracing_subscriber::fmt::format()
.with_timer(TimezoneTimer(timezone))
.with_thread_ids(false)
.with_thread_names(false)
.compact(),
))
.with_filter(filter)
.boxed(),
(LogFormat::Full, true) => base
.event_format(ProcessContextFormat::new(
tracing_subscriber::fmt::format()
.with_timer(TimezoneTimer(timezone))
.with_thread_ids(false)
.with_thread_names(false),
))
.with_filter(filter)
.boxed(),
(LogFormat::Pretty, true) => base
.event_format(ProcessContextFormat::new(
tracing_subscriber::fmt::format()
.with_timer(TimezoneTimer(timezone))
.with_thread_ids(false)
.with_thread_names(false)
.pretty(),
))
.with_filter(filter)
.boxed(),
}
}
pub fn build_local_layer(
config: &LogLocalConfig,
format: LogFormat,
timezone: Tz,
include_process_id: bool,
filter: SharedOrderedFilter,
) -> (
Box<dyn Layer<Registry> + Send + Sync>,
tracing_appender::non_blocking::WorkerGuard,
) {
let file_appender = tracing_appender::rolling::daily(&config.file_dir, &config.file_name);
let (writer, guard) = tracing_appender::non_blocking(file_appender);
let base = tracing_subscriber::fmt::layer()
.with_timer(TimezoneTimer(timezone))
.with_target(true)
.with_file(true)
.with_line_number(true)
.with_thread_ids(true)
.with_thread_names(true)
.with_writer(writer);
let layer = match (format, include_process_id) {
(LogFormat::Json, _) => base
.json()
.with_current_span(true)
.with_span_list(true)
.with_filter(filter)
.boxed(),
(LogFormat::Compact, false) => base.compact().with_filter(filter).boxed(),
(LogFormat::Full, false) => base.with_filter(filter).boxed(),
(LogFormat::Pretty, false) => base.pretty().with_filter(filter).boxed(),
(LogFormat::Compact, true) => base
.event_format(ProcessContextFormat::new(
tracing_subscriber::fmt::format()
.with_timer(TimezoneTimer(timezone))
.with_thread_ids(false)
.with_thread_names(false)
.compact(),
))
.with_filter(filter)
.boxed(),
(LogFormat::Full, true) => base
.event_format(ProcessContextFormat::new(
tracing_subscriber::fmt::format()
.with_timer(TimezoneTimer(timezone))
.with_thread_ids(false)
.with_thread_names(false),
))
.with_filter(filter)
.boxed(),
(LogFormat::Pretty, true) => base
.event_format(ProcessContextFormat::new(
tracing_subscriber::fmt::format()
.with_timer(TimezoneTimer(timezone))
.with_thread_ids(false)
.with_thread_names(false)
.pretty(),
))
.with_filter(filter)
.boxed(),
};
(layer, guard)
}