src/handlers/file_handler.rs

Lines

98.66 %

Functions

100.00 %

Regions

98.50 %

LineCountSource (jump to first uncovered line)
1
//
2
// File Name:    file_handler.rs
3
// Directory:    src/handlers
4
// Project Name: flogging
5
//
6
// Copyright (C) 2025 Bradley Willcott
7
//
8
// SPDX-License-Identifier: GPL-3.0-or-later
9
//
10
// This library (crate) is free software: you can redistribute it and/or modify
11
// it under the terms of the GNU General Public License as published by
12
// the Free Software Foundation, either version 3 of the License, or
13
// (at your option) any later version.
14
//
15
// This library (crate) is distributed in the hope that it will be useful,
16
// but WITHOUT ANY WARRANTY; without even the implied warranty of
17
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18
// GNU General Public License for more details.
19
//
20
// You should have received a copy of the GNU General Public License
21
// along with this library (crate).  If not, see <https://www.gnu.org/licenses/>.
22
//
23
24
//!
25
//! # FileHandler
26
//!
27
28
use std::{
29
    fmt,
30
    fs::{File, exists},
31
    io::{Error, ErrorKind::InvalidInput, Write},
32
};
33
34
use crate::*;
35
36
///
37
/// Publishes log entries to the file whose name was provided during
38
/// initialization.
39
///
40
#[derive(Debug, Default)]
41
pub struct FileHandler {
42
    filename: String,
43
    formatter: Formatter,
44
    file: Option<File>,
45
    writer: Option<Vec<u8>>,
46
}
47
48
impl FileHandler {
4920
    fn _create(filename: &str) -> Result<Self, Error> {
5020
        if filename.is_empty() {
511
            return Err(Error::new(InvalidInput, "'filename' must not be empty"));
5219
        }
53
5419
        let fh = FileHandler {
5519
            filename: filename.to_string(),
5619
            formatter: FormatType::Iso8601.create(None),
57
            file: {
5819
                let f = File::options().append(true).create(true).open(filename)?;
5919
                Some(f)
60
            },
6119
            writer: None,
62
        };
63
6419
        Ok(fh)
6520
    }
66
673
    fn log(&self) -> String {
683
        if let Some(w) = self.writer.to_owned() {
691
            String::from_utf8(w).unwrap()
70
        } else {
712
            String::new()
72
        }
733
    }
74
}
75
76
impl fmt::Display for FileHandler {
771
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
781
        write!(f, "{} : {}", self.filename, self.formatter)
791
    }
