hydro_lang/
telemetry.rs

1//! # Telemetry
2use tracing::Subscriber;
3use tracing_subscriber::EnvFilter;
4use tracing_subscriber::fmt::{FormatEvent, FormatFields, FormattedFields};
5use tracing_subscriber::registry::LookupSpan;
6
7#[expect(
8    missing_docs,
9    reason = "This is internal code. This struct needs to be pub for some reason for the Formatter impl to work in staged code?"
10)]
11pub struct Formatter;
12
13impl<S, N> FormatEvent<S, N> for Formatter
14where
15    S: Subscriber + for<'a> LookupSpan<'a>,
16    N: for<'a> FormatFields<'a> + 'static,
17{
18    fn format_event(
19        &self,
20        ctx: &tracing_subscriber::fmt::FmtContext<'_, S, N>,
21        mut writer: tracing_subscriber::fmt::format::Writer<'_>,
22        event: &tracing::Event<'_>,
23    ) -> std::fmt::Result {
24        use colored::Colorize;
25
26        let metadata = event.metadata();
27
28        if writer.has_ansi_escapes() {
29            write!(
30                &mut writer,
31                "{} {} {}{} {} {}:{}: ",
32                chrono::Utc::now()
33                    .format("%Y-%m-%dT%H:%M:%S%.f%:z")
34                    .to_string()
35                    .magenta()
36                    .underline()
37                    .on_white(),
38                metadata.level().as_str().red(),
39                std::thread::current()
40                    .name()
41                    .unwrap_or("unnamed-thread")
42                    .blue(),
43                format!("({:?})", std::thread::current().id()).blue(),
44                // gettid::gettid(), TODO: can't get gettid to link properly.
45                metadata.target().green(),
46                metadata.file().unwrap_or("unknown-file").red(),
47                format!("{}", metadata.line().unwrap_or(0)).red(),
48            )?;
49        } else {
50            write!(
51                &mut writer,
52                "{} {} {}{:?} {} {}:{}: ",
53                chrono::Utc::now().format("%Y-%m-%dT%H:%M:%S%.f%:z"),
54                metadata.level().as_str().red(),
55                std::thread::current().name().unwrap_or("unnamed-thread"),
56                std::thread::current().id(),
57                // gettid::gettid(), TODO: can't get gettid to link properly.
58                metadata.target(),
59                metadata.file().unwrap_or("unknown-file"),
60                metadata.line().unwrap_or(0),
61            )?;
62        }
63
64        if let Some(scope) = ctx.event_scope() {
65            for span in scope.from_root() {
66                if writer.has_ansi_escapes() {
67                    write!(writer, "{}", span.name().purple())?;
68                } else {
69                    write!(writer, "{}", span.name())?;
70                }
71
72                let ext = span.extensions();
73                let fields = &ext.get::<FormattedFields<N>>().unwrap();
74
75                if !fields.is_empty() {
76                    if writer.has_ansi_escapes() {
77                        write!(writer, "{{{}}}", fields.cyan())?;
78                    } else {
79                        write!(writer, "{{{}}}", fields)?;
80                    }
81                }
82
83                write!(writer, ": ")?;
84            }
85        }
86
87        if writer.has_ansi_escapes() {
88            write!(writer, "{}: ", metadata.name().yellow().bold().underline())?;
89        } else {
90            write!(writer, "{}: ", metadata.name())?;
91        }
92
93        ctx.field_format().format_fields(writer.by_ref(), event)?;
94
95        writeln!(writer)
96    }
97}
98
99/// Initialize tracing using the above custom formatter with the default directive level of "ERROR", if RUST_LOG is not set.
100pub fn initialize_tracing() {
101    let rust_log = std::env::var("RUST_LOG").unwrap_or_else(|err| {
102        match err {
103            std::env::VarError::NotPresent => {
104                // RUST_LOG not set, the user wants the default.
105                "error".to_string()
106            }
107            std::env::VarError::NotUnicode(v) => {
108                // Almost certainly there is a configuration issue.
109                eprintln!(
110                    "RUST_LOG is not unicode, defaulting to 'error' directive: {:?}",
111                    v
112                );
113                "error".to_string()
114            }
115        }
116    });
117
118    let filter = EnvFilter::try_new(&rust_log).unwrap_or_else(|err| {
119        // Configuration error.
120        eprintln!("Failed to parse RUST_LOG: {}, err: {:?}", rust_log, err);
121        "error".to_string().parse().unwrap()
122    });
123
124    initialize_tracing_with_filter(filter)
125}
126
127/// Initialize tracing using the above custom formatter, using the tracing directive.
128/// something like "{level},{abc}={level},{xyz}={level}" where {level} is one of "tracing,debug,info,warn,error"
129pub fn initialize_tracing_with_filter(filter: EnvFilter) {
130    use tracing::subscriber::set_global_default;
131    use tracing_subscriber::fmt::format::FmtSpan;
132    use tracing_subscriber::prelude::*;
133    use tracing_subscriber::{Layer, fmt, registry};
134
135    set_global_default(
136        registry().with(
137            fmt::layer()
138                .with_span_events(FmtSpan::NEW | FmtSpan::CLOSE)
139                .event_format(Formatter)
140                .with_filter(filter.clone()),
141        ),
142    )
143    .unwrap();
144
145    #[expect(
146        non_snake_case,
147        reason = "this variable represents an env var which is in all caps"
148    )]
149    let RUST_LOG = std::env::var("RUST_LOG");
150
151    tracing::trace!(name: "Tracing Initialized", ?RUST_LOG, ?filter);
152}