1use std::sync::LazyLock;
8
9use console::{Color, Style};
10use opentelemetry::{TraceId, trace::TraceContextExt as _};
11use tracing::{Level, Subscriber};
12use tracing_opentelemetry::get_otel_context;
13use tracing_subscriber::{
14 fmt::{
15 FormatEvent, FormatFields,
16 format::{DefaultFields, Writer},
17 time::{ChronoLocal, FormatTime},
18 },
19 registry::LookupSpan,
20};
21
22use crate::LogContext;
23
24static TIMER: LazyLock<ChronoLocal> =
25 LazyLock::new(|| ChronoLocal::new("%Y-%m-%dT%H:%M:%S%.6f%:z".to_owned()));
26
27#[derive(Debug, Default)]
30pub struct EventFormatter;
31
32struct FmtLevel<'a> {
33 level: &'a Level,
34 ansi: bool,
35}
36
37impl<'a> FmtLevel<'a> {
38 pub(crate) fn new(level: &'a Level, ansi: bool) -> Self {
39 Self { level, ansi }
40 }
41}
42
43const TRACE_STR: &str = "TRACE";
44const DEBUG_STR: &str = "DEBUG";
45const INFO_STR: &str = " INFO";
46const WARN_STR: &str = " WARN";
47const ERROR_STR: &str = "ERROR";
48
49const TRACE_STYLE: Style = Style::new().fg(Color::Magenta);
50const DEBUG_STYLE: Style = Style::new().fg(Color::Blue);
51const INFO_STYLE: Style = Style::new().fg(Color::Green);
52const WARN_STYLE: Style = Style::new().fg(Color::Yellow);
53const ERROR_STYLE: Style = Style::new().fg(Color::Red);
54
55impl std::fmt::Display for FmtLevel<'_> {
56 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
57 let msg = match *self.level {
58 Level::TRACE => TRACE_STYLE.force_styling(self.ansi).apply_to(TRACE_STR),
59 Level::DEBUG => DEBUG_STYLE.force_styling(self.ansi).apply_to(DEBUG_STR),
60 Level::INFO => INFO_STYLE.force_styling(self.ansi).apply_to(INFO_STR),
61 Level::WARN => WARN_STYLE.force_styling(self.ansi).apply_to(WARN_STR),
62 Level::ERROR => ERROR_STYLE.force_styling(self.ansi).apply_to(ERROR_STR),
63 };
64 write!(f, "{msg}")
65 }
66}
67
68struct TargetFmt<'a> {
69 target: &'a str,
70 line: Option<u32>,
71}
72
73impl<'a> TargetFmt<'a> {
74 pub(crate) fn new(metadata: &tracing::Metadata<'a>) -> Self {
75 Self {
76 target: metadata.target(),
77 line: metadata.line(),
78 }
79 }
80}
81
82impl std::fmt::Display for TargetFmt<'_> {
83 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
84 write!(f, "{}", self.target)?;
85 if let Some(line) = self.line {
86 write!(f, ":{line}")?;
87 }
88 Ok(())
89 }
90}
91
92impl<S, N> FormatEvent<S, N> for EventFormatter
93where
94 S: Subscriber + for<'a> LookupSpan<'a>,
95 N: for<'writer> FormatFields<'writer> + 'static,
96{
97 fn format_event(
98 &self,
99 ctx: &tracing_subscriber::fmt::FmtContext<'_, S, N>,
100 mut writer: Writer<'_>,
101 event: &tracing::Event<'_>,
102 ) -> std::fmt::Result {
103 let ansi = writer.has_ansi_escapes();
104 let metadata = event.metadata();
105
106 TIMER.format_time(&mut writer)?;
107
108 let level = FmtLevel::new(metadata.level(), ansi);
109 write!(&mut writer, " {level} ")?;
110
111 let style = Style::new().dim().force_styling(ansi);
116 if metadata.name().starts_with("event ") {
117 write!(&mut writer, "{} ", style.apply_to(TargetFmt::new(metadata)))?;
118 } else {
119 write!(&mut writer, "{} ", style.apply_to(metadata.name()))?;
120 }
121
122 LogContext::maybe_with(|log_context| {
123 let log_context = Style::new()
124 .bold()
125 .force_styling(ansi)
126 .apply_to(log_context);
127 write!(&mut writer, "{log_context} - ")
128 })
129 .transpose()?;
130
131 let field_fromatter = DefaultFields::new();
132 field_fromatter.format_fields(writer.by_ref(), event)?;
133
134 if let Some(span) = ctx.lookup_current()
136 && let Some(trace_id) = tracing::dispatcher::get_default(|dispatch| {
137 let otel_cx = get_otel_context(&span.id(), dispatch)?;
138 let trace_id = otel_cx.span().span_context().trace_id();
139 Some(trace_id)
140 })
141 && trace_id != TraceId::INVALID
142 {
143 let label = Style::new()
144 .italic()
145 .force_styling(ansi)
146 .apply_to("trace.id");
147 write!(&mut writer, " {label}={trace_id}")?;
148 }
149
150 writeln!(&mut writer)
151 }
152}