• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 #[cfg(default_log_impl)]
2 use {
3     crate as log,
4     super::log_ffi,
5 };
6 
7 use super::arrays::slice_assume_init_ref;
8 use super::{LOGGING_MSG_MAX_LEN, LogId, android_log, uninit_array};
9 use log::Level;
10 #[cfg(target_os = "android")]
11 use log_ffi::LogPriority;
12 use std::ffi::CStr;
13 use std::mem::MaybeUninit;
14 use std::{fmt, mem, ptr};
15 
16 /// The purpose of this "writer" is to split logged messages on whitespace when the log message
17 /// length exceeds the maximum. Without allocations.
18 pub struct PlatformLogWriter<'a> {
19     #[cfg(target_os = "android")]
20     priority: LogPriority,
21     #[cfg(not(target_os = "android"))]
22     priority: Level,
23     #[cfg(target_os = "android")]
24     buf_id: Option<log_ffi::log_id_t>,
25     #[cfg(not(target_os = "android"))]
26     buf_id: Option<LogId>,
27     len: usize,
28     last_newline_index: usize,
29     tag: &'a CStr,
30     buffer: [MaybeUninit<u8>; LOGGING_MSG_MAX_LEN + 1],
31 }
32 
33 impl PlatformLogWriter<'_> {
34     #[cfg(target_os = "android")]
new_with_priority( buf_id: Option<LogId>, priority: log_ffi::LogPriority, tag: &CStr, ) -> PlatformLogWriter<'_>35     pub fn new_with_priority(
36         buf_id: Option<LogId>,
37         priority: log_ffi::LogPriority,
38         tag: &CStr,
39     ) -> PlatformLogWriter<'_> {
40         #[allow(deprecated)] // created an issue #35 for this
41         PlatformLogWriter {
42             priority,
43             buf_id: LogId::to_native(buf_id),
44             len: 0,
45             last_newline_index: 0,
46             tag,
47             buffer: uninit_array(),
48         }
49     }
50 
51     #[cfg(target_os = "android")]
new(buf_id: Option<LogId>, level: Level, tag: &CStr) -> PlatformLogWriter<'_>52     pub fn new(buf_id: Option<LogId>, level: Level, tag: &CStr) -> PlatformLogWriter<'_> {
53         PlatformLogWriter::new_with_priority(
54             buf_id,
55             match level {
56                 Level::Warn => LogPriority::WARN,
57                 Level::Info => LogPriority::INFO,
58                 Level::Debug => LogPriority::DEBUG,
59                 Level::Error => LogPriority::ERROR,
60                 Level::Trace => LogPriority::VERBOSE,
61             },
62             tag,
63         )
64     }
65 
66     #[cfg(not(target_os = "android"))]
new(buf_id: Option<LogId>, level: Level, tag: &CStr) -> PlatformLogWriter<'_>67     pub fn new(buf_id: Option<LogId>, level: Level, tag: &CStr) -> PlatformLogWriter<'_> {
68         #[allow(deprecated)] // created an issue #35 for this
69         PlatformLogWriter {
70             priority: level,
71             buf_id,
72             len: 0,
73             last_newline_index: 0,
74             tag,
75             buffer: uninit_array(),
76         }
77     }
78 
79     /// Flush some bytes to android logger.
80     ///
81     /// If there is a newline, flush up to it.
82     /// If there was no newline, flush all.
83     ///
84     /// Not guaranteed to flush everything.
temporal_flush(&mut self)85     fn temporal_flush(&mut self) {
86         let total_len = self.len;
87 
88         if total_len == 0 {
89             return;
90         }
91 
92         if self.last_newline_index > 0 {
93             let copy_from_index = self.last_newline_index;
94             let remaining_chunk_len = total_len - copy_from_index;
95 
96             unsafe { self.output_specified_len(copy_from_index) };
97             self.copy_bytes_to_start(copy_from_index, remaining_chunk_len);
98             self.len = remaining_chunk_len;
99         } else {
100             unsafe { self.output_specified_len(total_len) };
101             self.len = 0;
102         }
103         self.last_newline_index = 0;
104     }
105 
106     /// Flush everything remaining to android logger.
flush(&mut self)107     pub fn flush(&mut self) {
108         let total_len = self.len;
109 
110         if total_len == 0 {
111             return;
112         }
113 
114         unsafe { self.output_specified_len(total_len) };
115         self.len = 0;
116         self.last_newline_index = 0;
117     }
118 
119     /// Output buffer up until the \0 which will be placed at `len` position.
120     ///
121     /// # Safety
122     /// The first `len` bytes of `self.buffer` must be initialized and not contain nullbytes.
output_specified_len(&mut self, len: usize)123     unsafe fn output_specified_len(&mut self, len: usize) {
124         let mut last_byte = MaybeUninit::new(b'\0');
125 
126         mem::swap(
127             &mut last_byte,
128             self.buffer.get_mut(len).expect("`len` is out of bounds"),
129         );
130 
131         let initialized = unsafe { slice_assume_init_ref(&self.buffer[..len + 1]) };
132         let msg = CStr::from_bytes_with_nul(initialized)
133             .expect("Unreachable: nul terminator was placed at `len`");
134         android_log(self.buf_id, self.priority, self.tag, msg);
135 
136         unsafe { *self.buffer.get_unchecked_mut(len) = last_byte };
137     }
138 
139     /// Copy `len` bytes from `index` position to starting position.
copy_bytes_to_start(&mut self, index: usize, len: usize)140     fn copy_bytes_to_start(&mut self, index: usize, len: usize) {
141         let dst = self.buffer.as_mut_ptr();
142         let src = unsafe { self.buffer.as_ptr().add(index) };
143         unsafe { ptr::copy(src, dst, len) };
144     }
145 }
146 
147 impl fmt::Write for PlatformLogWriter<'_> {
write_str(&mut self, s: &str) -> fmt::Result148     fn write_str(&mut self, s: &str) -> fmt::Result {
149         let mut incoming_bytes = s.as_bytes();
150 
151         while !incoming_bytes.is_empty() {
152             let len = self.len;
153 
154             // write everything possible to buffer and mark last \n
155             let new_len = len + incoming_bytes.len();
156             let last_newline = self.buffer[len..LOGGING_MSG_MAX_LEN]
157                 .iter_mut()
158                 .zip(incoming_bytes)
159                 .enumerate()
160                 .fold(None, |acc, (i, (output, input))| {
161                     if *input == b'\0' {
162                         // Replace nullbytes with whitespace, so we can put the message in a CStr
163                         // later to pass it through a const char*.
164                         output.write(b' ');
165                     } else {
166                         output.write(*input);
167                     }
168                     if *input == b'\n' { Some(i) } else { acc }
169                 });
170 
171             // update last \n index
172             if let Some(newline) = last_newline {
173                 self.last_newline_index = len + newline;
174             }
175 
176             // calculate how many bytes were written
177             let written_len = if new_len <= LOGGING_MSG_MAX_LEN {
178                 // if the len was not exceeded
179                 self.len = new_len;
180                 new_len - len // written len
181             } else {
182                 // if new length was exceeded
183                 self.len = LOGGING_MSG_MAX_LEN;
184                 self.temporal_flush();
185 
186                 LOGGING_MSG_MAX_LEN - len // written len
187             };
188 
189             incoming_bytes = &incoming_bytes[written_len..];
190         }
191 
192         Ok(())
193     }
194 }
195 
196 #[cfg(test)]
197 pub mod tests {
198     use crate::arrays::slice_assume_init_ref;
199     use crate::platform_log_writer::PlatformLogWriter;
200     use log::Level;
201     use std::ffi::CStr;
202     use std::fmt::Write;
203 
204     #[test]
platform_log_writer_init_values()205     fn platform_log_writer_init_values() {
206         let tag = CStr::from_bytes_with_nul(b"tag\0").unwrap();
207 
208         let writer = PlatformLogWriter::new(None, Level::Warn, tag);
209 
210         assert_eq!(writer.tag, tag);
211         // Android uses LogPriority instead, which doesn't implement equality checks
212         #[cfg(not(target_os = "android"))]
213         assert_eq!(writer.priority, Level::Warn);
214     }
215 
216     #[test]
temporal_flush()217     fn temporal_flush() {
218         let mut writer = get_tag_writer();
219 
220         writer
221             .write_str("12\n\n567\n90")
222             .expect("Unable to write to PlatformLogWriter");
223 
224         assert_eq!(writer.len, 10);
225         writer.temporal_flush();
226         // Should have flushed up until the last newline.
227         assert_eq!(writer.len, 3);
228         assert_eq!(writer.last_newline_index, 0);
229         assert_eq!(
230             unsafe { slice_assume_init_ref(&writer.buffer[..writer.len]) },
231             "\n90".as_bytes()
232         );
233 
234         writer.temporal_flush();
235         // Should have flushed all remaining bytes.
236         assert_eq!(writer.len, 0);
237         assert_eq!(writer.last_newline_index, 0);
238     }
239 
240     #[test]
flush()241     fn flush() {
242         let mut writer = get_tag_writer();
243         writer
244             .write_str("abcdefghij\n\nklm\nnopqr\nstuvwxyz")
245             .expect("Unable to write to PlatformLogWriter");
246 
247         writer.flush();
248 
249         assert_eq!(writer.last_newline_index, 0);
250         assert_eq!(writer.len, 0);
251     }
252 
253     #[test]
last_newline_index()254     fn last_newline_index() {
255         let mut writer = get_tag_writer();
256 
257         writer
258             .write_str("12\n\n567\n90")
259             .expect("Unable to write to PlatformLogWriter");
260 
261         assert_eq!(writer.last_newline_index, 7);
262     }
263 
264     #[test]
output_specified_len_leaves_buffer_unchanged()265     fn output_specified_len_leaves_buffer_unchanged() {
266         let mut writer = get_tag_writer();
267         let log_string = "abcdefghij\n\nklm\nnopqr\nstuvwxyz";
268         writer
269             .write_str(log_string)
270             .expect("Unable to write to PlatformLogWriter");
271 
272         unsafe { writer.output_specified_len(5) };
273 
274         assert_eq!(
275             unsafe { slice_assume_init_ref(&writer.buffer[..log_string.len()]) },
276             log_string.as_bytes()
277         );
278     }
279 
280     #[test]
copy_bytes_to_start()281     fn copy_bytes_to_start() {
282         let mut writer = get_tag_writer();
283         writer
284             .write_str("0123456789")
285             .expect("Unable to write to PlatformLogWriter");
286 
287         writer.copy_bytes_to_start(3, 2);
288 
289         assert_eq!(
290             unsafe { slice_assume_init_ref(&writer.buffer[..10]) },
291             "3423456789".as_bytes()
292         );
293     }
294 
295     #[test]
copy_bytes_to_start_nop()296     fn copy_bytes_to_start_nop() {
297         let test_string = "Test_string_with\n\n\n\nnewlines\n";
298         let mut writer = get_tag_writer();
299         writer
300             .write_str(test_string)
301             .expect("Unable to write to PlatformLogWriter");
302 
303         writer.copy_bytes_to_start(0, 20);
304         writer.copy_bytes_to_start(10, 0);
305 
306         assert_eq!(
307             unsafe { slice_assume_init_ref(&writer.buffer[..test_string.len()]) },
308             test_string.as_bytes()
309         );
310     }
311 
312     #[test]
writer_substitutes_nullbytes_with_spaces()313     fn writer_substitutes_nullbytes_with_spaces() {
314         let test_string = "Test_string_with\0\0\0\0nullbytes\0";
315         let mut writer = get_tag_writer();
316         writer
317             .write_str(test_string)
318             .expect("Unable to write to PlatformLogWriter");
319 
320         assert_eq!(
321             unsafe { slice_assume_init_ref(&writer.buffer[..test_string.len()]) },
322             test_string.replace("\0", " ").as_bytes()
323         );
324     }
325 
get_tag_writer() -> PlatformLogWriter<'static>326     fn get_tag_writer() -> PlatformLogWriter<'static> {
327         PlatformLogWriter::new(
328             None,
329             Level::Warn,
330             CStr::from_bytes_with_nul(b"tag\0").unwrap(),
331         )
332     }
333 }
334