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}