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