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]  | [`LevelFilter::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    /// [`FullFormatter`]: crate::formatter::FullFormatter
51    /// [error_handler]: FileSinkBuilder::error_handler
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**, and defaults to `false`.
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**, and defaults to the value consistent
168    /// with `std`.
169    #[must_use]
170    pub fn capacity(mut self, capacity: usize) -> Self {
171        self.capacity = Some(capacity);
172        self
173    }
174
175    // Prop
176    //
177
178    /// Specifies a log level filter.
179    ///
180    /// This parameter is **optional**, and defaults to [`LevelFilter::All`].
181    #[must_use]
182    pub fn level_filter(self, level_filter: LevelFilter) -> Self {
183        self.prop.set_level_filter(level_filter);
184        self
185    }
186
187    /// Specifies a formatter.
188    ///
189    /// This parameter is **optional**, and defaults to [`FullFormatter`].
190    ///
191    /// [`FullFormatter`]: crate::formatter::FullFormatter
192    #[must_use]
193    pub fn formatter<F>(self, formatter: F) -> Self
194    where
195        F: Formatter + 'static,
196    {
197        self.prop.set_formatter(formatter);
198        self
199    }
200
201    /// Specifies an error handler.
202    ///
203    /// This parameter is **optional**, and defaults to
204    /// [`ErrorHandler::default()`].
205    #[must_use]
206    pub fn error_handler<F: Into<ErrorHandler>>(self, handler: F) -> Self {
207        self.prop.set_error_handler(handler);
208        self
209    }
210}
211
212impl FileSinkBuilder<()> {
213    #[doc(hidden)]
214    #[deprecated(note = "\n\n\
215        builder compile-time error:\n\
216        - missing required parameter `path`\n\n\
217    ")]
218    pub fn build(self, _: Infallible) {}
219
220    #[doc(hidden)]
221    #[deprecated(note = "\n\n\
222        builder compile-time error:\n\
223        - missing required parameter `path`\n\n\
224    ")]
225    pub fn build_arc(self, _: Infallible) {}
226}
227
228impl FileSinkBuilder<PathBuf> {
229    /// Builds a [`FileSink`].
230    ///
231    /// # Error
232    ///
233    /// If an error occurs opening the file, [`Error::CreateDirectory`] or
234    /// [`Error::OpenFile`] will be returned.
235    pub fn build(self) -> Result<FileSink> {
236        let file = utils::open_file_bufw(self.path, self.truncate, self.capacity)?;
237
238        let sink = FileSink {
239            prop: self.prop,
240            file: Mutex::new(file),
241        };
242
243        Ok(sink)
244    }
245
246    /// Builds a `Arc<FileSink>`.
247    ///
248    /// This is a shorthand method for `.build().map(Arc::new)`.
249    pub fn build_arc(self) -> Result<Arc<FileSink>> {
250        self.build().map(Arc::new)
251    }
252}