Skip to content

Commit 77c21b8

Browse files
authored
Merge pull request #56 from TmLev/main
Format and print time at which event has occurred
2 parents d398164 + 4399399 commit 77c21b8

File tree

4 files changed

+237
-4
lines changed

4 files changed

+237
-4
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ tracing-subscriber = { version = "0.3", default-features = false, features = ["r
1414
nu-ansi-term = "0.46.0"
1515
atty = "0.2"
1616
tracing-log = { version = "0.1", optional = true }
17+
time = { version = "0.3.20", optional = true, features = ["formatting", "local-offset"] }
1718

1819
[features]
1920
default = ["tracing-log"]

src/lib.rs

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
pub(crate) mod format;
2+
pub mod time;
23

4+
use crate::time::FormatTime;
35
use format::{Buffers, ColorLevel, Config, FmtEvent, SpanMode};
6+
47
use nu_ansi_term::{Color, Style};
58
use std::{
69
fmt::{self, Write as _},
@@ -43,13 +46,15 @@ impl Visit for Data {
4346
}
4447
}
4548
#[derive(Debug)]
46-
pub struct HierarchicalLayer<W = fn() -> io::Stderr>
49+
pub struct HierarchicalLayer<W = fn() -> io::Stderr, FT = ()>
4750
where
4851
W: for<'writer> MakeWriter<'writer> + 'static,
52+
FT: FormatTime,
4953
{
5054
make_writer: W,
5155
bufs: Mutex<Buffers>,
5256
config: Config,
57+
timer: FT,
5358
}
5459

5560
impl Default for HierarchicalLayer {
@@ -70,13 +75,15 @@ impl HierarchicalLayer<fn() -> io::Stderr> {
7075
make_writer: io::stderr,
7176
bufs: Mutex::new(Buffers::new()),
7277
config,
78+
timer: (),
7379
}
7480
}
7581
}
7682

77-
impl<W> HierarchicalLayer<W>
83+
impl<W, FT> HierarchicalLayer<W, FT>
7884
where
7985
W: for<'writer> MakeWriter<'writer> + 'static,
86+
FT: FormatTime,
8087
{
8188
/// Enables terminal colors, boldness and italics.
8289
pub fn with_ansi(self, ansi: bool) -> Self {
@@ -86,14 +93,15 @@ where
8693
}
8794
}
8895

89-
pub fn with_writer<W2>(self, make_writer: W2) -> HierarchicalLayer<W2>
96+
pub fn with_writer<W2>(self, make_writer: W2) -> HierarchicalLayer<W2, FT>
9097
where
9198
W2: for<'writer> MakeWriter<'writer>,
9299
{
93100
HierarchicalLayer {
94101
make_writer,
95102
config: self.config,
96103
bufs: self.bufs,
104+
timer: self.timer,
97105
}
98106
}
99107

@@ -113,6 +121,16 @@ where
113121
}
114122
}
115123

124+
/// Specifies how to measure and format time at which event has occurred.
125+
pub fn with_timer<FT2: FormatTime>(self, timer: FT2) -> HierarchicalLayer<W, FT2> {
126+
HierarchicalLayer {
127+
make_writer: self.make_writer,
128+
config: self.config,
129+
bufs: self.bufs,
130+
timer,
131+
}
132+
}
133+
116134
/// Whether to render the event and span targets. Usually targets are the module path to the
117135
/// event/span macro invocation.
118136
pub fn with_targets(self, targets: bool) -> Self {
@@ -274,10 +292,11 @@ where
274292
}
275293
}
276294