80
}
81
82
impl HandlerTrait for FileHandler {
83
    ///
84
    /// Create a new handler instance.
85
    ///
86
    /// ## Parameters
87
    /// - `name` - This the `filename` of the log file.
88
    ///
8920
    fn create(name: &str) -> Result<Self, Error> {
9020
        FileHandler::_create(name)
9120
    }
92
93
    ///
94
    /// Flushes and closes the file.\
95
    /// Also, removes the internal buffer, if in `test_mode`.\
96
    /// Will therefore, no longer be *in* `test_mode`.
97
    ///
983
    fn close(&mut self) {
993
        self.flush();
1003
        self.file = None;
1013
    }
102
1036
    fn flush(&mut self) {
1046
        if let Some(f) = &self.file {
1056
            f.sync_all().expect("sync_all failed");
1066
        }
1076
    }
108
1093
    fn get_formatter(&self) -> Formatter {
1103
        self.formatter.clone()
1113
    }
112
1133
    fn get_log(&self) -> String {
1143
        self.log()
1153
    }
116
11753
    fn is_open(&self) -> bool {
11853
        self.file.is_some()
11953
    }
120
12149
    fn publish(&mut self, log_entry: &LogEntry) {
12249
        if self.is_open() {
12348
            let mut buf = self.formatter.format(log_entry);
12448
            buf.push('\n');
125
1262
            if let Some(w) = self.writer.as_mut() {
1272
                writeln!(w, "{}", self.formatter.format(log_entry)).expect("writeln!() failed");
12846
            } else {
12946
                self.file
13046
                    .as_mut()
13146
                    .unwrap()
13246
                    .write_all(buf.as_bytes())
13346
                    .expect("write_all() failed");
13446
            }
1351
        }
13649
    }
137
1383
    fn set_formatter(&mut self, formatter: Formatter) {
1393
        self.formatter = formatter;
1403
    }
141
142
    ///
143
    /// Sets the test mode to `state`.
144
    ///
145
    /// If set to `true`, use `get_log()` to obtain the
146
    /// log.
147
    ///
1483
    fn set_test_mode(&mut self, state: bool) {
1491
        if state {
1501
            // true
1511
            self.writer = Some(Vec::new());
1522
        } else {
1532
            self.writer = None;
1542
        }
1553
    }
156
}
157
158
#[cfg(test)]
159
mod tests {
160
    use crate::*;
161
    use std::{
162
        fs::File,
163
        io::{Error, Read, Result},
164
    };
165
166
    #[test]
1671
    fn file_handler() {
1681
        let mut log = Logger::file_logger(module_path!(), "test_logs/file_handler.log");
1691
        log.set_fn_name("file_handler");
170
1711
        let h = log.get_handler(crate::Handler::File).unwrap();
1721
        h.set_test_mode(false);
173
1741
        assert!(h.is_open());
1751
        assert_eq!(
1761
            h.get_formatter().to_string(),
1771
            "dt_fmt: \"%+\" - fmt_string: \"{dt:35} {mod_path}->{fn_name} [{level:7}] {message}\""
1781
                .to_string()
179
        );
180
1811
        log.info("trait methods");
1821
        log.warning("The sky is falling!");
183
1841
        let h = log.get_handler(crate::Handler::File).unwrap();
185
1861
        assert_eq!(h.get_log(), "".to_string());
187
1881
        h.flush();
1891
        h.close();
1901
        log.exiting_with("This should get thrown away.");
1911
    }
192
193
    #[test]
1941
    fn file_handler_file_test() {
1951
        let expected = "flogging::handlers::file_handler::tests->file_handler_file_test [INFO   ] trait methods
1961
flogging::handlers::file_handler::tests->file_handler_file_test [WARNING] The sky is falling!\n"
1971
            .to_string();
198
1991
        let mut log = Logger::builder(module_path!())
2001
            .set_fn_name("file_handler_file_test")
2011
            .remove_file("test_logs/file_handler_file_test.log")
2021
            .add_file_handler_with(
2031
                "test_logs/file_handler_file_test.log",
2041
                FormatType::Simple,
2051
                None,
206
            )
2071
            .build();
208
2091
        let h = log.get_handler(crate::Handler::File).unwrap();
2101
        h.set_test_mode(false);
211
2121
        assert!(h.is_open());
2131
        assert_eq!(
2141
            h.get_formatter().to_string(),
2151
            "dt_fmt: \"\" - fmt_string: \"{mod_path}->{fn_name} [{level:7}] {message}\""
2161
                .to_string()
217
        );
218
2191
        log.info("trait methods");
2201
        log.warning("The sky is falling!");
221
2221
        let h = log.get_handler(crate::Handler::File).unwrap();
223
2241
        assert_eq!(h.get_log(), "".to_string());
225
2261
        h.flush();
2271
        h.close();
2281
        assert!(!h.is_open());
229
2301
        log.severe("This should get thrown away.");
231
2321
        if let Ok(mut file) = File::open("test_logs/file_handler_file_test.log") {
2331
            let mut buf = String::new();
2341
            if let Ok(_count) = file.read_to_string(&mut buf) {
2351
                assert_eq!(expected, buf);
2360
            }
2370
        }
2381
    }
239
240
    #[test]
2411
    fn file_handler_test_mode() {
2421
        let expected = "flogging::handlers::file_handler::tests->file_handler_test_mode [INFO   ] trait methods
2431
flogging::handlers::file_handler::tests->file_handler_test_mode [WARNING] The sky is falling!\n"
2441
            .to_string();
245
2461
        let mut log = Logger::builder(module_path!())
2471
            .set_fn_name("file_handler_test_mode")
2481
            .remove_file("test_logs/file_handler_test_mode.log")
2491
            .add_file_handler_with(
2501
                "test_logs/file_handler_test_mode.log",
2511
                FormatType::Simple,
2521
                None,
253
            )
2541
            .build();
255
2561
        let h = log.get_handler(crate::Handler::File).unwrap();
2571
        h.set_test_mode(true);
258
2591
        assert!(h.is_open());
2601
        assert_eq!(
2611
            h.get_formatter().to_string(),
2621
            "dt_fmt: \"\" - fmt_string: \"{mod_path}->{fn_name} [{level:7}] {message}\""
2631
                .to_string()
264
        );
265
2661
        log.info("trait methods");
2671
        log.warning("The sky is falling!");
268
2691
        let h = log.get_handler(crate::Handler::File).unwrap();
2701
        let buf = h.get_log();
271
2721
        assert_eq!(expected, buf);
273
2741
        h.flush();
2751
        h.close();
2761
    }
277
278
    #[test]
279
    #[should_panic(expected = "'filename' must not be empty")]
2801
    fn filename_empty() {
2811
        let _ = Logger::file_logger(module_path!(), "");
2821
    }
283
}