logforth_core/logger/
log_impl.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 std::io::Write;
16use std::panic;
17use std::sync::Once;
18use std::sync::OnceLock;
19
20use crate::Append;
21use crate::Diagnostic;
22use crate::Error;
23use crate::Filter;
24use crate::filter::FilterResult;
25use crate::record::Metadata;
26use crate::record::Record;
27
28static DEFAULT_LOGGER: OnceLock<Logger> = OnceLock::new();
29
30/// Return the default global logger instance.
31///
32/// If no default logger has been set, `None` is returned.
33pub fn default_logger() -> &'static Logger {
34    static NOP_LOGGER: Logger = Logger { dispatches: vec![] };
35    DEFAULT_LOGGER.get().unwrap_or(&NOP_LOGGER)
36}
37
38/// Set the default global logger instance.
39///
40/// If a default logger has already been set, the function returns the provided logger
41/// as an error.
42pub fn set_default_logger(logger: Logger) -> Result<(), Logger> {
43    static ATEXIT_CALLBACK: Once = Once::new();
44
45    DEFAULT_LOGGER.set(logger)?;
46    ATEXIT_CALLBACK.call_once(flush_default_logger_at_exit);
47    Ok(())
48}
49
50fn flush_default_logger_at_exit() {
51    // Rust never calls `drop` for static variables.
52    //
53    // Setting up an exit handler gives us a chance to flush the default logger
54    // once at the program exit, thus we don't lose the last logs.
55
56    extern "C" fn handler() {
57        if let Some(default_logger) = DEFAULT_LOGGER.get() {
58            default_logger.exit();
59        }
60    }
61
62    #[must_use]
63    fn try_atexit() -> bool {
64        use std::os::raw::c_int;
65
66        unsafe extern "C" {
67            fn atexit(cb: extern "C" fn()) -> c_int;
68        }
69
70        (unsafe { atexit(handler) }) == 0
71    }
72
73    fn hook_panic() {
74        let previous_hook = panic::take_hook();
75
76        panic::set_hook(Box::new(move |info| {
77            handler();
78            previous_hook(info);
79        }));
80    }
81
82    if !try_atexit() {
83        // if we failed to register the `atexit` handler, at least we hook into panic
84        hook_panic();
85    }
86}
87
88/// A logger that dispatches log records to one or more dispatcher.
89#[derive(Debug)]
90pub struct Logger {
91    dispatches: Vec<Dispatch>,
92}
93
94impl Logger {
95    pub(super) fn new(dispatches: Vec<Dispatch>) -> Self {
96        Self { dispatches }
97    }
98}
99
100impl Logger {
101    /// Determine if a log message with the specified metadata would be logged.
102    pub fn enabled(&self, metadata: &Metadata) -> bool {
103        self.dispatches
104            .iter()
105            .any(|dispatch| dispatch.enabled(metadata))
106    }
107
108    /// Log the [`Record`].
109    pub fn log(&self, record: &Record) {
110        for dispatch in &self.dispatches {
111            for err in dispatch.log(record) {
112                handle_log_error(record, &err);
113            }
114        }
115    }
116
117    /// Flush any buffered records.
118    pub fn flush(&self) {
119        for dispatch in &self.dispatches {
120            for err in dispatch.flush() {
121                handle_flush_error(&err);
122            }
123        }
124    }
125
126    /// Perform any cleanup work before the program exits.
127    pub fn exit(&self) {
128        for dispatch in &self.dispatches {
129            for err in dispatch.exit() {
130                handle_exit_error(&err);
131            }
132        }
133    }
134}
135
136/// A grouped set of appenders and filters.
137///
138/// The [`Logger`] facade dispatches log records to one or more [`Dispatch`] instances.
139/// Each [`Dispatch`] instance contains a set of filters and appenders.
140///
141/// `filters` are used to determine whether a log record should be passed to the appenders.
142/// `appends` are used to write log records to a destination.
143#[derive(Debug)]
144pub(super) struct Dispatch {
145    filters: Vec<Box<dyn Filter>>,
146    diagnostics: Vec<Box<dyn Diagnostic>>,
147    appends: Vec<Box<dyn Append>>,
148}
149
150impl Dispatch {
151    pub(super) fn new(
152        filters: Vec<Box<dyn Filter>>,
153        diagnostics: Vec<Box<dyn Diagnostic>>,
154        appends: Vec<Box<dyn Append>>,
155    ) -> Self {
156        debug_assert!(
157            !appends.is_empty(),
158            "A Dispatch must have at least one filter"
159        );
160
161        Self {
162            filters,
163            diagnostics,
164            appends,
165        }
166    }
167
168    fn enabled(&self, metadata: &Metadata) -> bool {
169        let diagnostics = &self.diagnostics;
170
171        for filter in &self.filters {
172            match filter.enabled(metadata, diagnostics) {
173                FilterResult::Reject => return false,
174                FilterResult::Accept => return true,
175                FilterResult::Neutral => {}
176            }
177        }
178
179        true
180    }
181
182    fn log(&self, record: &Record) -> Vec<Error> {
183        let diagnostics = &self.diagnostics;
184
185        for filter in &self.filters {
186            match filter.matches(record, diagnostics) {
187                FilterResult::Reject => return vec![],
188                FilterResult::Accept => break,
189                FilterResult::Neutral => {}
190            }
191        }
192
193        let mut errors = vec![];
194        for append in &self.appends {
195            if let Err(err) = append.append(record, diagnostics) {
196                errors.push(err);
197            }
198        }
199        errors
200    }
201
202    fn flush(&self) -> Vec<Error> {
203        let mut errors = vec![];
204        for append in &self.appends {
205            if let Err(err) = append.flush() {
206                errors.push(err);
207            }
208        }
209        errors
210    }
211
212    fn exit(&self) -> Vec<Error> {
213        let mut errors = vec![];
214        for append in &self.appends {
215            if let Err(err) = append.exit() {
216                errors.push(err);
217            }
218        }
219        errors
220    }
221}
222
223fn handle_log_error(record: &Record, error: &Error) {
224    let Err(fallback_error) = write!(
225        std::io::stderr(),
226        r###"
227Error perform logging.
228    Attempted to log: {args}
229    Record: {record:?}
230    Error: {error:?}
231"###,
232        args = record.payload(),
233        record = record,
234        error = error,
235    ) else {
236        return;
237    };
238
239    panic!(
240        r###"
241Error performing stderr logging after error occurred during regular logging.
242    Attempted to log: {args}
243    Record: {record:?}
244    Error: {error:?}
245    Fallback error: {fallback_error}
246"###,
247        args = record.payload(),
248        record = record,
249        error = error,
250        fallback_error = fallback_error,
251    );
252}
253
254fn handle_flush_error(error: &Error) {
255    let Err(fallback_error) = write!(
256        std::io::stderr(),
257        r###"
258Error perform flush.
259    Error: {error:?}
260"###,
261    ) else {
262        return;
263    };
264
265    panic!(
266        r###"
267Error performing stderr logging after error occurred during regular flush.
268    Error: {error:?}
269    Fallback error: {fallback_error}
270"###,
271    );
272}
273
274fn handle_exit_error(error: &Error) {
275    let Err(fallback_error) = write!(
276        std::io::stderr(),
277        r###"
278Error perform exit.
279    Error: {error:?}
280"###,
281    ) else {
282        return;
283    };
284
285    panic!(
286        r###"
287Error performing stderr logging after error occurred during atexit.
288    Error: {error:?}
289    Fallback error: {fallback_error}
290"###,
291    );
292}