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}