logforth_core/logger/
builder.rs

1// Copyright 2024 FastLabs Developers
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use crate::Append;
16use crate::Diagnostic;
17use crate::Filter;
18use crate::Logger;
19use crate::logger::log_impl::Dispatch;
20use crate::logger::log_impl::set_default_logger;
21
22/// Create a new empty [`LoggerBuilder`] instance for configuring log dispatching.
23///
24/// # Examples
25///
26/// ```
27/// use logforth_core::append;
28///
29/// let builder = logforth_core::builder()
30///     .dispatch(|d| d.append(append::Stderr::default()))
31///     .apply();
32/// ```
33pub fn builder() -> LoggerBuilder {
34    LoggerBuilder { dispatches: vec![] }
35}
36
37/// A builder for configuring log dispatching and setting up the global logger.
38///
39/// # Examples
40///
41/// ```
42/// use logforth_core::append;
43///
44/// logforth_core::builder()
45///     .dispatch(|d| d.append(append::Stdout::default()))
46///     .apply();
47/// ```
48#[must_use = "call `apply` to set the global logger or `build` to construct a logger instance"]
49#[derive(Debug)]
50pub struct LoggerBuilder {
51    // stashed dispatches
52    dispatches: Vec<Dispatch>,
53}
54
55impl LoggerBuilder {
56    /// Register a new dispatch with the [`LoggerBuilder`].
57    ///
58    /// # Examples
59    ///
60    /// ```
61    /// use logforth_core::append;
62    ///
63    /// logforth_core::builder()
64    ///     .dispatch(|d| d.append(append::Stderr::default()))
65    ///     .apply();
66    /// ```
67    pub fn dispatch<F>(mut self, f: F) -> Self
68    where
69        F: FnOnce(DispatchBuilder<false>) -> DispatchBuilder<true>,
70    {
71        self.dispatches.push(f(DispatchBuilder::new()).build());
72        self
73    }
74
75    /// Build the [`Logger`].
76    ///
77    /// # Examples
78    ///
79    /// ```
80    /// use logforth_core::record::Record;
81    ///
82    /// let l = logforth_core::builder().build();
83    /// let r = Record::builder().payload("hello world!").build();
84    /// l.log(&r);
85    /// ```
86    pub fn build(self) -> Logger {
87        Logger::new(self.dispatches)
88    }
89
90    /// Set up the global logger with all the configured dispatches.
91    ///
92    /// This should be called early in the execution of a Rust program. Any log events that occur
93    /// before initialization will be ignored.
94    ///
95    /// # Errors
96    ///
97    /// Return an error if a global logger has already been set.
98    ///
99    /// # Examples
100    ///
101    /// ```
102    /// if logforth_core::builder().try_apply().is_err() {
103    ///     eprintln!("failed to set logger");
104    /// }
105    /// ```
106    pub fn try_apply(self) -> Result<(), Logger> {
107        set_default_logger(self.build())
108    }
109
110    /// Set up the global logger with all the configured dispatches.
111    ///
112    /// This should be called early in the execution of a Rust program. Any log events that occur
113    /// before initialization will be ignored.
114    ///
115    /// This function will panic if it is called more than once, or if another library has already
116    /// initialized a global logger.
117    ///
118    /// # Panics
119    ///
120    /// Panic if the global logger has already been set.
121    ///
122    /// # Examples
123    ///
124    /// ```
125    /// logforth_core::builder().apply();
126    /// ```
127    pub fn apply(self) {
128        self.try_apply()
129            .expect("LoggerBuilder::apply must be called before the global logger initialized");
130    }
131}
132
133/// A builder for configuring a log dispatch, including filters and appenders.
134///
135/// # Examples
136///
137/// ```
138/// use logforth_core::append;
139/// use logforth_core::record::Level;
140/// use logforth_core::record::LevelFilter;
141///
142/// logforth_core::builder()
143///     .dispatch(|d| {
144///         d.filter(LevelFilter::MoreSevereEqual(Level::Info))
145///             .append(append::Stdout::default())
146///     })
147///     .apply();
148/// ```
149#[derive(Debug)]
150pub struct DispatchBuilder<const APPEND: bool> {
151    filters: Vec<Box<dyn Filter>>,
152    diagnostics: Vec<Box<dyn Diagnostic>>,
153    appends: Vec<Box<dyn Append>>,
154}
155
156impl DispatchBuilder<false> {
157    fn new() -> Self {
158        DispatchBuilder {
159            filters: vec![],
160            diagnostics: vec![],
161            appends: vec![],
162        }
163    }
164
165    /// Add a filter to this dispatch.
166    ///
167    /// # Examples
168    ///
169    /// ```
170    /// use logforth_core::append;
171    /// use logforth_core::record::Level;
172    /// use logforth_core::record::LevelFilter;
173    ///
174    /// logforth_core::builder()
175    ///     .dispatch(|d| {
176    ///         d.filter(LevelFilter::MoreSevereEqual(Level::Error))
177    ///             .append(append::Stderr::default())
178    ///     })
179    ///     .apply();
180    /// ```
181    pub fn filter(mut self, filter: impl Into<Box<dyn Filter>>) -> Self {
182        self.filters.push(filter.into());
183        self
184    }
185
186    /// Add a diagnostic to this dispatch.
187    ///
188    /// # Examples
189    ///
190    /// ```
191    /// use logforth_core::append;
192    /// use logforth_core::diagnostic;
193    /// use logforth_core::record::Level;
194    /// use logforth_core::record::LevelFilter;
195    ///
196    /// logforth_core::builder()
197    ///     .dispatch(|d| {
198    ///         d.filter(LevelFilter::MoreSevereEqual(Level::Error))
199    ///             .diagnostic(diagnostic::ThreadLocalDiagnostic::default())
200    ///             .append(append::Stderr::default())
201    ///     })
202    ///     .apply();
203    /// ```
204    pub fn diagnostic(mut self, diagnostic: impl Into<Box<dyn Diagnostic>>) -> Self {
205        self.diagnostics.push(diagnostic.into());
206        self
207    }
208}
209
210impl DispatchBuilder<true> {
211    fn build(self) -> Dispatch {
212        Dispatch::new(self.filters, self.diagnostics, self.appends)
213    }
214}
215
216impl<const APPEND: bool> DispatchBuilder<APPEND> {
217    /// Add an appender to this dispatch.
218    ///
219    /// # Examples
220    ///
221    /// ```
222    /// use logforth_core::append;
223    ///
224    /// logforth_core::builder()
225    ///     .dispatch(|d| d.append(append::Stdout::default()))
226    ///     .apply();
227    /// ```
228    pub fn append(mut self, append: impl Into<Box<dyn Append>>) -> DispatchBuilder<true> {
229        self.appends.push(append.into());
230        DispatchBuilder {
231            filters: self.filters,
232            diagnostics: self.diagnostics,
233            appends: self.appends,
234        }
235    }
236}