starbase/tracing/
format.rs

1use 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        // if TEST_ENV.load(Ordering::Relaxed) {
64        //     return write!(writer, "YYYY-MM-DD");
65        // }
66
67        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        // [level timestamp]
101        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        // target:spans...
119        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        // message ...field=value
136        ctx.format_fields(writer.by_ref(), event)?;
137
138        // spans(vars=values)...
139        // if let Some(scope) = ctx.event_scope() {
140        //     for span in scope.from_root() {
141        //         let ext = span.extensions();
142
143        //         if let Some(fields) = &ext.get::<FormattedFields<N>>() {
144        //             write!(
145        //                 writer,
146        //                 " {}{}{}{}",
147        //                 color::muted_light(span.name()),
148        //                 color::muted_light("("),
149        //                 fields,
150        //                 color::muted_light(")"),
151        //             )?;
152        //         } else {
153        //             write!(writer, " {}", color::muted_light(span.name()))?;
154        //         }
155        //     }
156        // }
157
158        writeln!(writer)
159    }
160}