tracing_systemd/
output.rs1use std::fmt;
7use std::io::{self, IsTerminal, Write};
8use std::sync::Mutex;
9
10pub struct Output {
12 sink: Sink,
13 is_terminal: bool,
14}
15
16enum Sink {
17 Stdout,
18 Stderr,
19 Custom(Mutex<Box<dyn Write + Send>>),
20}
21
22impl Output {
23 #[must_use]
25 pub fn stdout() -> Self {
26 Self {
27 sink: Sink::Stdout,
28 is_terminal: io::stdout().is_terminal(),
29 }
30 }
31
32 #[must_use]
34 pub fn stderr() -> Self {
35 Self {
36 sink: Sink::Stderr,
37 is_terminal: io::stderr().is_terminal(),
38 }
39 }
40
41 pub fn writer<W>(writer: W) -> Self
46 where
47 W: Write + Send + 'static,
48 {
49 Self {
50 sink: Sink::Custom(Mutex::new(Box::new(writer))),
51 is_terminal: false,
52 }
53 }
54
55 #[must_use]
57 pub fn is_terminal(&self) -> bool {
58 self.is_terminal
59 }
60
61 pub(crate) fn write_line(&self, line: &str) {
64 match &self.sink {
65 Sink::Stdout => {
66 let mut guard = io::stdout().lock();
67 let _ = writeln!(guard, "{line}");
68 }
69 Sink::Stderr => {
70 let mut guard = io::stderr().lock();
71 let _ = writeln!(guard, "{line}");
72 }
73 Sink::Custom(m) => {
74 let mut guard = m.lock().unwrap_or_else(std::sync::PoisonError::into_inner);
76 let _ = writeln!(guard, "{line}");
77 }
78 }
79 }
80}
81
82impl fmt::Debug for Output {
83 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
84 let kind = match &self.sink {
85 Sink::Stdout => "Stdout",
86 Sink::Stderr => "Stderr",
87 Sink::Custom(_) => "Custom",
88 };
89 f.debug_struct("Output")
90 .field("sink", &kind)
91 .field("is_terminal", &self.is_terminal)
92 .finish()
93 }
94}
95
96impl Default for Output {
97 fn default() -> Self {
98 Self::stdout()
99 }
100}
101
102#[cfg(test)]
103mod tests {
104 use super::*;
105 use std::sync::{Arc, Mutex};
106
107 #[derive(Clone, Default)]
108 struct Buf(Arc<Mutex<Vec<u8>>>);
109 impl Write for Buf {
110 fn write(&mut self, b: &[u8]) -> io::Result<usize> {
111 self.0.lock().unwrap().extend_from_slice(b);
112 Ok(b.len())
113 }
114 fn flush(&mut self) -> io::Result<()> {
115 Ok(())
116 }
117 }
118
119 #[test]
120 fn writer_captures_lines() {
121 let buf = Buf::default();
122 let captured = buf.0.clone();
123 let out = Output::writer(buf);
124
125 out.write_line("hello");
126 out.write_line("world");
127
128 let bytes = captured.lock().unwrap();
129 assert_eq!(std::str::from_utf8(&bytes).unwrap(), "hello\nworld\n");
130 }
131
132 #[test]
133 fn writer_is_not_a_terminal() {
134 let buf = Buf::default();
135 let out = Output::writer(buf);
136 assert!(!out.is_terminal());
137 }
138
139 #[test]
140 fn debug_does_not_panic() {
141 let _ = format!("{:?}", Output::stdout());
142 let _ = format!("{:?}", Output::stderr());
143 let _ = format!("{:?}", Output::writer(Vec::<u8>::new()));
144 }
145}