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}