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    /// use logforth_core::record::RecordBuilder;
82    ///
83    /// let l = logforth_core::builder().build();
84    /// let r = RecordBuilder::default().payload("hello world!").build();
85    /// l.log(&r);
86    /// ```
87    pub fn build(self) -> Logger {
88        Logger::new(self.dispatches)
89    }
90
91    /// Set up the global logger with all the configured dispatches.
92    ///
93    /// This should be called early in the execution of a Rust program. Any log events that occur
94    /// before initialization will be ignored.
95    ///
96    /// # Errors
97    ///
98    /// Return an error if a global logger has already been set.
99    ///
100    /// # Examples
101    ///
102    /// ```
103    /// if logforth_core::builder().try_apply().is_err() {
104    ///     eprintln!("failed to set logger");
105    /// }
106    /// ```
107    pub fn try_apply(self) -> Result<(), Logger> {
108        set_default_logger(self.build())
109    }
110
111    /// Set up the global logger with all the configured dispatches.
112    ///
113    /// This should be called early in the execution of a Rust program. Any log events that occur
114    /// before initialization will be ignored.
115    ///
116    /// This function will panic if it is called more than once, or if another library has already
117    /// initialized a global logger.
118    ///
119    /// # Panics
120    ///
121    /// Panic if the global logger has already been set.
122    ///
123    /// # Examples
124    ///
125    /// ```
126    /// logforth_core::builder().apply();
127    /// ```
128    pub fn apply(self) {
129        self.try_apply()
130            .expect("LoggerBuilder::apply must be called before the global logger initialized");
131    }
132}
133
134/// A builder for configuring a log dispatch, including filters and appenders.
135///
136/// # Examples
137///
138/// ```
139/// use logforth_core::append;
140/// use logforth_core::record::LevelFilter;
141///
142/// logforth_core::builder()
143///     .dispatch(|d| {
144///         d.filter(LevelFilter::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::LevelFilter;
172    ///
173    /// logforth_core::builder()
174    ///     .dispatch(|d| {
175    ///         d.filter(LevelFilter::Error)
176    ///             .append(append::Stderr::default())
177    ///     })
178    ///     .apply();
179    /// ```
180    pub fn filter(mut self, filter: impl Into<Box<dyn Filter>>) -> Self {
181        self.filters.push(filter.into());
182        self
183    }
184
185    /// Add a diagnostic to this dispatch.
186    ///
187    /// # Examples
188    ///
189    /// ```
190    /// use logforth_core::append;
191    /// use logforth_core::diagnostic;
192    /// use logforth_core::record::LevelFilter;
193    ///
194    /// logforth_core::builder()
195    ///     .dispatch(|d| {
196    ///         d.filter(LevelFilter::Error)
197    ///             .diagnostic(diagnostic::ThreadLocalDiagnostic::default())
198    ///             .append(append::Stderr::default())
199    ///     })
200    ///     .apply();
201    /// ```
202    pub fn diagnostic(mut self, diagnostic: impl Into<Box<dyn Diagnostic>>) -> Self {
203        self.diagnostics.push(diagnostic.into());
204        self
205    }
206}
207
208impl DispatchBuilder<true> {
209    fn build(self) -> Dispatch {
210        Dispatch::new(self.filters, self.diagnostics, self.appends)
211    }
212}
213
214impl<const APPEND: bool> DispatchBuilder<APPEND> {
215    /// Add an appender to this dispatch.
216    ///
217    /// # Examples
218    ///
219    /// ```
220    /// use logforth_core::append;
221    ///
222    /// logforth_core::builder()
223    ///     .dispatch(|d| d.append(append::Stdout::default()))
224    ///     .apply();
225    /// ```
226    pub fn append(mut self, append: impl Into<Box<dyn Append>>) -> DispatchBuilder<true> {
227        self.appends.push(append.into());
228        DispatchBuilder {
229            filters: self.filters,
230            diagnostics: self.diagnostics,
231            appends: self.appends,
232        }
233    }
234}