tracing_forest/
processor.rs

1//! Trait for processing log trees on completion.
2//!
3//! See [`Processor`] for more details.
4use crate::printer::{MakeStderr, MakeStdout, Pretty, Printer};
5use crate::tree::Tree;
6use std::error;
7use std::sync::Arc;
8use thiserror::Error;
9
10/// Error type returned if a [`Processor`] fails.
11#[derive(Error, Debug)]
12#[error("{source}")]
13pub struct Error {
14    /// The recoverable [`Tree`] type that couldn't be processed.
15    pub tree: Tree,
16
17    source: Box<dyn error::Error + Send + Sync>,
18}
19
20/// Create an error for when a [`Processor`] fails to process a [`Tree`].
21pub fn error(tree: Tree, source: Box<dyn error::Error + Send + Sync>) -> Error {
22    Error { tree, source }
23}
24
25/// The result type of [`Processor::process`].
26pub type Result = std::result::Result<(), Error>;
27
28/// A trait for processing completed [`Tree`]s.
29///
30/// `Processor`s are responsible for both formatting and writing logs to their
31/// intended destinations. This is typically implemented using
32/// [`Formatter`], [`MakeWriter`], and [`io::Write`].
33///
34/// While this trait may be implemented on downstream types, [`from_fn`]
35/// provides a convenient interface for creating `Processor`s without having to
36/// explicitly define new types.
37///
38/// [trace trees]: crate::tree::Tree
39/// [`Formatter`]: crate::printer::Formatter
40/// [`MakeWriter`]: tracing_subscriber::fmt::MakeWriter
41/// [`io::Write`]: std::io::Write
42pub trait Processor: 'static + Sized {
43    /// Process a [`Tree`]. This can mean many things, such as writing to
44    /// stdout or a file, sending over a network, storing in memory, ignoring,
45    /// or anything else.
46    ///
47    /// # Errors
48    ///
49    /// If the `Tree` cannot be processed, then it is returned along with a
50    /// `Box<dyn Error + Send + Sync>`. If the processor is configured with a
51    /// fallback processor from [`Processor::or`], then the `Tree` is deferred
52    /// to that processor.
53    #[allow(clippy::result_large_err)]
54    fn process(&self, tree: Tree) -> Result;
55
56    /// Returns a `Processor` that first attempts processing with `self`, and
57    /// resorts to processing with `fallback` on failure.
58    ///
59    /// Note that [`or_stdout`], [`or_stderr`], and [`or_none`] can be used as
60    /// shortcuts for pretty printing or dropping the `Tree` entirely.
61    ///
62    /// [`or_stdout`]: Processor::or_stdout
63    /// [`or_stderr`]: Processor::or_stderr
64    /// [`or_none`]: Processor::or_none
65    fn or<P: Processor>(self, processor: P) -> WithFallback<Self, P> {
66        WithFallback {
67            primary: self,
68            fallback: processor,
69        }
70    }
71
72    /// Returns a `Processor` that first attempts processing with `self`, and
73    /// resorts to pretty-printing to stdout on failure.
74    fn or_stdout(self) -> WithFallback<Self, Printer<Pretty, MakeStdout>> {
75        self.or(Printer::new().writer(MakeStdout))
76    }
77
78    /// Returns a `Processor` that first attempts processing with `self`, and
79    /// resorts to pretty-printing to stderr on failure.
80    fn or_stderr(self) -> WithFallback<Self, Printer<Pretty, MakeStderr>> {
81        self.or(Printer::new().writer(MakeStderr))
82    }
83
84    /// Returns a `Processor` that first attempts processing with `self`, otherwise
85    /// silently fails.
86    fn or_none(self) -> WithFallback<Self, Sink> {
87        self.or(Sink)
88    }
89}
90
91/// A [`Processor`] composed of a primary and a fallback `Processor`.
92///
93/// This type is returned by [`Processor::or`].
94#[derive(Debug)]
95pub struct WithFallback<P, F> {
96    primary: P,
97    fallback: F,
98}
99
100/// A [`Processor`] that ignores any incoming logs.
101///
102/// This processor cannot fail.
103#[derive(Debug)]
104pub struct Sink;
105
106/// A [`Processor`] that processes incoming logs via a function.
107///
108/// Instances of `FromFn` are returned by the [`from_fn`] function.
109#[derive(Debug)]
110pub struct FromFn<F>(F);
111
112/// Create a processor that processes incoming logs via a function.
113///
114/// # Examples
115///
116/// Internally, [`worker_task`] uses `from_fn` to allow the subscriber to send
117/// trace data across a channel to a processing task.
118/// ```
119/// use tokio::sync::mpsc;
120/// use tracing_forest::processor;
121///
122/// let (tx, rx) = mpsc::unbounded_channel();
123///
124/// let sender_processor = processor::from_fn(move |tree| tx
125///     .send(tree)
126///     .map_err(|err| {
127///         let msg = err.to_string().into();
128///         processor::error(err.0, msg)
129///     })
130/// );
131///
132/// // -- snip --
133/// ```
134///
135/// [`worker_task`]: crate::runtime::worker_task
136pub fn from_fn<F>(f: F) -> FromFn<F>
137where
138    F: 'static + Fn(Tree) -> Result,
139{
140    FromFn(f)
141}
142
143impl<P, F> Processor for WithFallback<P, F>
144where
145    P: Processor,
146    F: Processor,
147{
148    fn process(&self, tree: Tree) -> Result {
149        self.primary.process(tree).or_else(|err| {
150            eprintln!("{err}, using fallback processor...");
151            self.fallback.process(err.tree)
152        })
153    }
154}
155
156impl Processor for Sink {
157    fn process(&self, _tree: Tree) -> Result {
158        Ok(())
159    }
160}
161
162impl<F> Processor for FromFn<F>
163where
164    F: 'static + Fn(Tree) -> Result,
165{
166    fn process(&self, tree: Tree) -> Result {
167        (self.0)(tree)
168    }
169}
170
171impl<P: Processor> Processor for Box<P> {
172    fn process(&self, tree: Tree) -> Result {
173        self.as_ref().process(tree)
174    }
175}
176
177impl<P: Processor> Processor for Arc<P> {
178    fn process(&self, tree: Tree) -> Result {
179        self.as_ref().process(tree)
180    }
181}