Skip to main content

tuwunel_core/log/
console.rs

1use std::{env, io, sync::LazyLock};
2
3use tracing::{
4	Event, Level, Subscriber,
5	field::{Field, Visit},
6};
7use tracing_subscriber::{
8	field::RecordFields,
9	fmt,
10	fmt::{
11		FmtContext, FormatEvent, FormatFields, MakeWriter,
12		format::{Compact, DefaultVisitor, Format, Full, Pretty, Writer},
13	},
14	registry::LookupSpan,
15};
16
17use crate::{Config, Result, apply, debug, is_equal_to};
18
19static SYSTEMD_MODE: LazyLock<bool> =
20	LazyLock::new(|| env::var("SYSTEMD_EXEC_PID").is_ok() && env::var("JOURNAL_STREAM").is_ok());
21
22pub struct ConsoleWriter {
23	stdout: io::Stdout,
24	stderr: io::Stderr,
25	_journal_stream: [u64; 2],
26	use_stderr: bool,
27}
28
29impl ConsoleWriter {
30	#[must_use]
31	pub fn new(config: &Config) -> Self {
32		let journal_stream = get_journal_stream();
33		Self {
34			stdout: io::stdout(),
35			stderr: io::stderr(),
36			_journal_stream: journal_stream.into(),
37			use_stderr: journal_stream.0 != 0 || config.log_to_stderr,
38		}
39	}
40}
41
42impl<'a> MakeWriter<'a> for ConsoleWriter {
43	type Writer = &'a Self;
44
45	fn make_writer(&'a self) -> Self::Writer { self }
46}
47
48impl io::Write for &'_ ConsoleWriter {
49	fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
50		if self.use_stderr {
51			self.stderr.lock().write(buf)
52		} else {
53			self.stdout.lock().write(buf)
54		}
55	}
56
57	fn flush(&mut self) -> io::Result<()> {
58		if self.use_stderr {
59			self.stderr.lock().flush()
60		} else {
61			self.stdout.lock().flush()
62		}
63	}
64}
65
66pub struct ConsoleFormat {
67	pretty: Format<Pretty>,
68	full: Format<Full>,
69	compact: Format<Compact>,
70	compact_mode: bool,
71}
72
73impl ConsoleFormat {
74	#[must_use]
75	pub fn new(config: &Config) -> Self {
76		Self {
77			pretty: fmt::format()
78				.pretty()
79				.with_ansi(config.log_colors)
80				.with_thread_names(true)
81				.with_thread_ids(true)
82				.with_target(true)
83				.with_file(true)
84				.with_line_number(true)
85				.with_source_location(true),
86
87			full: Format::<Full>::default()
88				.with_thread_ids(config.log_thread_ids)
89				.with_ansi(config.log_colors),
90
91			compact: fmt::format()
92				.compact()
93				.with_ansi(config.log_colors),
94
95			compact_mode: config.log_compact,
96		}
97	}
98}
99
100impl<S, N> FormatEvent<S, N> for ConsoleFormat
101where
102	S: Subscriber + for<'a> LookupSpan<'a>,
103	N: for<'a> FormatFields<'a> + 'static,
104{
105	fn format_event(
106		&self,
107		ctx: &FmtContext<'_, S, N>,
108		writer: Writer<'_>,
109		event: &Event<'_>,
110	) -> Result<(), std::fmt::Error> {
111		let is_debug = debug::logging()
112			&& event
113				.fields()
114				.map(|field| field.name())
115				.any(is_equal_to!("_debug"));
116
117		match *event.metadata().level() {
118			| _ if self.compact_mode => self.compact.format_event(ctx, writer, event),
119			| Level::ERROR if !is_debug => self.pretty.format_event(ctx, writer, event),
120			| _ => self.full.format_event(ctx, writer, event),
121		}
122	}
123}
124
125struct ConsoleVisitor<'a> {
126	visitor: DefaultVisitor<'a>,
127}
128
129impl<'writer> FormatFields<'writer> for ConsoleFormat {
130	fn format_fields<R>(&self, writer: Writer<'writer>, fields: R) -> Result<(), std::fmt::Error>
131	where
132		R: RecordFields,
133	{
134		let mut visitor = ConsoleVisitor {
135			visitor: DefaultVisitor::<'_>::new(writer, true),
136		};
137
138		fields.record(&mut visitor);
139
140		Ok(())
141	}
142}
143
144impl Visit for ConsoleVisitor<'_> {
145	fn record_debug(&mut self, field: &Field, value: &dyn std::fmt::Debug) {
146		if field.name().starts_with('_') {
147			return;
148		}
149
150		self.visitor.record_debug(field, value);
151	}
152}
153
154#[must_use]
155fn get_journal_stream() -> (u64, u64) {
156	is_systemd_mode()
157		.then(|| env::var("JOURNAL_STREAM").ok())
158		.flatten()
159		.as_deref()
160		.and_then(|s| s.split_once(':'))
161		.map(apply!(2, str::parse))
162		.map(apply!(2, Result::unwrap_or_default))
163		.unwrap_or((0, 0))
164}
165
166#[inline]
167#[must_use]
168pub fn is_systemd_mode() -> bool { *SYSTEMD_MODE }