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