spdlog/sink/
file_sink.rs

1//! Provides a file sink.
2
3use std::{
4    convert::Infallible,
5    fs::File,
6    io::{BufWriter, Write},
7    path::{Path, PathBuf},
8};
9
10use crate::{
11    formatter::{Formatter, FormatterContext},
12    sink::{GetSinkProp, Sink, SinkProp},
13    sync::*,
14    utils, Error, ErrorHandler, LevelFilter, Record, Result, StringBuf,
15};
16
17/// A sink with a file as the target.
18///
19/// It writes logs to a single file. If you want to automatically rotate into
20/// multiple files, see  [`RotatingFileSink`].
21///
22/// The file and directories will be created recursively if they do not exist.
23///
24/// # Examples
25///
26/// See [./examples] directory.
27///
28/// [`RotatingFileSink`]: crate::sink::RotatingFileSink
29/// [./examples]: https://github.com/SpriteOvO/spdlog-rs/tree/main/spdlog/examples
30pub struct FileSink {
31    prop: SinkProp,
32    file: Mutex<BufWriter<File>>,
33}
34
35impl FileSink {
36    /// Gets a builder of `FileSink` with default parameters:
37    ///
38    /// | Parameter       | Default Value               |
39    /// |-----------------|-----------------------------|
40    /// | [level_filter]  | `All`                       |
41    /// | [formatter]     | `FullFormatter`             |
42    /// | [error_handler] | [`ErrorHandler::default()`] |
43    /// |                 |                             |
44    /// | [path]          | *must be specified*         |
45    /// | [truncate]      | `false`                     |
46    /// | [capacity]      | consistent with `std`       |
47    ///
48    /// [level_filter]: FileSinkBuilder::level_filter
49    /// [formatter]: FileSinkBuilder::formatter
50    /// [error_handler]: FileSinkBuilder::error_handler
51    /// [`ErrorHandler::default()`]: crate::error::ErrorHandler::default()
52    /// [path]: FileSinkBuilder::path
53    /// [truncate]: FileSinkBuilder::truncate
54    /// [capacity]: FileSinkBuilder::capacity
55    #[must_use]
56    pub fn builder() -> FileSinkBuilder<()> {
57        FileSinkBuilder {
58            prop: SinkProp::default(),
59            path: (),
60            truncate: false,
61            capacity: None,
62        }
63    }
64
65    /// Constructs a `FileSink`.
66    ///
67    /// If the parameter `truncate` is `true`, the existing contents of the file
68    /// will be discarded.
69    ///
70    /// # Error
71    ///
72    /// If an error occurs opening the file, [`Error::CreateDirectory`] or
73    /// [`Error::OpenFile`] will be returned.
74    #[deprecated(
75        since = "0.3.0",
76        note = "it may be removed in the future, use `FileSink::builder()` instead"
77    )]
78    pub fn new<P>(path: P, truncate: bool) -> Result<FileSink>
79    where
80        P: AsRef<Path>, /* Keep the `AsRef<Path>` instead of `Into<PathBuf>` for backward
81                         * compatible */
82    {
83        Self::builder()
84            .path(path.as_ref())
85            .truncate(truncate)
86            .build()
87    }
88}
89
90impl GetSinkProp for FileSink {
91    fn prop(&self) -> &SinkProp {
92        &self.prop
93    }
94}
95
96impl Sink for FileSink {
97    fn log(&self, record: &Record) -> Result<()> {
98        let mut string_buf = StringBuf::new();
99        let mut ctx = FormatterContext::new();
100        self.prop
101            .formatter()
102            .format(record, &mut string_buf, &mut ctx)?;
103
104        self.file
105            .lock_expect()
106            .write_all(string_buf.as_bytes())
107            .map_err(Error::WriteRecord)?;
108
109        Ok(())
110    }
111
112    fn flush(&self) -> Result<()> {
113        self.file.lock_expect().flush().map_err(Error::FlushBuffer)
114    }
115}
116
117impl Drop for FileSink {
118    fn drop(&mut self) {
119        if let Err(err) = self.file.lock_expect().flush() {
120            self.prop
121                .call_error_handler_internal("FileSink", Error::FlushBuffer(err))
122        }
123    }
124}
125
126// --------------------------------------------------
127
128/// #
129#[doc = include_str!("../include/doc/generic-builder-note.md")]
130pub struct FileSinkBuilder<ArgPath> {
131    prop: SinkProp,
132    path: ArgPath,
133    truncate: bool,
134    capacity: Option<usize>,
135}
136
137impl<ArgPath> FileSinkBuilder<ArgPath> {
138    /// The path of the log file.
139    ///
140    /// This parameter is **required**.
141    #[must_use]
142    pub fn path<P>(self, path: P) -> FileSinkBuilder<PathBuf>
143    where
144        P: Into<PathBuf>,
145    {
146        FileSinkBuilder {
147            prop: self.prop,
148            path: path.into(),
149            truncate: self.truncate,
150            capacity: self.capacity,
151        }
152    }
153
154    /// Truncates the contents when opening an existing file.
155    ///
156    /// If it is `true`, the existing contents of the file will be discarded.
157    ///
158    /// This parameter is **optional**.
159    #[must_use]
160    pub fn truncate(mut self, truncate: bool) -> Self {
161        self.truncate = truncate;
162        self
163    }
164
165    /// Specifies the internal buffer capacity.
166    ///
167    /// This parameter is **optional**.
168    #[must_use]
169    pub fn capacity(mut self, capacity: usize) -> Self {
170        self.capacity = Some(capacity);
171        self
172    }
173
174    // Prop
175    //
176
177    /// Specifies a log level filter.
178    ///
179    /// This parameter is **optional**.
180    #[must_use]
181    pub fn level_filter(self, level_filter: LevelFilter) -> Self {
182        self.prop.set_level_filter(level_filter);
183        self
184    }
185
186    /// Specifies a formatter.
187    ///
188    /// This parameter is **optional**.
189    #[must_use]
190    pub fn formatter<F>(self, formatter: F) -> Self
191    where
192        F: Formatter + 'static,
193    {
194        self.prop.set_formatter(formatter);
195        self
196    }
197
198    /// Specifies an error handler.
199    ///
200    /// This parameter is **optional**.
201    #[must_use]
202    pub fn error_handler<F: Into<ErrorHandler>>(self, handler: F) -> Self {
203        self.prop.set_error_handler(handler);
204        self
205    }
206}
207
208impl FileSinkBuilder<()> {
209    #[doc(hidden)]
210    #[deprecated(note = "\n\n\
211        builder compile-time error:\n\
212        - missing required parameter `path`\n\n\
213    ")]
214    pub fn build(self, _: Infallible) {}
215}
216
217impl FileSinkBuilder<PathBuf> {
218    /// Builds a [`FileSink`].
219    ///
220    /// # Error
221    ///
222    /// If an error occurs opening the file, [`Error::CreateDirectory`] or
223    /// [`Error::OpenFile`] will be returned.
224    pub fn build(self) -> Result<FileSink> {
225        let file = utils::open_file_bufw(self.path, self.truncate, self.capacity)?;
226
227        let sink = FileSink {
228            prop: self.prop,
229            file: Mutex::new(file),
230        };
231
232        Ok(sink)
233    }
234}