Skip to main content

soil_client/tracing/logging/
event_format.rs

1// This file is part of Soil.
2
3// Copyright (C) Soil contributors.
4// Copyright (C) Parity Technologies (UK) Ltd.
5// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
6
7use super::fast_local_time::FastLocalTime;
8use ::tracing::{Event, Level, Subscriber};
9use console::style;
10use std::fmt;
11use tracing_log::NormalizeEvent;
12use tracing_subscriber::{
13	fmt::{format, time::FormatTime, FmtContext, FormatEvent, FormatFields},
14	registry::LookupSpan,
15};
16
17/// A pre-configured event formatter.
18pub struct EventFormat<T = FastLocalTime> {
19	/// Use the given timer for log message timestamps.
20	pub timer: T,
21	/// Sets whether or not an event's target is displayed.
22	pub display_target: bool,
23	/// Sets whether or not an event's level is displayed.
24	pub display_level: bool,
25	/// Sets whether or not the name of the current thread is displayed when formatting events.
26	pub display_thread_name: bool,
27	/// Duplicate INFO, WARN and ERROR messages to stdout.
28	pub dup_to_stdout: bool,
29}
30
31impl<T> EventFormat<T>
32where
33	T: FormatTime,
34{
35	// NOTE: the following code took inspiration from tracing-subscriber
36	//
37	//       https://github.com/tokio-rs/tracing/blob/2f59b32/tracing-subscriber/src/fmt/format/mod.rs#L449
38	pub(crate) fn format_event_custom<'b, 'w, S, N>(
39		&self,
40		ctx: &FmtContext<'b, S, N>,
41		mut writer: format::Writer<'w>,
42		event: &Event,
43	) -> fmt::Result
44	where
45		S: Subscriber + for<'a> LookupSpan<'a>,
46		N: for<'a> FormatFields<'a> + 'static,
47	{
48		let normalized_meta = event.normalized_metadata();
49		let meta = normalized_meta.as_ref().unwrap_or_else(|| event.metadata());
50		time::write(&self.timer, &mut format::Writer::new(&mut writer))?;
51
52		if self.display_level {
53			let fmt_level = FmtLevel::new(meta.level());
54			write!(writer, "{} ", fmt_level)?;
55		}
56
57		if self.display_thread_name {
58			let current_thread = std::thread::current();
59			match current_thread.name() {
60				Some(name) => {
61					write!(&mut writer, "{} ", FmtThreadName::new(name))?;
62				},
63				// fall-back to thread id when name is absent and ids are not enabled
64				None => {
65					write!(&mut writer, "{:0>2?} ", current_thread.id())?;
66				},
67			}
68		}
69
70		if self.display_target {
71			write!(&mut writer, "{}: ", meta.target())?;
72		}
73
74		// Custom code to display node name
75		if let Some(span) = ctx.lookup_current() {
76			for span in span.scope() {
77				let exts = span.extensions();
78				if let Some(prefix) = exts.get::<super::layers::Prefix>() {
79					write!(&mut writer, "{}", prefix.as_str())?;
80					break;
81				}
82			}
83		}
84
85		ctx.format_fields(format::Writer::new(&mut writer), event)?;
86		writeln!(&mut writer)?;
87
88		Ok(())
89	}
90}
91
92// NOTE: the following code took inspiration from tracing-subscriber
93//
94//       https://github.com/tokio-rs/tracing/blob/2f59b32/tracing-subscriber/src/fmt/format/mod.rs#L449
95impl<S, N, T> FormatEvent<S, N> for EventFormat<T>
96where
97	S: Subscriber + for<'a> LookupSpan<'a>,
98	N: for<'a> FormatFields<'a> + 'static,
99	T: FormatTime,
100{
101	fn format_event(
102		&self,
103		ctx: &FmtContext<S, N>,
104		mut writer: format::Writer<'_>,
105		event: &Event,
106	) -> fmt::Result {
107		if self.dup_to_stdout
108			&& (event.metadata().level() == &Level::INFO
109				|| event.metadata().level() == &Level::WARN
110				|| event.metadata().level() == &Level::ERROR)
111		{
112			let mut out = String::new();
113			let buf_writer = format::Writer::new(&mut out);
114			self.format_event_custom(ctx, buf_writer, event)?;
115			writer.write_str(&out)?;
116			print!("{}", out);
117			Ok(())
118		} else {
119			self.format_event_custom(ctx, writer, event)
120		}
121	}
122}
123
124struct FmtLevel<'a> {
125	level: &'a Level,
126}
127
128impl<'a> FmtLevel<'a> {
129	pub(crate) fn new(level: &'a Level) -> Self {
130		Self { level }
131	}
132}
133
134const TRACE_STR: &str = "TRACE";
135const DEBUG_STR: &str = "DEBUG";
136const INFO_STR: &str = " INFO";
137const WARN_STR: &str = " WARN";
138const ERROR_STR: &str = "ERROR";
139
140impl<'a> fmt::Display for FmtLevel<'a> {
141	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
142		match *self.level {
143			Level::TRACE => write!(f, "{}", style(TRACE_STR).magenta()),
144			Level::DEBUG => write!(f, "{}", style(DEBUG_STR).blue()),
145			Level::INFO => write!(f, "{}", style(INFO_STR).green()),
146			Level::WARN => write!(f, "{}", style(WARN_STR).yellow()),
147			Level::ERROR => write!(f, "{}", style(ERROR_STR).red()),
148		}
149	}
150}
151
152struct FmtThreadName<'a> {
153	name: &'a str,
154}
155
156impl<'a> FmtThreadName<'a> {
157	pub(crate) fn new(name: &'a str) -> Self {
158		Self { name }
159	}
160}
161
162// NOTE: the following code has been duplicated from tracing-subscriber
163//
164//       https://github.com/tokio-rs/tracing/blob/2f59b32/tracing-subscriber/src/fmt/format/mod.rs#L845
165impl<'a> fmt::Display for FmtThreadName<'a> {
166	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
167		use std::sync::atomic::{
168			AtomicUsize,
169			Ordering::{AcqRel, Acquire, Relaxed},
170		};
171
172		// Track the longest thread name length we've seen so far in an atomic,
173		// so that it can be updated by any thread.
174		static MAX_LEN: AtomicUsize = AtomicUsize::new(0);
175		let len = self.name.len();
176		// Snapshot the current max thread name length.
177		let mut max_len = MAX_LEN.load(Relaxed);
178
179		while len > max_len {
180			// Try to set a new max length, if it is still the value we took a
181			// snapshot of.
182			match MAX_LEN.compare_exchange(max_len, len, AcqRel, Acquire) {
183				// We successfully set the new max value
184				Ok(_) => break,
185				// Another thread set a new max value since we last observed
186				// it! It's possible that the new length is actually longer than
187				// ours, so we'll loop again and check whether our length is
188				// still the longest. If not, we'll just use the newer value.
189				Err(actual) => max_len = actual,
190			}
191		}
192
193		// pad thread name using `max_len`
194		write!(f, "{:>width$}", self.name, width = max_len)
195	}
196}
197
198// NOTE: the following code has been duplicated from tracing-subscriber
199//
200//       https://github.com/tokio-rs/tracing/blob/2f59b32/tracing-subscriber/src/fmt/time/mod.rs#L252
201mod time {
202	use std::fmt;
203	use tracing_subscriber::fmt::{format, time::FormatTime};
204
205	pub(crate) fn write<T>(timer: T, writer: &mut format::Writer<'_>) -> fmt::Result
206	where
207		T: FormatTime,
208	{
209		if console::colors_enabled() {
210			write!(writer, "\x1B[2m")?;
211			timer.format_time(writer)?;
212			write!(writer, "\x1B[0m")?;
213		} else {
214			timer.format_time(writer)?;
215		}
216
217		writer.write_char(' ')?;
218		Ok(())
219	}
220}