Skip to main content

log_io/sink/
writer.rs

1//! Generic writer-backed sinks.
2
3use std::fs::{File, OpenOptions};
4use std::io::{BufWriter, Write};
5use std::path::Path;
6use std::sync::Mutex;
7
8use super::{flush_locked, write_locked, Sink};
9use crate::error::Result;
10use crate::format::Format;
11use crate::record::Record;
12
13/// Adapter that turns any `Send + Write` value into a [`Sink`].
14///
15/// Useful for piping records to in-memory buffers, sockets, or other
16/// non-file destinations. Wraps the writer in a mutex so the sink is
17/// safe to share across threads.
18pub struct WriterSink<W: Write + Send, F: Format> {
19    format: F,
20    target: Mutex<W>,
21}
22
23impl<W: Write + Send, F: Format> WriterSink<W, F> {
24    /// Wrap `writer` in a sink using `format`.
25    pub fn new(writer: W, format: F) -> Self {
26        Self {
27            format,
28            target: Mutex::new(writer),
29        }
30    }
31}
32
33impl<W: Write + Send, F: Format> Sink for WriterSink<W, F> {
34    fn write_record(&self, record: &Record<'_>) -> Result<()> {
35        write_locked(&self.target, &self.format, record)
36    }
37    fn flush(&self) -> Result<()> {
38        flush_locked(&self.target)
39    }
40}
41
42impl<W: Write + Send, F: Format + core::fmt::Debug> core::fmt::Debug for WriterSink<W, F> {
43    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
44        f.debug_struct("WriterSink")
45            .field("format", &self.format)
46            .finish()
47    }
48}
49
50/// Buffered file sink.
51///
52/// Wraps a [`std::fs::File`] in a [`BufWriter`] under a mutex.
53/// [`Sink::flush`] flushes the buffer through to the OS; it does not
54/// `fsync` the underlying file. Use the file directly if a stricter
55/// guarantee is needed.
56pub struct FileSink<F: Format> {
57    format: F,
58    target: Mutex<BufWriter<File>>,
59}
60
61impl<F: Format> FileSink<F> {
62    /// Open `path` for append and wrap it as a sink. Creates the file
63    /// if it does not exist.
64    pub fn append<P: AsRef<Path>>(path: P, format: F) -> Result<Self> {
65        let file = OpenOptions::new().create(true).append(true).open(path)?;
66        Ok(Self {
67            format,
68            target: Mutex::new(BufWriter::new(file)),
69        })
70    }
71
72    /// Open `path` for truncating write and wrap it as a sink.
73    pub fn create<P: AsRef<Path>>(path: P, format: F) -> Result<Self> {
74        let file = OpenOptions::new()
75            .create(true)
76            .write(true)
77            .truncate(true)
78            .open(path)?;
79        Ok(Self {
80            format,
81            target: Mutex::new(BufWriter::new(file)),
82        })
83    }
84}
85
86impl<F: Format> Sink for FileSink<F> {
87    fn write_record(&self, record: &Record<'_>) -> Result<()> {
88        write_locked(&self.target, &self.format, record)
89    }
90    fn flush(&self) -> Result<()> {
91        flush_locked(&self.target)
92    }
93}
94
95impl<F: Format + core::fmt::Debug> core::fmt::Debug for FileSink<F> {
96    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
97        f.debug_struct("FileSink")
98            .field("format", &self.format)
99            .finish()
100    }
101}
102
103/// Sink that discards every record. Useful in benchmarks and tests.
104#[derive(Debug, Default)]
105pub struct NullSink;
106
107impl NullSink {
108    /// New null sink.
109    pub const fn new() -> Self {
110        Self
111    }
112}
113
114impl Sink for NullSink {
115    fn write_record(&self, _record: &Record<'_>) -> Result<()> {
116        Ok(())
117    }
118    fn flush(&self) -> Result<()> {
119        Ok(())
120    }
121}