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