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