tracing_forest/printer/mod.rs
1//! Utilities for formatting and writing trace trees.
2use crate::processor::{self, Processor};
3use crate::tree::Tree;
4use std::error::Error;
5use std::io::{self, Write};
6use tracing_subscriber::fmt::MakeWriter;
7
8mod pretty;
9pub use pretty::Pretty;
10
11/// Format a [`Tree`] into a `String`.
12///
13/// # Examples
14///
15/// This trait implements all `Fn(&Tree) -> Result<String, E>` types, where `E: Error + Send + Sync`.
16/// If the `serde` feature is enabled, functions like `serde_json::to_string_pretty`
17/// can be used wherever a `Formatter` is required.
18/// ```
19/// # use tracing::info;
20/// # #[tokio::main(flavor = "current_thread")]
21/// # async fn main() {
22/// tracing_forest::worker_task()
23/// .map_receiver(|receiver| {
24/// receiver.formatter(serde_json::to_string_pretty)
25/// })
26/// .build()
27/// .on(async {
28/// info!("write this as json");
29/// })
30/// .await
31/// # }
32/// ```
33/// Produces the following result:
34/// ```json
35/// {
36/// "Event": {
37/// "uuid": "00000000-0000-0000-0000-000000000000",
38/// "timestamp": "2022-03-24T16:08:17.761149+00:00",
39/// "level": "INFO",
40/// "message": "write this as json",
41/// "tag": "info",
42/// "fields": {}
43/// }
44/// }
45/// ```
46pub trait Formatter {
47 /// The error type if the `Tree` cannot be stringified.
48 type Error: Error + Send + Sync;
49
50 /// Stringifies the `Tree`, or returns an error.
51 ///
52 /// # Errors
53 ///
54 /// If the `Tree` cannot be formatted to a string, an error is returned.
55 fn fmt(&self, tree: &Tree) -> Result<String, Self::Error>;
56}
57
58impl<F, E> Formatter for F
59where
60 F: Fn(&Tree) -> Result<String, E>,
61 E: Error + Send + Sync,
62{
63 type Error = E;
64
65 #[inline]
66 fn fmt(&self, tree: &Tree) -> Result<String, E> {
67 self(tree)
68 }
69}
70
71/// A [`Processor`] that formats and writes logs.
72#[derive(Clone, Debug)]
73pub struct Printer<F, W> {
74 formatter: F,
75 make_writer: W,
76}
77
78/// A [`MakeWriter`] that writes to stdout.
79///
80/// This is functionally the same as using [`std::io::stdout`] as a `MakeWriter`,
81/// except it has a named type and can therefore be used in type signatures.
82#[derive(Debug)]
83pub struct MakeStdout;
84
85/// A [`MakeWriter`] that writes to stderr.
86///
87/// This is functionally the same as using [`std::io::stderr`] as a `MakeWriter`,
88/// except it has a named type and can therefore be used in type signatures.
89#[derive(Debug)]
90pub struct MakeStderr;
91
92impl<'a> MakeWriter<'a> for MakeStdout {
93 type Writer = io::Stdout;
94
95 fn make_writer(&self) -> Self::Writer {
96 io::stdout()
97 }
98}
99
100impl<'a> MakeWriter<'a> for MakeStderr {
101 type Writer = io::Stderr;
102
103 fn make_writer(&self) -> Self::Writer {
104 io::stderr()
105 }
106}
107
108/// A [`Processor`] that pretty-prints to stdout.
109pub type PrettyPrinter = Printer<Pretty, MakeStdout>;
110
111impl PrettyPrinter {
112 /// Returns a new [`PrettyPrinter`] that pretty-prints to stdout.
113 ///
114 /// Use [`Printer::formatter`] and [`Printer::writer`] for custom configuration.
115 pub const fn new() -> Self {
116 Printer {
117 formatter: Pretty,
118 make_writer: MakeStdout,
119 }
120 }
121}
122
123impl<F, W> Printer<F, W>
124where
125 F: 'static + Formatter,
126 W: 'static + for<'a> MakeWriter<'a>,
127{
128 /// Set the formatter.
129 ///
130 /// See the [`Formatter`] trait for details on possible inputs.
131 pub fn formatter<F2>(self, formatter: F2) -> Printer<F2, W>
132 where
133 F2: 'static + Formatter,
134 {
135 Printer {
136 formatter,
137 make_writer: self.make_writer,
138 }
139 }
140
141 /// Set the writer.
142 pub fn writer<W2>(self, make_writer: W2) -> Printer<F, W2>
143 where
144 W2: 'static + for<'a> MakeWriter<'a>,
145 {
146 Printer {
147 formatter: self.formatter,
148 make_writer,
149 }
150 }
151}
152
153impl Default for PrettyPrinter {
154 fn default() -> Self {
155 PrettyPrinter::new()
156 }
157}
158
159impl<F, W> Processor for Printer<F, W>
160where
161 F: 'static + Formatter,
162 W: 'static + for<'a> MakeWriter<'a>,
163{
164 fn process(&self, tree: Tree) -> processor::Result {
165 let string = match self.formatter.fmt(&tree) {
166 Ok(s) => s,
167 Err(e) => return Err(processor::error(tree, e.into())),
168 };
169
170 match self.make_writer.make_writer().write_all(string.as_bytes()) {
171 Ok(()) => Ok(()),
172 Err(e) => Err(processor::error(tree, e.into())),
173 }
174 }
175}
176
177/// A [`Processor`] that captures logs during tests and allows them to be presented
178/// when --nocapture is used.
179#[derive(Clone, Debug)]
180pub struct TestCapturePrinter<F> {
181 formatter: F,
182}
183
184impl TestCapturePrinter<Pretty> {
185 /// Construct a new test capturing printer with the default `Pretty` formatter. This printer
186 /// is intented for use in tests only as it works with the default rust stdout capture mechanism
187 pub const fn new() -> Self {
188 TestCapturePrinter { formatter: Pretty }
189 }
190}
191
192impl<F> Processor for TestCapturePrinter<F>
193where
194 F: 'static + Formatter,
195{
196 fn process(&self, tree: Tree) -> processor::Result {
197 let string = self
198 .formatter
199 .fmt(&tree)
200 .map_err(|e| processor::error(tree, e.into()))?;
201
202 print!("{}", string);
203 Ok(())
204 }
205}