spdlog/sink/
write_sink.rs

1use std::{convert::Infallible, io::Write, marker::PhantomData};
2
3use crate::{
4    formatter::{Formatter, FormatterContext},
5    sink::{GetSinkProp, Sink, SinkProp},
6    sync::*,
7    Error, ErrorHandler, LevelFilter, Record, Result, StringBuf,
8};
9
10/// A sink that writes log messages into an arbitrary `impl Write` object.
11///
12/// # Performance Notice
13///
14/// Since `WriteSink` can write into any `impl Write` objects, the assumptions
15/// made on the underlying `impl Write` object is very weak and this does impact
16/// performance. You should use other sinks or implement your own sinks whenever
17/// possible. `WriteSink` is your last resort if no other sinks meet your
18/// requirement.
19///
20/// If you want to log into a file, use [`FileSink`] or [`RotatingFileSink`]
21/// instead.
22///
23/// If you want to log into the standard streams, use [`StdStreamSink`] instead.
24///
25/// [`FileSink`]: crate::sink::FileSink
26/// [`RotatingFileSink`]: crate::sink::RotatingFileSink
27/// [`StdStreamSink`]: crate::sink::StdStreamSink
28pub struct WriteSink<W>
29where
30    W: Write + Send,
31{
32    prop: SinkProp,
33    target: Mutex<W>,
34}
35
36impl<W> WriteSink<W>
37where
38    W: Write + Send,
39{
40    /// Gets a builder of `WriteSink` with default parameters:
41    ///
42    /// | Parameter         | Default Value               |
43    /// |-------------------|-----------------------------|
44    /// | [level_filter]    | [`LevelFilter::All`]        |
45    /// | [formatter]       | [`FullFormatter`]           |
46    /// | [error_handler]   | [`ErrorHandler::default()`] |
47    /// |                   |                             |
48    /// | [target]          | *must be specified*         |
49    ///
50    /// [level_filter]: WriteSinkBuilder::level_filter
51    /// [formatter]: WriteSinkBuilder::formatter
52    /// [`FullFormatter`]: crate::formatter::FullFormatter
53    /// [error_handler]: WriteSinkBuilder::error_handler
54    /// [target]: WriteSinkBuilder::target
55    #[must_use]
56    pub fn builder() -> WriteSinkBuilder<W, ()> {
57        WriteSinkBuilder {
58            prop: SinkProp::default(),
59            target: None,
60            _phantom: PhantomData,
61        }
62    }
63
64    /// Invoke a callback function with the underlying `impl Write` object.
65    ///
66    /// This function returns whatever the given callback function returns.
67    ///
68    /// Note that this sink cannot write into the underlying `impl Write` object
69    /// while the given callback function is running. If the underlying
70    /// `impl Write` object supports a relatively cheap `clone` operation,
71    /// consider using the [`clone_target`] method.
72    ///
73    /// [`clone_target`]: Self::clone_target
74    #[must_use]
75    pub fn with_target<F, R>(&self, callback: F) -> R
76    where
77        F: FnOnce(&mut W) -> R,
78    {
79        callback(&mut *self.lock_target())
80    }
81
82    fn lock_target(&self) -> MutexGuard<'_, W> {
83        self.target.lock_expect()
84    }
85}
86
87impl<W> WriteSink<W>
88where
89    W: Write + Send + Clone,
90{
91    /// Clone the underlying `impl Write` object.
92    #[must_use]
93    pub fn clone_target(&self) -> W {
94        self.lock_target().clone()
95    }
96}
97
98impl<W> GetSinkProp for WriteSink<W>
99where
100    W: Write + Send,
101{
102    fn prop(&self) -> &SinkProp {
103        &self.prop
104    }
105}
106
107impl<W> Sink for WriteSink<W>
108where
109    W: Write + Send,
110{
111    fn log(&self, record: &Record) -> Result<()> {
112        let mut string_buf = StringBuf::new();
113        let mut ctx = FormatterContext::new();
114        self.prop
115            .formatter()
116            .format(record, &mut string_buf, &mut ctx)?;
117
118        self.lock_target()
119            .write_all(string_buf.as_bytes())
120            .map_err(Error::WriteRecord)?;
121
122        Ok(())
123    }
124
125    fn flush(&self) -> Result<()> {
126        self.lock_target().flush().map_err(Error::FlushBuffer)
127    }
128}
129
130impl<W> Drop for WriteSink<W>
131where
132    W: Write + Send,
133{
134    fn drop(&mut self) {
135        let flush_result = self.lock_target().flush().map_err(Error::FlushBuffer);
136        if let Err(err) = flush_result {
137            self.prop.call_error_handler_internal("WriteSink", err)
138        }
139    }
140}
141
142/// #
143#[doc = include_str!("../include/doc/generic-builder-note.md")]
144pub struct WriteSinkBuilder<W, ArgW> {
145    prop: SinkProp,
146    target: Option<W>,
147    _phantom: PhantomData<ArgW>,
148}
149
150impl<W, ArgW> WriteSinkBuilder<W, ArgW>
151where
152    W: Write + Send,
153{
154    /// Specifies the target that implemented [`Write`] trait, log messages will
155    /// be written into the target.
156    ///
157    /// This parameter is **required**.
158    #[must_use]
159    pub fn target(self, target: W) -> WriteSinkBuilder<W, PhantomData<W>> {
160        WriteSinkBuilder {
161            prop: self.prop,
162            target: Some(target),
163            _phantom: PhantomData,
164        }
165    }
166
167    // Prop
168    //
169
170    /// Specifies a log level filter.
171    ///
172    /// This parameter is **optional**, and defaults to [`LevelFilter::All`].
173    #[must_use]
174    pub fn level_filter(self, level_filter: LevelFilter) -> Self {
175        self.prop.set_level_filter(level_filter);
176        self
177    }
178
179    /// Specifies a formatter.
180    ///
181    /// This parameter is **optional**, and defaults to [`FullFormatter`].
182    ///
183    /// [`FullFormatter`]: crate::formatter::FullFormatter
184    #[must_use]
185    pub fn formatter<F>(self, formatter: F) -> Self
186    where
187        F: Formatter + 'static,
188    {
189        self.prop.set_formatter(formatter);
190        self
191    }
192
193    /// Specifies an error handler.
194    ///
195    /// This parameter is **optional**, and defaults to
196    /// [`ErrorHandler::default()`].
197    #[must_use]
198    pub fn error_handler<F: Into<ErrorHandler>>(self, handler: F) -> Self {
199        self.prop.set_error_handler(handler);
200        self
201    }
202}
203
204impl<W> WriteSinkBuilder<W, ()>
205where
206    W: Write + Send,
207{
208    #[doc(hidden)]
209    #[deprecated(note = "\n\n\
210        builder compile-time error:\n\
211        - missing required parameter `target`\n\n\
212    ")]
213    pub fn build(self, _: Infallible) {}
214
215    #[doc(hidden)]
216    #[deprecated(note = "\n\n\
217        builder compile-time error:\n\
218        - missing required parameter `target`\n\n\
219    ")]
220    pub fn build_arc(self, _: Infallible) {}
221}
222
223impl<W> WriteSinkBuilder<W, PhantomData<W>>
224where
225    W: Write + Send,
226{
227    /// Builds a [`WriteSink`].
228    pub fn build(self) -> Result<WriteSink<W>> {
229        let sink = WriteSink {
230            prop: self.prop,
231            target: Mutex::new(self.target.unwrap()),
232        };
233        Ok(sink)
234    }
235
236    /// Builds a `Arc<WriteSink>`.
237    ///
238    /// This is a shorthand method for `.build().map(Arc::new)`.
239    pub fn build_arc(self) -> Result<Arc<WriteSink<W>>> {
240        self.build().map(Arc::new)
241    }
242}
243
244#[cfg(test)]
245mod tests {
246    use super::*;
247    use crate::{prelude::*, test_utils::*};
248
249    #[test]
250    fn validation() {
251        let sink = WriteSink::builder().target(Vec::new()).build_arc().unwrap();
252        sink.set_formatter(Box::new(NoModFormatter::new()));
253        let logger = build_test_logger(|b| b.sink(sink.clone()).level_filter(LevelFilter::All));
254
255        info!(logger: logger, "hello WriteSink");
256
257        let data = sink.clone_target();
258        assert_eq!(data.as_slice(), b"hello WriteSink");
259    }
260}