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}