1use parking_lot::Once;
7use std::time::Instant;
8use tracing_subscriber::{
9 fmt::{MakeWriter, time::SystemTime},
10 util::SubscriberInitExt,
11};
12
13use tracing::level_filters::LevelFilter;
14use tracing_subscriber::{
15 EnvFilter,
16 fmt::{
17 format::{FmtSpan, Writer},
18 time::FormatTime,
19 },
20};
21
22pub fn running_in_antithesis() -> bool {
24 std::env::var("ANTITHESIS_OUTPUT_DIR").is_ok()
25}
26
27#[derive(PartialEq, Eq)]
29pub enum TracingConsumer {
30 Test,
32 Server,
34 Tool,
36}
37
38pub fn init_tracing(consumer: TracingConsumer, process_id: Option<String>) {
40 init_tracing_with_writer(consumer, process_id, std::io::stdout);
41}
42
43pub fn init_tracing_with_writer<W>(consumer: TracingConsumer, process_id: Option<String>, writer: W)
53where
54 W: for<'writer> MakeWriter<'writer> + 'static + Send + Sync,
55{
56 static INIT: Once = Once::new();
57 INIT.call_once(move || {
58 let process_id = process_id
59 .unwrap_or_else(|| bs58::encode(rand::random::<u64>().to_le_bytes()).into_string());
60
61 let antithesis = running_in_antithesis();
62 let testing = consumer == TracingConsumer::Test;
63 let color = !antithesis && !std::env::var("NO_COLOR").is_ok_and(|s| !s.is_empty());
64
65 let default_level = if consumer == TracingConsumer::Tool {
66 LevelFilter::WARN
67 } else {
68 LevelFilter::INFO
69 };
70
71 let mut filter = EnvFilter::builder()
72 .with_default_directive(default_level.into())
73 .from_env()
74 .unwrap();
75
76 let mut span_events = FmtSpan::NONE;
77
78 if antithesis || testing {
79 span_events = FmtSpan::NEW | FmtSpan::CLOSE;
80 filter = filter
81 .add_directive("graft_client=trace".parse().unwrap())
82 .add_directive("graft_core=trace".parse().unwrap())
83 .add_directive("graft_server=trace".parse().unwrap())
84 .add_directive("graft_test=trace".parse().unwrap())
85 .add_directive("graft_sqlite=debug".parse().unwrap())
86 }
87
88 let prefix = if antithesis || testing {
89 Some(process_id.clone())
90 } else {
91 None
92 };
93
94 let time = if antithesis {
95 TimeFormat::None
96 } else if consumer == TracingConsumer::Server {
97 TimeFormat::Long(SystemTime)
98 } else {
99 TimeFormat::Offset { start: Instant::now() }
100 };
101
102 tracing_subscriber::fmt()
103 .with_env_filter(filter)
104 .with_thread_names(true)
105 .with_span_events(span_events)
106 .with_ansi(color)
107 .with_timer(TimeAndPrefix::new(prefix, time))
108 .with_writer(writer)
109 .finish()
110 .try_init()
111 .expect("failed to setup tracing subscriber");
112 });
113}
114
115enum TimeFormat {
116 None,
117 Long(SystemTime),
118 Offset { start: Instant },
119}
120
121struct TimeAndPrefix {
122 prefix: Option<String>,
123 time: TimeFormat,
124}
125
126impl TimeAndPrefix {
127 fn new(prefix: Option<String>, time: TimeFormat) -> Self {
128 Self { prefix, time }
129 }
130 fn write_time(&self, w: &mut Writer<'_>) -> std::fmt::Result {
131 match self.time {
132 TimeFormat::None => Ok(()),
133 TimeFormat::Long(inner) => inner.format_time(w),
134 TimeFormat::Offset { start } => {
135 let e = start.elapsed();
136 let nanos = e.subsec_nanos();
137 let millis = (nanos as f64 / 1_000_000.0).round();
139 write!(w, "{:03}.{:03}s", e.as_secs(), millis)
140 }
141 }
142 }
143}
144
145impl FormatTime for TimeAndPrefix {
146 fn format_time(&self, w: &mut Writer<'_>) -> std::fmt::Result {
147 match (&self.prefix, &self.time) {
148 (None, _) => self.write_time(w),
149 (Some(prefix), TimeFormat::None) => write!(w, "{prefix}"),
150 (Some(prefix), _) => {
151 write!(w, "{prefix} ")?;
152 self.write_time(w)
153 }
154 }
155 }
156}