tracing_tree/
time.rs

1use std::{fmt::Write, time::Duration};
2
3use nu_ansi_term::Style;
4
5use crate::styled;
6
7/// A type that can measure and format the current time.
8///
9/// This trait is used by [HierarchicalLayer] to include a timestamp with each
10/// [Event] when it is logged.
11///
12/// Notable default implementations of this trait are [LocalDateTime] and `()`.
13/// The former prints the current time as reported by [time's OffsetDateTime]
14/// (note that it requires a `time` feature to be enabled and may panic!
15/// make sure to check out the docs for the [LocalDateTime]),
16/// and the latter does not print the current time at all.
17///
18/// Inspired by the [FormatTime] trait from [tracing-subscriber].
19///
20/// [HierarchicalLayer]: crate::HierarchicalLayer
21/// [Event]: tracing_core::Event
22/// [time's OffsetDateTime]: time::OffsetDateTime
23/// [FormatTime]: tracing_subscriber::fmt::time::FormatTime
24/// [tracing-subscriber]: tracing_subscriber
25// NB:
26//   We can't use `tracing_subscriber::fmt::format::Writer`
27//   since it doesn't have a public constructor.
28pub trait FormatTime {
29    fn format_time(&self, w: &mut impl std::fmt::Write) -> std::fmt::Result;
30    fn style_timestamp(
31        &self,
32        ansi: bool,
33        elapsed: Duration,
34        w: &mut impl std::fmt::Write,
35    ) -> std::fmt::Result;
36}
37
38////////////////////////////////////////////////////////////////////////////////////////////////////
39
40/// Default do-nothing time formatter.
41impl FormatTime for () {
42    fn format_time(&self, _w: &mut impl std::fmt::Write) -> std::fmt::Result {
43        Ok(())
44    }
45    fn style_timestamp(
46        &self,
47        _ansi: bool,
48        _elapsed: Duration,
49        _w: &mut impl std::fmt::Write,
50    ) -> std::fmt::Result {
51        Ok(())
52    }
53}
54
55////////////////////////////////////////////////////////////////////////////////////////////////////
56
57/// Retrieve and print the current wall-clock time in UTC timezone.
58#[cfg(feature = "time")]
59#[derive(Debug, Clone, Copy, Eq, PartialEq, Default)]
60pub struct UtcDateTime {
61    /// Whether to print the time with higher precision.
62    pub higher_precision: bool,
63}
64
65#[cfg(feature = "time")]
66impl FormatTime for UtcDateTime {
67    fn format_time(&self, w: &mut impl std::fmt::Write) -> std::fmt::Result {
68        let time = time::OffsetDateTime::now_utc();
69        write!(w, "{} {}", time.date(), time.time())
70    }
71
72    fn style_timestamp(
73        &self,
74        ansi: bool,
75        elapsed: Duration,
76        w: &mut impl std::fmt::Write,
77    ) -> std::fmt::Result {
78        style_timestamp(ansi, self.higher_precision, elapsed, w)
79    }
80}
81
82////////////////////////////////////////////////////////////////////////////////////////////////////
83
84/// Retrieve and print the current wall-clock time.
85///
86/// # Panics
87///
88/// Panics if [time crate] cannot determine the local UTC offset.
89///
90/// [time crate]: time
91// NB:
92//   Can't use `tracing_subscriber::fmt::time::SystemTime` since it uses
93//   private `datetime` module to format the actual time.
94#[cfg(feature = "time")]
95#[derive(Debug, Clone, Copy, Eq, PartialEq, Default)]
96pub struct LocalDateTime {
97    /// Whether to print the time with higher precision.
98    pub higher_precision: bool,
99}
100
101#[cfg(feature = "time")]
102impl FormatTime for LocalDateTime {
103    fn format_time(&self, w: &mut impl std::fmt::Write) -> std::fmt::Result {
104        let time = time::OffsetDateTime::now_local().expect("time offset cannot be determined");
105        write!(w, "{}", time)
106    }
107    fn style_timestamp(
108        &self,
109        ansi: bool,
110        elapsed: Duration,
111        w: &mut impl std::fmt::Write,
112    ) -> std::fmt::Result {
113        style_timestamp(ansi, self.higher_precision, elapsed, w)
114    }
115}
116
117////////////////////////////////////////////////////////////////////////////////////////////////////
118
119/// Retrieve and print the relative elapsed wall-clock time since an epoch.
120///
121/// The `Default` implementation for `Uptime` makes the epoch the current time.
122// NB: Copy-pasted from `tracing-subscriber::fmt::time::Uptime`.
123#[derive(Debug, Clone, Copy, Eq, PartialEq)]
124pub struct Uptime {
125    epoch: std::time::Instant,
126    /// Whether to print the time with higher precision.
127    pub higher_precision: bool,
128}
129
130impl Default for Uptime {
131    fn default() -> Self {
132        Uptime::from(std::time::Instant::now())
133    }
134}
135
136impl From<std::time::Instant> for Uptime {
137    fn from(epoch: std::time::Instant) -> Self {
138        Uptime {
139            epoch,
140            higher_precision: false,
141        }
142    }
143}
144
145impl FormatTime for Uptime {
146    fn format_time(&self, w: &mut impl std::fmt::Write) -> std::fmt::Result {
147        let e = self.epoch.elapsed();
148        write!(w, "{:4}.{:06}s", e.as_secs(), e.subsec_micros())
149    }
150    fn style_timestamp(
151        &self,
152        ansi: bool,
153        elapsed: Duration,
154        w: &mut impl std::fmt::Write,
155    ) -> std::fmt::Result {
156        style_timestamp(ansi, self.higher_precision, elapsed, w)
157    }
158}
159
160fn style_timestamp(
161    ansi: bool,
162    higher_precision: bool,
163    elapsed: Duration,
164    w: &mut impl Write,
165) -> std::fmt::Result {
166    if higher_precision {
167        format_timestamp_with_decimals(ansi, elapsed, w)
168    } else {
169        format_timestamp(ansi, elapsed, w)
170    }
171}
172
173fn format_timestamp(ansi: bool, elapsed: Duration, w: &mut impl Write) -> std::fmt::Result {
174    let millis = elapsed.as_millis();
175    let secs = elapsed.as_secs();
176
177    // Convert elapsed time to appropriate units: ms, s, or m.
178    // - Less than 1s : use ms
179    // - Less than 1m : use s
180    // - 1m and above : use m
181    let (n, unit) = if millis < 1000 {
182        (millis as _, "ms")
183    } else if secs < 60 {
184        (secs, "s ")
185    } else {
186        (secs / 60, "m ")
187    };
188
189    let timestamp = format!("{n:>3}");
190    write_style_timestamp(ansi, timestamp, unit, w)
191}
192
193fn format_timestamp_with_decimals(
194    ansi: bool,
195    elapsed: Duration,
196    w: &mut impl Write,
197) -> std::fmt::Result {
198    let secs = elapsed.as_secs_f64();
199
200    // Convert elapsed time to appropriate units: μs, ms, or s.
201    // - Less than 1ms: use μs
202    // - Less than 1s : use ms
203    // - 1s and above : use s
204    let (n, unit) = if secs < 0.001 {
205        (secs * 1_000_000.0, "μs")
206    } else if secs < 1.0 {
207        (secs * 1_000.0, "ms")
208    } else {
209        (secs, "s ")
210    };
211
212    let timestamp = format!(" {n:.2}");
213    write_style_timestamp(ansi, timestamp, unit, w)
214}
215
216fn write_style_timestamp(
217    ansi: bool,
218    timestamp: String,
219    unit: &str,
220    w: &mut impl Write,
221) -> std::fmt::Result {
222    write!(
223        w,
224        "{timestamp}{unit}",
225        timestamp = styled(ansi, Style::new().dimmed(), timestamp),
226        unit = styled(ansi, Style::new().dimmed(), unit),
227    )
228}
229
230////////////////////////////////////////////////////////////////////////////////////////////////////
231
232impl<'a, F> FormatTime for &'a F
233where
234    F: FormatTime,
235{
236    fn format_time(&self, w: &mut impl std::fmt::Write) -> std::fmt::Result {
237        F::format_time(self, w)
238    }
239    fn style_timestamp(
240        &self,
241        ansi: bool,
242        duration: Duration,
243        w: &mut impl std::fmt::Write,
244    ) -> std::fmt::Result {
245        F::style_timestamp(self, ansi, duration, w)
246    }
247}
248
249// NB:
250//   Can't impl for `fn(&mut impl std::fmt::Write)` since impl trait is not allowed
251//   outside of function and inherent method return types for now.