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::FormatterContext,
12    sink::{helper, Sink},
13    sync::*,
14    utils, Error, 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    common_impl: helper::CommonImpl,
32    file: SpinMutex<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] | [default error handler] |
43    /// |                 |                         |
44    /// | [path]          | *must be specified*     |
45    /// | [truncate]      | `false`                 |
46    ///
47    /// [level_filter]: FileSinkBuilder::level_filter
48    /// [formatter]: FileSinkBuilder::formatter
49    /// [error_handler]: FileSinkBuilder::error_handler
50    /// [default error handler]: error/index.html#default-error-handler
51    /// [path]: FileSinkBuilder::path
52    /// [truncate]: FileSinkBuilder::truncate
53    #[must_use]
54    pub fn builder() -> FileSinkBuilder<()> {
55        FileSinkBuilder {
56            path: (),
57            truncate: false,
58            common_builder_impl: helper::CommonBuilderImpl::new(),
59        }
60    }
61
62    /// Constructs a `FileSink`.
63    ///
64    /// If the parameter `truncate` is `true`, the existing contents of the file
65    /// will be discarded.
66    ///
67    /// # Error
68    ///
69    /// If an error occurs opening the file, [`Error::CreateDirectory`] or
70    /// [`Error::OpenFile`] will be returned.
71    #[deprecated(
72        since = "0.3.0",
73        note = "it may be removed in the future, use `FileSink::builder()` instead"
74    )]
75    pub fn new<P>(path: P, truncate: bool) -> Result<FileSink>
76    where
77        P: AsRef<Path>, /* Keep the `AsRef<Path>` instead of `Into<PathBuf>` for backward
78                         * compatible */
79    {
80        Self::builder()
81            .path(path.as_ref())
82            .truncate(truncate)
83            .build()
84    }
85}
86
87impl Sink for FileSink {
88    fn log(&self, record: &Record) -> Result<()> {
89        let mut string_buf = StringBuf::new();
90        let mut ctx = FormatterContext::new();
91        self.common_impl
92            .formatter
93            .read()
94            .format(record, &mut string_buf, &mut ctx)?;
95
96        self.file
97            .lock()
98            .write_all(string_buf.as_bytes())
99            .map_err(Error::WriteRecord)?;
100
101        Ok(())
102    }
103
104    fn flush(&self) -> Result<()> {
105        self.file.lock().flush().map_err(Error::FlushBuffer)
106    }
107
108    helper::common_impl!(@Sink: common_impl);
109}
110
111impl Drop for FileSink {
112    fn drop(&mut self) {
113        if let Err(err) = self.file.lock().flush() {
114            self.common_impl
115                .non_returnable_error("FileSink", Error::FlushBuffer(err))
116        }
117    }
118}
119
120// --------------------------------------------------
121
122/// #
123#[doc = include_str!("../include/doc/generic-builder-note.md")]
124pub struct FileSinkBuilder<ArgPath> {
125    common_builder_impl: helper::CommonBuilderImpl,
126    path: ArgPath,
127    truncate: bool,
128}
129
130impl<ArgPath> FileSinkBuilder<ArgPath> {
131    /// The path of the log file.
132    ///
133    /// This parameter is **required**.
134    #[must_use]
135    pub fn path<P>(self, path: P) -> FileSinkBuilder<PathBuf>
136    where
137        P: Into<PathBuf>,
138    {
139        FileSinkBuilder {
140            common_builder_impl: self.common_builder_impl,
141            path: path.into(),
142            truncate: self.truncate,
143        }
144    }
145
146    /// Truncates the contents when opening an existing file.
147    ///
148    /// If it is `true`, the existing contents of the file will be discarded.
149    ///
150    /// This parameter is **optional**.
151    #[must_use]
152    pub fn truncate(mut self, truncate: bool) -> Self {
153        self.truncate = truncate;
154        self
155    }
156
157    helper::common_impl!(@SinkBuilder: common_builder_impl);
158}
159
160impl FileSinkBuilder<()> {
161    #[doc(hidden)]
162    #[deprecated(note = "\n\n\
163        builder compile-time error:\n\
164        - missing required parameter `path`\n\n\
165    ")]
166    pub fn build(self, _: Infallible) {}
167}
168
169impl FileSinkBuilder<PathBuf> {
170    /// Builds a [`FileSink`].
171    ///
172    /// # Error
173    ///
174    /// If an error occurs opening the file, [`Error::CreateDirectory`] or
175    /// [`Error::OpenFile`] will be returned.
176    pub fn build(self) -> Result<FileSink> {
177        let file = utils::open_file(self.path, self.truncate)?;
178
179        let sink = FileSink {
180            common_impl: helper::CommonImpl::from_builder(self.common_builder_impl),
181            file: SpinMutex::new(BufWriter::new(file)),
182        };
183
184        Ok(sink)
185    }
186}