tuwunel_core/log/
console.rs1use 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 }