read_logger/
lib.rs

1//! Wrap `Read` with a read statistics logger.
2
3//! ## Usage example
4//!
5//! ```
6//! use std::fs::File;
7//! use std::io::{BufReader, Read};
8//! use read_logger::{Level, ReadLogger};
9//!
10//! let f = File::open("Cargo.toml").unwrap();
11//! let mut read_logger = ReadLogger::new(f, Level::Debug, "READ");
12//! let mut reader = BufReader::new(&mut read_logger);
13//!
14//! let mut bytes = [0; 4];
15//! reader.read_exact(&mut bytes).unwrap();
16//! reader.read_exact(&mut bytes).unwrap();
17//!
18//! // BufReader does only one read() call:
19//! assert_eq!(read_logger.stats().read_count, 1);
20//! assert!(read_logger.stats().bytes_total > 200);
21//! ```
22
23//! Run with (using `env_logger`):
24//! ```shell
25//! RUST_LOG=read_logger=debug cargo run
26//! ```
27
28//! Log output:
29//! ```text
30//! [2023-09-02T18:41:41Z DEBUG read_logger] Initialize Read logger `READ`,tag,begin,end,length,request_length,count,bytes_total
31//! [2023-09-02T18:41:41Z DEBUG read_logger] Read 0-236 (237 bytes). Total requests: 1 (237 bytes),READ,0,236,237,8192,1,237
32//! ```
33
34use log::log;
35pub use log::Level;
36use std::io::{Error, Read, Seek, SeekFrom};
37use std::result::Result;
38
39/// Log reads, counts and totals
40pub struct ReadStatsLogger {
41    tag: String,
42    level: Level,
43    pub read_count: usize,
44    pub bytes_total: usize,
45}
46
47impl ReadStatsLogger {
48    pub fn new(level: Level, tag: &str) -> Self {
49        log!(
50            level,
51            "Initialize Read logger `{tag}`,tag,begin,end,length,request_length,count,bytes_total"
52        );
53        ReadStatsLogger {
54            tag: tag.to_string(),
55            level,
56            read_count: 0,
57            bytes_total: 0,
58        }
59    }
60    /// Log a read request with effective `length` and `request_length` starting at `begin`
61    pub fn log(&mut self, begin: usize, length: usize, request_length: usize) {
62        // Wraparound is ok
63        self.read_count += 1;
64        self.bytes_total += length;
65        let end = (begin + length).saturating_sub(1);
66        log!(
67            self.level,
68            "Read {begin}-{end} ({length} bytes). Total requests: {} ({} bytes),{},{begin},{end},{length},{request_length},{},{}",
69            self.read_count,
70            self.bytes_total,
71            self.tag,
72            self.read_count,
73            self.bytes_total,
74        );
75    }
76}
77
78/// Wrap `Read` with a [ReadStatsLogger]
79pub struct ReadLogger<T: Read> {
80    inner: T,
81    offset: usize,
82    logger: ReadStatsLogger,
83}
84
85impl<T: Read> ReadLogger<T> {
86    pub fn new(read: T, level: Level, tag: &str) -> Self {
87        ReadLogger {
88            inner: read,
89            offset: 0,
90            logger: ReadStatsLogger::new(level, tag),
91        }
92    }
93    pub fn stats(&self) -> &ReadStatsLogger {
94        &self.logger
95    }
96}
97
98impl<T: Read> Read for ReadLogger<T> {
99    fn read(&mut self, buf: &mut [u8]) -> Result<usize, Error> {
100        let length = self.inner.read(buf)?;
101        self.logger.log(self.offset, length, buf.len());
102        self.offset += length;
103        Ok(length)
104    }
105}
106
107impl<T: Read + Seek> Seek for ReadLogger<T> {
108    fn seek(&mut self, pos: SeekFrom) -> Result<u64, Error> {
109        let offset = self.inner.seek(pos)?;
110        self.offset = offset as usize;
111        Ok(offset)
112    }
113}
114
115#[cfg(test)]
116mod tests {
117    use super::*;
118    use std::fs::File;
119    use std::io::{BufReader, Cursor};
120
121    fn init_logger() {
122        let _ = env_logger::builder().is_test(true).try_init();
123    }
124
125    #[test]
126    fn check_stats() {
127        init_logger();
128        let mut stats = ReadStatsLogger::new(Level::Info, "READ");
129        stats.log(0, 4, 4);
130        stats.log(4, 4, 4);
131        assert_eq!(stats.read_count, 2);
132        assert_eq!(stats.bytes_total, 8);
133    }
134
135    #[test]
136    fn read_cursor() {
137        init_logger();
138        let text = "0123456789";
139        let mut reader = ReadLogger::new(Cursor::new(text), Level::Info, "READ");
140
141        let mut bytes = [0; 4];
142        reader.read_exact(&mut bytes).unwrap();
143        reader.read_exact(&mut bytes).unwrap();
144        assert_eq!(&bytes, b"4567");
145        assert_eq!(reader.stats().read_count, 2);
146        assert_eq!(reader.stats().bytes_total, 8);
147
148        let n = reader.read(&mut bytes).unwrap();
149        assert_eq!(n, 2);
150        // We count effective bytes, not requested bytes
151        assert_eq!(reader.stats().bytes_total, 10);
152    }
153
154    #[test]
155    fn seek() {
156        init_logger();
157        let text = "0123456789";
158        let mut reader = ReadLogger::new(Cursor::new(text), Level::Info, "READ");
159
160        let mut bytes = [0; 4];
161        reader.seek(SeekFrom::Start(4)).unwrap();
162        reader.read_exact(&mut bytes).unwrap();
163        assert_eq!(&bytes, b"4567");
164        assert_eq!(reader.stats().read_count, 1);
165        assert_eq!(reader.stats().bytes_total, 4);
166    }
167
168    #[test]
169    fn buf_reader() {
170        init_logger();
171        let text = "0123456789";
172        let mut cursor = ReadLogger::new(Cursor::new(text), Level::Debug, "READ");
173        // To be able to access stats after reading, we borrow cursor to BufReader
174        let mut buffer = ReadLogger::new(BufReader::new(&mut cursor), Level::Info, "BUFFER");
175
176        let mut bytes = [0; 4];
177        buffer.read_exact(&mut bytes).unwrap();
178        buffer.read_exact(&mut bytes).unwrap();
179        assert_eq!(&bytes, b"4567");
180        assert_eq!(buffer.stats().read_count, 2);
181        assert_eq!(buffer.stats().bytes_total, 8);
182        assert_eq!(cursor.stats().read_count, 1);
183        assert_eq!(cursor.stats().bytes_total, 10);
184    }
185
186    #[test]
187    fn file() {
188        init_logger();
189        let f = File::open("Cargo.toml").unwrap();
190        let mut read_logger = ReadLogger::new(f, Level::Debug, "READ");
191        let mut reader = BufReader::new(&mut read_logger);
192        let mut bytes = [0; 4];
193        reader.read_exact(&mut bytes).unwrap();
194        reader.read_exact(&mut bytes).unwrap();
195        assert_eq!(read_logger.stats().read_count, 1);
196        assert!(read_logger.stats().bytes_total > 200);
197    }
198}