starbase/tracing/
format.rs1use chrono::{Local, Timelike};
2use starbase_styles::{apply_style_tags, color};
3use std::sync::atomic::{AtomicBool, AtomicU8, Ordering};
4use tracing::{Level, Metadata, Subscriber, field::Visit, metadata::LevelFilter};
5use tracing_subscriber::{
6 field::RecordFields,
7 fmt::{self, FormatEvent, FormatFields, time::FormatTime},
8 registry::LookupSpan,
9};
10
11pub static LAST_HOUR: AtomicU8 = AtomicU8::new(0);
12pub static TEST_ENV: AtomicBool = AtomicBool::new(false);
13
14struct FieldVisitor<'writer> {
15 writer: fmt::format::Writer<'writer>,
16}
17
18impl Visit for FieldVisitor<'_> {
19 fn record_str(&mut self, field: &tracing::field::Field, value: &str) {
20 if field.name() == "message" {
21 self.record_debug(field, &format_args!("{value}"))
22 } else {
23 self.record_debug(field, &value)
24 }
25 }
26
27 fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) {
28 if field.name() == "message" {
29 write!(self.writer, " {} ", apply_style_tags(format!("{value:?}"))).unwrap()
30 } else {
31 write!(
32 self.writer,
33 " {}",
34 color::muted(format!("{}={:?}", field.name(), value))
35 )
36 .unwrap()
37 }
38 }
39}
40
41pub struct FieldFormatter;
42
43impl<'writer> FormatFields<'writer> for FieldFormatter {
44 fn format_fields<R: RecordFields>(
45 &self,
46 writer: fmt::format::Writer<'writer>,
47 fields: R,
48 ) -> std::fmt::Result {
49 let mut visitor = FieldVisitor { writer };
50
51 fields.record(&mut visitor);
52
53 Ok(())
54 }
55}
56
57pub struct EventFormatter {
58 pub show_spans: bool,
59}
60
61impl FormatTime for EventFormatter {
62 fn format_time(&self, writer: &mut fmt::format::Writer<'_>) -> std::fmt::Result {
63 let mut date_format = "%Y-%m-%d %H:%M:%S%.3f";
68 let current_timestamp = Local::now();
69 let current_hour = current_timestamp.hour() as u8;
70
71 if current_hour == LAST_HOUR.load(Ordering::Acquire) {
72 date_format = "%H:%M:%S%.3f";
73 } else {
74 LAST_HOUR.store(current_hour, Ordering::Release);
75 }
76
77 write!(
78 writer,
79 "{}",
80 color::muted(current_timestamp.format(date_format).to_string()),
81 )
82 }
83}
84
85impl<S, N> FormatEvent<S, N> for EventFormatter
86where
87 S: Subscriber + for<'a> LookupSpan<'a>,
88 N: for<'a> FormatFields<'a> + 'static,
89{
90 fn format_event(
91 &self,
92 ctx: &fmt::FmtContext<'_, S, N>,
93 mut writer: fmt::format::Writer<'_>,
94 event: &tracing::Event<'_>,
95 ) -> std::fmt::Result {
96 let meta: &Metadata = event.metadata();
97 let level: &Level = meta.level();
98 let level_label = format!("{: >5}", level.as_str());
99
100 write!(writer, "{}", color::muted("["))?;
102 write!(
103 writer,
104 "{} ",
105 if *level == LevelFilter::ERROR {
106 color::failure(level_label)
107 } else if *level == LevelFilter::WARN {
108 color::invalid(level_label)
109 } else {
110 color::muted(level_label)
111 }
112 )?;
113
114 self.format_time(&mut writer)?;
115
116 write!(writer, "{}", color::muted("]"))?;
117
118 write!(writer, " {}", color::log_target(meta.target()))?;
120
121 if self.show_spans {
122 write!(writer, " ")?;
123
124 if let Some(scope) = ctx.event_scope() {
125 for span in scope.from_root() {
126 if span.parent().is_some() {
127 write!(writer, "{}", color::muted(":"))?;
128 }
129
130 write!(writer, "{}", color::muted_light(span.name()))?;
131 }
132 }
133 }
134
135 ctx.format_fields(writer.by_ref(), event)?;
137
138 writeln!(writer)
159 }
160}