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 fn process(&self, tree: Tree) -> Result;
54
55 /// Returns a `Processor` that first attempts processing with `self`, and
56 /// resorts to processing with `fallback` on failure.
57 ///
58 /// Note that [`or_stdout`], [`or_stderr`], and [`or_none`] can be used as
59 /// shortcuts for pretty printing or dropping the `Tree` entirely.
60 ///
61 /// [`or_stdout`]: Processor::or_stdout
62 /// [`or_stderr`]: Processor::or_stderr
63 /// [`or_none`]: Processor::or_none
64 fn or<P: Processor>(self, processor: P) -> WithFallback<Self, P> {
65 WithFallback {
66 primary: self,
67 fallback: processor,
68 }
69 }
70
71 /// Returns a `Processor` that first attempts processing with `self`, and
72 /// resorts to pretty-printing to stdout on failure.
73 fn or_stdout(self) -> WithFallback<Self, Printer<Pretty, MakeStdout>> {
74 self.or(Printer::new().writer(MakeStdout))
75 }
76
77 /// Returns a `Processor` that first attempts processing with `self`, and
78 /// resorts to pretty-printing to stderr on failure.
79 fn or_stderr(self) -> WithFallback<Self, Printer<Pretty, MakeStderr>> {
80 self.or(Printer::new().writer(MakeStderr))
81 }
82
83 /// Returns a `Processor` that first attempts processing with `self`, otherwise
84 /// silently fails.
85 fn or_none(self) -> WithFallback<Self, Sink> {
86 self.or(Sink)
87 }
88}
89
90/// A [`Processor`] composed of a primary and a fallback `Processor`.
91///
92/// This type is returned by [`Processor::or`].
93#[derive(Debug)]
94pub struct WithFallback<P, F> {
95 primary: P,
96 fallback: F,
97}
98
99/// A [`Processor`] that ignores any incoming logs.
100///
101/// This processor cannot fail.
102#[derive(Debug)]
103pub struct Sink;
104
105/// A [`Processor`] that processes incoming logs via a function.
106///
107/// Instances of `FromFn` are returned by the [`from_fn`] function.
108#[derive(Debug)]
109pub struct FromFn<F>(F);
110
111/// Create a processor that processes incoming logs via a function.
112///
113/// # Examples
114///
115/// Internally, [`worker_task`] uses `from_fn` to allow the subscriber to send
116/// trace data across a channel to a processing task.
117/// ```
118/// use tokio::sync::mpsc;
119/// use tracing_forest::processor;
120///
121/// let (tx, rx) = mpsc::unbounded_channel();
122///
123/// let sender_processor = processor::from_fn(move |tree| tx
124/// .send(tree)
125/// .map_err(|err| {
126/// let msg = err.to_string().into();
127/// processor::error(err.0, msg)
128/// })
129/// );
130///
131/// // -- snip --
132/// ```
133///
134/// [`worker_task`]: crate::runtime::worker_task
135pub fn from_fn<F>(f: F) -> FromFn<F>
136where
137 F: 'static + Fn(Tree) -> Result,
138{
139 FromFn(f)
140}
141
142impl<P, F> Processor for WithFallback<P, F>
143where
144 P: Processor,
145 F: Processor,
146{
147 fn process(&self, tree: Tree) -> Result {
148 self.primary.process(tree).or_else(|err| {
149 eprintln!("{}, using fallback processor...", err);
150 self.fallback.process(err.tree)
151 })
152 }
153}
154
155impl Processor for Sink {
156 fn process(&self, _tree: Tree) -> Result {
157 Ok(())
158 }
159}
160
161impl<F> Processor for FromFn<F>
162where
163 F: 'static + Fn(Tree) -> Result,
164{
165 fn process(&self, tree: Tree) -> Result {
166 (self.0)(tree)
167 }
168}
169
170impl<P: Processor> Processor for Box<P> {
171 fn process(&self, tree: Tree) -> Result {
172 self.as_ref().process(tree)
173 }
174}
175
176impl<P: Processor> Processor for Arc<P> {
177 fn process(&self, tree: Tree) -> Result {
178 self.as_ref().process(tree)
179 }
180}