Coverage Report

Created: 2026-01-30 06:08

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/logforth-append-file-0.3.0/src/append.rs
Line
Count
Source
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
15
use std::io::Write;
16
use std::num::NonZeroUsize;
17
use std::path::PathBuf;
18
use std::sync::Mutex;
19
use std::sync::MutexGuard;
20
21
use logforth_core::Diagnostic;
22
use logforth_core::Error;
23
use logforth_core::Layout;
24
use logforth_core::Trap;
25
use logforth_core::append::Append;
26
use logforth_core::layout::PlainTextLayout;
27
use logforth_core::record::Record;
28
29
use crate::rolling::RollingFileWriter;
30
use crate::rolling::RollingFileWriterBuilder;
31
use crate::rotation::Rotation;
32
33
/// A builder to configure and create an [`File`] appender.
34
#[derive(Debug)]
35
pub struct FileBuilder {
36
    builder: RollingFileWriterBuilder,
37
    layout: Box<dyn Layout>,
38
}
39
40
impl FileBuilder {
41
    /// Create a new file appender builder.
42
0
    pub fn new(basedir: impl Into<PathBuf>, filename: impl Into<String>) -> Self {
43
0
        Self {
44
0
            builder: RollingFileWriterBuilder::new(basedir, filename),
45
0
            layout: Box::new(PlainTextLayout::default()),
46
0
        }
47
0
    }
48
49
    /// Build the [`File`] appender.
50
    ///
51
    /// # Errors
52
    ///
53
    /// Return an error if either:
54
    ///
55
    /// * The log directory cannot be created.
56
    /// * The configured filename is empty.
57
0
    pub fn build(self) -> Result<File, Error> {
58
0
        let FileBuilder { builder, layout } = self;
59
0
        let writer = builder.build()?;
60
0
        Ok(File::new(writer, layout))
61
0
    }
62
63
    /// Set the layout for the logs.
64
    ///
65
    /// Default to [`PlainTextLayout`].
66
    ///
67
    /// # Examples
68
    ///
69
    /// ```
70
    /// use logforth_append_file::FileBuilder;
71
    /// use logforth_layout_json::JsonLayout;
72
    ///
73
    /// let builder = FileBuilder::new("my_service", "my_app");
74
    /// builder.layout(JsonLayout::default());
75
    /// ```
76
0
    pub fn layout(mut self, layout: impl Into<Box<dyn Layout>>) -> Self {
77
0
        self.layout = layout.into();
78
0
        self
79
0
    }
80
81
    /// Set the trap for the file writer.
82
    ///
83
    /// Default to [`BestEffortTrap`].
84
    ///
85
    /// # Examples
86
    ///
87
    /// ```
88
    /// use logforth_append_file::FileBuilder;
89
    /// use logforth_core::trap::BestEffortTrap;
90
    ///
91
    /// let builder = FileBuilder::new("my_service", "my_app");
92
    /// builder.trap(BestEffortTrap::default());
93
    /// ```
94
    ///
95
    /// [`BestEffortTrap`]: logforth_core::trap::BestEffortTrap
96
0
    pub fn trap(mut self, trap: impl Into<Box<dyn Trap>>) -> Self {
97
0
        self.builder = self.builder.trap(trap);
98
0
        self
99
0
    }
100
101
    /// Set the rotation strategy to roll over log files minutely.
102
0
    pub fn rollover_minutely(mut self) -> Self {
103
0
        self.builder = self.builder.rotation(Rotation::Minutely);
104
0
        self
105
0
    }
106
107
    /// Set the rotation strategy to roll over log files hourly.
108
0
    pub fn rollover_hourly(mut self) -> Self {
109
0
        self.builder = self.builder.rotation(Rotation::Hourly);
110
0
        self
111
0
    }
112
113
    /// Set the rotation strategy to roll over log files daily at 00:00 in the local time zone.
114
0
    pub fn rollover_daily(mut self) -> Self {
115
0
        self.builder = self.builder.rotation(Rotation::Daily);
116
0
        self
117
0
    }
118
119
    /// Set the rotation strategy to roll over log files if the current log file exceeds the given
120
    /// size.
121
    ///
122
    /// If any time-based rotation strategy is set, the size-based rotation will be checked on the
123
    /// current log file after the time-based rotation check.
124
0
    pub fn rollover_size(mut self, n: NonZeroUsize) -> Self {
125
0
        self.builder = self.builder.max_file_size(n);
126
0
        self
127
0
    }
128
129
    /// Set the filename suffix.
130
0
    pub fn filename_suffix(mut self, suffix: impl Into<String>) -> Self {
131
0
        self.builder = self.builder.filename_suffix(suffix);
132
0
        self
133
0
    }
134
135
    /// Set the maximum number of log files to keep.
136
0
    pub fn max_log_files(mut self, n: NonZeroUsize) -> Self {
137
0
        self.builder = self.builder.max_log_files(n);
138
0
        self
139
0
    }
140
}
141
142
/// An appender that writes log records to rolling files.
143
#[derive(Debug)]
144
pub struct File {
145
    writer: Mutex<RollingFileWriter>,
146
    layout: Box<dyn Layout>,
147
}
148
149
impl File {
150
0
    fn new(writer: RollingFileWriter, layout: Box<dyn Layout>) -> Self {
151
0
        let writer = Mutex::new(writer);
152
0
        Self { writer, layout }
153
0
    }
154
155
0
    fn writer(&self) -> MutexGuard<'_, RollingFileWriter> {
156
0
        self.writer.lock().unwrap_or_else(|e| e.into_inner())
157
0
    }
158
}
159
160
impl Append for File {
161
0
    fn append(&self, record: &Record, diags: &[Box<dyn Diagnostic>]) -> Result<(), Error> {
162
0
        let mut bytes = self.layout.format(record, diags)?;
163
0
        bytes.push(b'\n');
164
0
        let mut writer = self.writer();
165
0
        writer.write_all(&bytes).map_err(Error::from_io_error)?;
166
0
        Ok(())
167
0
    }
168
169
0
    fn flush(&self) -> Result<(), Error> {
170
0
        let mut writer = self.writer();
171
0
        writer.flush().map_err(Error::from_io_error)?;
172
0
        Ok(())
173
0
    }
174
}
175
176
impl Drop for File {
177
0
    fn drop(&mut self) {
178
0
        let writer = self.writer.get_mut().unwrap_or_else(|e| e.into_inner());
179
0
        let _ = writer.flush();
180
0
    }
181
}