277-
impl<S, W> Layer<S> for HierarchicalLayer<W>
295+
impl<S, W, FT> Layer<S> for HierarchicalLayer<W, FT>
278296
where
279297
S: Subscriber + for<'span> LookupSpan<'span>,
280298
W: for<'writer> MakeWriter<'writer> + 'static,
299+
FT: FormatTime + 'static,
281300
{
282301
fn on_new_span(&self, attrs: &Attributes, id: &Id, ctx: Context<S>) {
283302
let span = ctx.span(id).expect("in new_span but span does not exist");
@@ -306,6 +325,21 @@ where
306325
let bufs = &mut *guard;
307326
let mut event_buf = &mut bufs.current_buf;
308327

328+
// Time.
329+
330+
{
331+
let prev_buffer_len = event_buf.len();
332+
333+
self.timer
334+
.format_time(&mut event_buf)
335+
.expect("Unable to write time to buffer");
336+
337+
// Something was written to the buffer, pad it with a space.
338+
if prev_buffer_len < event_buf.len() {
339+
write!(event_buf, " ").expect("Unable to write to buffer");
340+
}
341+
}
342+
309343
// printing the indentation
310344
let indent = ctx
311345
.event_scope(event)

src/time.rs

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
/// A type that can measure and format the current time.
2+
///
3+
/// This trait is used by [HierarchicalLayer] to include a timestamp with each
4+
/// [Event] when it is logged.
5+
///
6+
/// Notable default implementations of this trait are [LocalDateTime] and `()`.
7+
/// The former prints the current time as reported by [time's OffsetDateTime]
8+
/// (note that it requires a `time` feature to be enabled and may panic!
9+
/// make sure to check out the docs for the [LocalDateTime]),
10+
/// and the latter does not print the current time at all.
11+
///
12+
/// Inspired by the [FormatTime] trait from [tracing-subscriber].
13+
///
14+
/// [HierarchicalLayer]: crate::HierarchicalLayer
15+
/// [Event]: tracing_core::Event
16+
/// [time's OffsetDateTime]: time::OffsetDateTime
17+
/// [FormatTime]: tracing_subscriber::fmt::time::FormatTime
18+
/// [tracing-subscriber]: tracing_subscriber
19+
// NB:
20+
// We can't use `tracing_subscriber::fmt::format::Writer`
21+
// since it doesn't have a public constructor.
22+
pub trait FormatTime {
23+
fn format_time(&self, w: &mut impl std::fmt::Write) -> std::fmt::Result;
24+
}
25+
26+
////////////////////////////////////////////////////////////////////////////////////////////////////
27+
28+
/// Default do-nothing time formatter.
29+
impl FormatTime for () {
30+
fn format_time(&self, _w: &mut impl std::fmt::Write) -> std::fmt::Result {
31+
Ok(())
32+
}
33+
}
34+
35+
////////////////////////////////////////////////////////////////////////////////////////////////////
36+
37+
/// Retrieve and print the current wall-clock time in UTC timezone.
38+
#[cfg(feature = "time")]
39+
#[derive(Debug, Clone, Copy, Eq, PartialEq, Default)]
40+
pub struct UtcDateTime;
41+
42+
#[cfg(feature = "time")]
43+
impl FormatTime for UtcDateTime {
44+
fn format_time(&self, w: &mut impl std::fmt::Write) -> std::fmt::Result {
45+
let time = time::OffsetDateTime::now_utc();
46+
write!(w, "{} {}", time.date(), time.time())
47+
}
48+
}
49+
50+
////////////////////////////////////////////////////////////////////////////////////////////////////
51+
52+
/// Retrieve and print the current wall-clock time.
53+
///
54+
/// # Panics
55+
///
56+
/// Panics if [time crate] cannot determine the local UTC offset.
57+
///
58+
/// [time crate]: time
59+
// NB:
60+
// Can't use `tracing_subscriber::fmt::time::SystemTime` since it uses
61+
// private `datetime` module to format the actual time.
62+
#[cfg(feature = "time")]
63+
#[derive(Debug, Clone, Copy, Eq, PartialEq, Default)]
64+
pub struct LocalDateTime;
65+
66+
#[cfg(feature = "time")]
67+
impl FormatTime for LocalDateTime {
68+
fn format_time(&self, w: &mut impl std::fmt::Write) -> std::fmt::Result {
69+
let time = time::OffsetDateTime::now_local().expect("time offset cannot be determined");
70+
write!(w, "{}", time)
71+
}
72+
}
73+
74+
////////////////////////////////////////////////////////////////////////////////////////////////////
75+
76+
/// Retrieve and print the relative elapsed wall-clock time since an epoch.
77+
///
78+
/// The `Default` implementation for `Uptime` makes the epoch the current time.
79+
// NB: Copy-pasted from `tracing-subscriber::fmt::time::Uptime`.
80+
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
81+
pub struct Uptime {
82+
epoch: std::time::Instant,
83+
}
84+
85+
impl Default for Uptime {
86+
fn default() -> Self {
87+
Uptime {
88+
epoch: std::time::Instant::now(),
89+
}
90+
}
91+
}
92+
93+
impl From<std::time::Instant> for Uptime {
94+
fn from(epoch: std::time::Instant) -> Self {
95+
Uptime { epoch }
96+
}
97+
}
98+
99+
impl FormatTime for Uptime {
100+
fn format_time(&self, w: &mut impl std::fmt::Write) -> std::fmt::Result {
101+
let e = self.epoch.elapsed();
102+
write!(w, "{:4}.{:06}s", e.as_secs(), e.subsec_micros())
103+
}
104+
}
105+
106+
////////////////////////////////////////////////////////////////////////////////////////////////////
107+
108+
impl<'a, F> FormatTime for &'a F
109+
where
110+
F: FormatTime,
111+
{
112+
fn format_time(&self, w: &mut impl std::fmt::Write) -> std::fmt::Result {
113+
(*self).format_time(w)
114+
}
115+
}
116+
117+
// NB:
118+
// Can't impl for `fn(&mut impl std::fmt::Write)` since impl trait is not allowed
119+
// outside of function and inherent method return types for now.

tests/format_time.rs

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
use std::{
2+
fmt::Write,
3+
sync::{
4+
atomic::{AtomicU64, Ordering},
5+
Arc,
6+
},
7+
};
8+
9+
use tracing::{span, Level};
10+
use tracing_subscriber::{layer::SubscriberExt, Registry};
11+
12+
use tracing_tree::{time::FormatTime, HierarchicalLayer};
13+
14+
#[derive(Debug)]
15+
struct FormatTimeCounter(Arc<AtomicU64>);
16+
17+
impl FormatTime for FormatTimeCounter {
18+
fn format_time(&self, _w: &mut impl Write) -> std::fmt::Result {
19+
self.0.fetch_add(1, Ordering::Relaxed);
20+
Ok(())
21+
}
22+
}
23+
24+
#[test]
25+
fn format_time_num_calls() {
26+
let num_called = Arc::new(AtomicU64::new(0));
27+
let format_time_counter = FormatTimeCounter(Arc::clone(&num_called));
28+
29+
let layer = HierarchicalLayer::default()
30+
.with_writer(std::io::stdout)
31+
.with_indent_lines(true)
32+
.with_indent_amount(2)
33+
.with_timer(format_time_counter)
34+
.with_thread_names(true)
35+
.with_thread_ids(true)
36+
.with_verbose_exit(true)
37+
.with_verbose_entry(true)
38+
.with_targets(true);
39+
40+
let subscriber = Registry::default().with(layer);
41+
tracing::subscriber::set_global_default(subscriber).unwrap();
42+
43+
let test_span = span!(Level::TRACE, "format-time-num-calls-test", version = %0.1);
44+
let _e = test_span.enter();
45+
46+
tracing::info!("first event");
47+
assert_eq!(num_called.load(Ordering::Relaxed), 1);
48+
49+
std::thread::sleep(std::time::Duration::from_millis(100));
50+
tracing::info!("second event");
51+
assert_eq!(num_called.load(Ordering::Relaxed), 2);
52+
53+
let nested_span = span!(Level::TRACE, "nested-span");
54+
nested_span.in_scope(|| {
55+
tracing::debug!("nested event");
56+
assert_eq!(num_called.load(Ordering::Relaxed), 3);
57+
58+
tracing::info!("important nested event");
59+
assert_eq!(num_called.load(Ordering::Relaxed), 4);
60+
});
61+
drop(nested_span);
62+
63+
instrumented_function();
64+
assert_eq!(num_called.load(Ordering::Relaxed), 6);
65+
66+
tracing::info!("exiting");
67+
assert_eq!(num_called.load(Ordering::Relaxed), 7);
68+
}
69+
70+
#[tracing::instrument]
71+
fn instrumented_function() {
72+
tracing::info!("instrumented function");
73+
nested_instrumented_function();
74+
}
75+
76+
#[tracing::instrument]
77+
fn nested_instrumented_function() {
78+
tracing::warn!("nested instrumented function");
79+
}

0 commit comments

Comments
 (0)