log_buffer/
lib.rs

1//! `log_buffer` provides a way to record and extract logs without allocation.
2//! The [LogBuffer](struct.LogBuffer.html) achieves this by providing a ring
3//! buffer, similar to a *nix _dmesg_ facility.
4//!
5//! # Usage example
6//!
7//! ```
8//! use std::fmt::Write;
9//!
10//! let mut dmesg = log_buffer::LogBuffer::new([0; 16]);
11//! write!(dmesg, "\nfirst\n").unwrap();
12//! write!(dmesg, "second\n").unwrap();
13//! write!(dmesg, "third\n").unwrap();
14//!
15//! assert_eq!(dmesg.extract(),
16//!            "st\nsecond\nthird\n");
17//! assert_eq!(dmesg.extract_lines().collect::<Vec<_>>(),
18//!            vec!["second", "third"]);
19//! ```
20//!
21//! # Choices of backing storage
22//!
23//! Backed by an array:
24//!
25//! ```
26//! let mut dmesg = log_buffer::LogBuffer::new([0; 16]);
27//! ```
28//!
29//! Backed by a mutable slice:
30//!
31//! ```
32//! let mut storage = [0; 16];
33//! let mut dmesg = log_buffer::LogBuffer::new(&mut storage);
34//! ```
35//!
36//! Backed by a vector:
37//!
38//! ```
39//! let mut dmesg = log_buffer::LogBuffer::new(vec![0; 16]);
40//! ```
41
42#![no_std]
43#![cfg_attr(feature = "const_fn", feature(const_fn))]
44
45/// A ring buffer that stores UTF-8 text.
46///
47/// Anything that implements `AsMut<[u8]>` can be used for backing storage;
48/// e.g. `[u8; N]`, `Vec<[u8]>`, `Box<[u8]>`.
49#[derive(Debug)]
50pub struct LogBuffer<T: AsRef<[u8]> + AsMut<[u8]>> {
51    buffer:   T,
52    position: usize
53}
54
55impl<T: AsRef<[u8]> + AsMut<[u8]>> LogBuffer<T> {
56    /// Creates a new ring buffer, backed by `storage`.
57    ///
58    /// The buffer is cleared after creation.
59    pub fn new(storage: T) -> LogBuffer<T> {
60        let mut buffer = LogBuffer { buffer: storage, position: 0 };
61        buffer.clear();
62        buffer
63    }
64
65    /// Creates a new ring buffer, backed by `storage`.
66    ///
67    /// The buffer is *not* cleared after creation, and contains whatever is in `storage`.
68    /// The `clear()` method should be called before use.
69    /// However, this function can be used in a static initializer.
70    #[cfg(feature = "const_fn")]
71    pub const fn uninitialized(storage: T) -> LogBuffer<T> {
72        LogBuffer { buffer: storage, position: 0 }
73    }
74
75    /// Clears the buffer.
76    ///
77    /// Only the text written after clearing will be read out by a future extraction.
78    ///
79    /// This function takes O(n) time where n is buffer length.
80    pub fn clear(&mut self) {
81        self.position = 0;
82        for b in self.buffer.as_mut().iter_mut() {
83            // Any non-leading UTF-8 code unit would do, but 0xff looks like an obvious sentinel.
84            // Can't be 0x00 since that is a valid codepoint.
85            *b = 0xff;
86        }
87    }
88
89    /// Checks whether the ring buffer is empty.
90    ///
91    /// This function takes O(1) time.
92    pub fn is_empty(&self) -> bool {
93        let buffer = self.buffer.as_ref();
94        self.position == 0 &&
95            (buffer.len() == 0 || buffer[buffer.len() - 1] == 0xff)
96    }
97
98    fn rotate(&mut self) {
99        self.buffer.as_mut().rotate_left(self.position);
100        self.position = 0;
101    }
102
103    /// Extracts the contents of the ring buffer as a string slice, excluding any
104    /// partially overwritten UTF-8 code unit sequences at the beginning.
105    ///
106    /// Extraction rotates the contents of the ring buffer such that all of its
107    /// contents becomes contiguous in memory.
108    ///
109    /// This function takes O(n) time where n is buffer length.
110    pub fn extract(&mut self) -> &str {
111        self.rotate();
112
113        // Skip any non-leading UTF-8 code units at the start.
114        fn is_utf8_leader(byte: u8) -> bool {
115            byte & 0b10000000 == 0b00000000 ||
116            byte & 0b11100000 == 0b11000000 ||
117            byte & 0b11110000 == 0b11100000 ||
118            byte & 0b11111000 == 0b11110000
119        }
120
121        let buffer = self.buffer.as_mut();
122        for i in 0..buffer.len() {
123            if is_utf8_leader(buffer[i]) {
124                return core::str::from_utf8(&buffer[i..]).unwrap()
125            }
126        }
127        return ""
128    }
129
130    /// Extracts the contents of the ring buffer as an iterator over its lines,
131    /// excluding any partially overwritten lines at the beginning.
132    ///
133    /// The first line written to the ring buffer after clearing it should start
134    /// with `'\n'`, or it will be treated as partially overwritten and lost.
135    ///
136    /// Extraction rotates the contents of the ring buffer such that all of its
137    /// contents becomes contiguous in memory.
138    ///
139    /// This function takes O(n) time where n is buffer length.
140    pub fn extract_lines(&mut self) -> core::str::Lines {
141        self.rotate();
142
143        let buffer = self.buffer.as_mut();
144        for i in 0..buffer.len() {
145            if i > 0 && buffer[i - 1] == b'\n' {
146                let slice = core::str::from_utf8(&buffer[i..]).unwrap();
147                return slice.lines()
148            }
149        }
150        return "".lines()
151    }
152}
153
154impl<T: AsRef<[u8]> + AsMut<[u8]>> core::fmt::Write for LogBuffer<T> {
155    /// Append `s` to the ring buffer.
156    ///
157    /// This function takes O(n) time where n is length of `s`.
158    fn write_str(&mut self, s: &str) -> core::fmt::Result {
159        for &b in s.as_bytes() {
160            self.buffer.as_mut()[self.position] = b;
161            self.position = (self.position + 1) % self.buffer.as_mut().len()
162        }
163        Ok(())
164    }
165}