• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2016 The android_logger Developers
2 //
3 // Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
4 // http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
5 // http://opensource.org/licenses/MIT>, at your option. This file may not be
6 // copied, modified, or distributed except according to those terms.
7 
8 //! A logger which writes to android output.
9 //!
10 //! ## Example
11 //!
12 //! ```
13 //! #[macro_use] extern crate log;
14 //! extern crate android_logger;
15 //!
16 //! use log::LevelFilter;
17 //! use android_logger::Config;
18 //!
19 //! /// Android code may not have obvious "main", this is just an example.
20 //! fn main() {
21 //!     android_logger::init_once(
22 //!         Config::default().with_max_level(LevelFilter::Trace),
23 //!     );
24 //!
25 //!     debug!("this is a debug {}", "message");
26 //!     error!("this is printed by default");
27 //! }
28 //! ```
29 //!
30 //! ## Example with module path filter
31 //!
32 //! It is possible to limit log messages to output from a specific crate,
33 //! and override the logcat tag name (by default, the crate name is used):
34 //!
35 //! ```
36 //! #[macro_use] extern crate log;
37 //! extern crate android_logger;
38 //!
39 //! use log::LevelFilter;
40 //! use android_logger::{Config,FilterBuilder};
41 //!
42 //! fn main() {
43 //!     android_logger::init_once(
44 //!         Config::default()
45 //!             .with_max_level(LevelFilter::Trace)
46 //!             .with_tag("mytag")
47 //!             .with_filter(FilterBuilder::new().parse("debug,hello::crate=trace").build()),
48 //!     );
49 //!
50 //!     // ..
51 //! }
52 //! ```
53 //!
54 //! ## Example with a custom log formatter
55 //!
56 //! ```
57 //! use android_logger::Config;
58 //!
59 //! android_logger::init_once(
60 //!     Config::default()
61 //!         .with_max_level(log::LevelFilter::Trace)
62 //!         .format(|f, record| write!(f, "my_app: {}", record.args()))
63 //! )
64 //! ```
65 
66 #[cfg(target_os = "android")]
67 extern crate android_log_sys as log_ffi;
68 extern crate once_cell;
69 use once_cell::sync::OnceCell;
70 #[macro_use]
71 extern crate log;
72 
73 extern crate env_logger;
74 
75 use log::{Level, LevelFilter, Log, Metadata, Record};
76 #[cfg(target_os = "android")]
77 use log_ffi::LogPriority;
78 use std::ffi::{CStr, CString};
79 use std::fmt;
80 use std::mem::{self, MaybeUninit};
81 use std::ptr;
82 
83 pub use env_logger::filter::{Builder as FilterBuilder, Filter};
84 pub use env_logger::fmt::Formatter;
85 
86 pub(crate) type FormatFn = Box<dyn Fn(&mut dyn fmt::Write, &Record) -> fmt::Result + Sync + Send>;
87 
88 #[derive(Copy, Clone, Eq, PartialEq, Debug)]
89 pub enum LogId {
90     Main,
91     Radio,
92     Events,
93     System,
94     Crash
95 }
96 
97 impl LogId {
98     #[cfg(target_os = "android")]
to_native(log_id: Option<Self>) -> log_ffi::log_id_t99     fn to_native(log_id: Option<Self>) -> log_ffi::log_id_t {
100         match log_id {
101             Some(LogId::Main) => log_ffi::log_id_t::MAIN,
102             Some(LogId::Radio) => log_ffi::log_id_t::RADIO,
103             Some(LogId::Events) => log_ffi::log_id_t::EVENTS,
104             Some(LogId::System) => log_ffi::log_id_t::SYSTEM,
105             Some(LogId::Crash) => log_ffi::log_id_t::CRASH,
106             None => log_ffi::log_id_t::DEFAULT,
107         }
108     }
109 }
110 
111 /// Output log to android system.
112 #[cfg(target_os = "android")]
android_log(log_id: log_ffi::log_id_t, prio: log_ffi::LogPriority, tag: &CStr, msg: &CStr)113 fn android_log(log_id: log_ffi::log_id_t, prio: log_ffi::LogPriority, tag: &CStr, msg: &CStr) {
114     unsafe {
115         log_ffi::__android_log_buf_write(log_id as i32,
116                                          prio as i32,
117                                          tag.as_ptr() as *const log_ffi::c_char,
118                                          msg.as_ptr() as *const log_ffi::c_char);
119     };
120 }
121 
122 /// Dummy output placeholder for tests.
123 #[cfg(not(target_os = "android"))]
android_log(_log_id: Option<LogId>, _priority: Level, _tag: &CStr, _msg: &CStr)124 fn android_log(_log_id: Option<LogId>, _priority: Level, _tag: &CStr, _msg: &CStr) {}
125 
126 /// Underlying android logger backend
127 pub struct AndroidLogger {
128     config: OnceCell<Config>,
129 }
130 
131 impl AndroidLogger {
132     /// Create new logger instance from config
new(config: Config) -> AndroidLogger133     pub fn new(config: Config) -> AndroidLogger {
134         AndroidLogger {
135             config: OnceCell::from(config),
136         }
137     }
138 
config(&self) -> &Config139     fn config(&self) -> &Config {
140         self.config.get_or_init(Config::default)
141     }
142 }
143 
144 static ANDROID_LOGGER: OnceCell<AndroidLogger> = OnceCell::new();
145 
146 const LOGGING_TAG_MAX_LEN: usize = 23;
147 const LOGGING_MSG_MAX_LEN: usize = 4000;
148 
149 impl Default for AndroidLogger {
150     /// Create a new logger with default config
default() -> AndroidLogger151     fn default() -> AndroidLogger {
152         AndroidLogger {
153             config: OnceCell::from(Config::default()),
154         }
155     }
156 }
157 
158 impl Log for AndroidLogger {
enabled(&self, metadata: &Metadata) -> bool159     fn enabled(&self, metadata: &Metadata) -> bool {
160         let config = self.config();
161         // todo: consider __android_log_is_loggable.
162         metadata.level() <= config.log_level.unwrap_or_else(log::max_level)
163     }
164 
log(&self, record: &Record)165     fn log(&self, record: &Record) {
166         let config = self.config();
167 
168         if !self.enabled(record.metadata()) {
169             return;
170         }
171 
172         // this also checks the level, but only if a filter was
173         // installed.
174         if !config.filter_matches(record) {
175             return;
176         }
177 
178         // tag must not exceed LOGGING_TAG_MAX_LEN
179         let mut tag_bytes: [MaybeUninit<u8>; LOGGING_TAG_MAX_LEN + 1] = uninit_array();
180 
181         let module_path = record.module_path().unwrap_or_default().to_owned();
182 
183         // If no tag was specified, use module name
184         let custom_tag = &config.tag;
185         let tag = custom_tag
186             .as_ref()
187             .map(|s| s.as_bytes())
188             .unwrap_or_else(|| module_path.as_bytes());
189 
190         // truncate the tag here to fit into LOGGING_TAG_MAX_LEN
191         self.fill_tag_bytes(&mut tag_bytes, tag);
192         // use stack array as C string
193         let tag: &CStr = unsafe { CStr::from_ptr(mem::transmute(tag_bytes.as_ptr())) };
194 
195         // message must not exceed LOGGING_MSG_MAX_LEN
196         // therefore split log message into multiple log calls
197         let mut writer = PlatformLogWriter::new(config.log_id, record.level(), tag);
198 
199         // If a custom tag is used, add the module path to the message.
200         // Use PlatformLogWriter to output chunks if they exceed max size.
201         let _ = match (custom_tag, &config.custom_format) {
202             (_, Some(format)) => format(&mut writer, record),
203             (Some(_), _) => fmt::write(
204                 &mut writer,
205                 format_args!("{}: {}", module_path, *record.args()),
206             ),
207             _ => fmt::write(&mut writer, *record.args()),
208         };
209 
210         // output the remaining message (this would usually be the most common case)
211         writer.flush();
212     }
213 
flush(&self)214     fn flush(&self) {}
215 }
216 
217 impl AndroidLogger {
fill_tag_bytes(&self, array: &mut [MaybeUninit<u8>], tag: &[u8])218     fn fill_tag_bytes(&self, array: &mut [MaybeUninit<u8>], tag: &[u8]) {
219         if tag.len() > LOGGING_TAG_MAX_LEN {
220             for (input, output) in tag
221                 .iter()
222                 .take(LOGGING_TAG_MAX_LEN - 2)
223                 .chain(b"..\0".iter())
224                 .zip(array.iter_mut())
225             {
226                 output.write(*input);
227             }
228         } else {
229             for (input, output) in tag.iter().chain(b"\0".iter()).zip(array.iter_mut()) {
230                 output.write(*input);
231             }
232         }
233     }
234 }
235 
236 /// Filter for android logger.
237 #[derive(Default)]
238 pub struct Config {
239     log_level: Option<LevelFilter>,
240     log_id: Option<LogId>,
241     filter: Option<env_logger::filter::Filter>,
242     tag: Option<CString>,
243     custom_format: Option<FormatFn>,
244 }
245 
246 impl Config {
247     // TODO: Remove on 0.13 version release.
248     /// **DEPRECATED**, use [`Config::with_max_level()`] instead.
249     #[deprecated(note = "use `.with_max_level()` instead")]
with_min_level(self, level: Level) -> Self250     pub fn with_min_level(self, level: Level) -> Self {
251         self.with_max_level(level.to_level_filter())
252     }
253 
254     /// Changes the maximum log level.
255     ///
256     /// Note, that `Trace` is the maximum level, because it provides the
257     /// maximum amount of detail in the emitted logs.
258     ///
259     /// If `Off` level is provided, then nothing is logged at all.
260     ///
261     /// [`log::max_level()`] is considered as the default level.
with_max_level(mut self, level: LevelFilter) -> Self262     pub fn with_max_level(mut self, level: LevelFilter) -> Self {
263         self.log_level = Some(level);
264         self
265     }
266 
267     /// Change which log buffer is used
268     ///
269     /// By default, logs are sent to the `Main` log. Other logging buffers may only be accessible
270     /// to certain processes.
with_log_id(mut self, log_id: LogId) -> Self271     pub fn with_log_id(mut self, log_id: LogId) -> Self {
272         self.log_id = Some(log_id);
273         self
274     }
275 
filter_matches(&self, record: &Record) -> bool276     fn filter_matches(&self, record: &Record) -> bool {
277         if let Some(ref filter) = self.filter {
278             filter.matches(record)
279         } else {
280             true
281         }
282     }
283 
with_filter(mut self, filter: env_logger::filter::Filter) -> Self284     pub fn with_filter(mut self, filter: env_logger::filter::Filter) -> Self {
285         self.filter = Some(filter);
286         self
287     }
288 
with_tag<S: Into<Vec<u8>>>(mut self, tag: S) -> Self289     pub fn with_tag<S: Into<Vec<u8>>>(mut self, tag: S) -> Self {
290         self.tag = Some(CString::new(tag).expect("Can't convert tag to CString"));
291         self
292     }
293 
294     /// Sets the format function for formatting the log output.
295     /// ```
296     /// # use android_logger::Config;
297     /// android_logger::init_once(
298     ///     Config::default()
299     ///         .with_max_level(log::LevelFilter::Trace)
300     ///         .format(|f, record| write!(f, "my_app: {}", record.args()))
301     /// )
302     /// ```
format<F>(mut self, format: F) -> Self where F: Fn(&mut dyn fmt::Write, &Record) -> fmt::Result + Sync + Send + 'static,303     pub fn format<F>(mut self, format: F) -> Self
304     where
305         F: Fn(&mut dyn fmt::Write, &Record) -> fmt::Result + Sync + Send + 'static,
306     {
307         self.custom_format = Some(Box::new(format));
308         self
309     }
310 }
311 
312 pub struct PlatformLogWriter<'a> {
313     #[cfg(target_os = "android")]
314     priority: LogPriority,
315     #[cfg(not(target_os = "android"))]
316     priority: Level,
317     #[cfg(target_os = "android")] log_id: log_ffi::log_id_t,
318     #[cfg(not(target_os = "android"))] log_id: Option<LogId>,
319     len: usize,
320     last_newline_index: usize,
321     tag: &'a CStr,
322     buffer: [MaybeUninit<u8>; LOGGING_MSG_MAX_LEN + 1],
323 }
324 
325 impl<'a> PlatformLogWriter<'a> {
326     #[cfg(target_os = "android")]
new_with_priority(log_id: Option<LogId>, priority: log_ffi::LogPriority, tag: &CStr) -> PlatformLogWriter327     pub fn new_with_priority(log_id: Option<LogId>, priority: log_ffi::LogPriority, tag: &CStr) -> PlatformLogWriter {
328         #[allow(deprecated)] // created an issue #35 for this
329         PlatformLogWriter {
330             priority,
331             log_id: LogId::to_native(log_id),
332             len: 0,
333             last_newline_index: 0,
334             tag,
335             buffer: uninit_array(),
336         }
337     }
338 
339     #[cfg(target_os = "android")]
new(log_id: Option<LogId>, level: Level, tag: &CStr) -> PlatformLogWriter340     pub fn new(log_id: Option<LogId>, level: Level, tag: &CStr) -> PlatformLogWriter {
341         Self::new_with_priority(
342             log_id,
343             match level {
344                 Level::Warn => LogPriority::WARN,
345                 Level::Info => LogPriority::INFO,
346                 Level::Debug => LogPriority::DEBUG,
347                 Level::Error => LogPriority::ERROR,
348                 Level::Trace => LogPriority::VERBOSE,
349             },
350             tag,
351         )
352     }
353 
354     #[cfg(not(target_os = "android"))]
new(log_id: Option<LogId>, level: Level, tag: &CStr) -> PlatformLogWriter355     pub fn new(log_id: Option<LogId>, level: Level, tag: &CStr) -> PlatformLogWriter {
356         #[allow(deprecated)] // created an issue #35 for this
357         PlatformLogWriter {
358             priority: level,
359             log_id,
360             len: 0,
361             last_newline_index: 0,
362             tag,
363             buffer: uninit_array(),
364         }
365     }
366 
367     /// Flush some bytes to android logger.
368     ///
369     /// If there is a newline, flush up to it.
370     /// If ther was no newline, flush all.
371     ///
372     /// Not guaranteed to flush everything.
temporal_flush(&mut self)373     fn temporal_flush(&mut self) {
374         let total_len = self.len;
375 
376         if total_len == 0 {
377             return;
378         }
379 
380         if self.last_newline_index > 0 {
381             let copy_from_index = self.last_newline_index;
382             let remaining_chunk_len = total_len - copy_from_index;
383 
384             self.output_specified_len(copy_from_index);
385             self.copy_bytes_to_start(copy_from_index, remaining_chunk_len);
386             self.len = remaining_chunk_len;
387         } else {
388             self.output_specified_len(total_len);
389             self.len = 0;
390         }
391         self.last_newline_index = 0;
392     }
393 
394     /// Flush everything remaining to android logger.
flush(&mut self)395     pub fn flush(&mut self) {
396         let total_len = self.len;
397 
398         if total_len == 0 {
399             return;
400         }
401 
402         self.output_specified_len(total_len);
403         self.len = 0;
404         self.last_newline_index = 0;
405     }
406 
407     /// Output buffer up until the \0 which will be placed at `len` position.
output_specified_len(&mut self, len: usize)408     fn output_specified_len(&mut self, len: usize) {
409         let mut last_byte = MaybeUninit::new(b'\0');
410 
411         mem::swap(&mut last_byte, unsafe {
412             self.buffer.get_unchecked_mut(len)
413         });
414 
415         let msg: &CStr = unsafe { CStr::from_ptr(self.buffer.as_ptr().cast()) };
416         android_log(self.log_id, self.priority, self.tag, msg);
417 
418         unsafe { *self.buffer.get_unchecked_mut(len) = last_byte };
419     }
420 
421     /// Copy `len` bytes from `index` position to starting position.
copy_bytes_to_start(&mut self, index: usize, len: usize)422     fn copy_bytes_to_start(&mut self, index: usize, len: usize) {
423         let dst = self.buffer.as_mut_ptr();
424         let src = unsafe { self.buffer.as_ptr().add(index) };
425         unsafe { ptr::copy(src, dst, len) };
426     }
427 }
428 
429 impl<'a> fmt::Write for PlatformLogWriter<'a> {
write_str(&mut self, s: &str) -> fmt::Result430     fn write_str(&mut self, s: &str) -> fmt::Result {
431         let mut incomming_bytes = s.as_bytes();
432 
433         while !incomming_bytes.is_empty() {
434             let len = self.len;
435 
436             // write everything possible to buffer and mark last \n
437             let new_len = len + incomming_bytes.len();
438             let last_newline = self.buffer[len..LOGGING_MSG_MAX_LEN]
439                 .iter_mut()
440                 .zip(incomming_bytes)
441                 .enumerate()
442                 .fold(None, |acc, (i, (output, input))| {
443                     output.write(*input);
444                     if *input == b'\n' {
445                         Some(i)
446                     } else {
447                         acc
448                     }
449                 });
450 
451             // update last \n index
452             if let Some(newline) = last_newline {
453                 self.last_newline_index = len + newline;
454             }
455 
456             // calculate how many bytes were written
457             let written_len = if new_len <= LOGGING_MSG_MAX_LEN {
458                 // if the len was not exceeded
459                 self.len = new_len;
460                 new_len - len // written len
461             } else {
462                 // if new length was exceeded
463                 self.len = LOGGING_MSG_MAX_LEN;
464                 self.temporal_flush();
465 
466                 LOGGING_MSG_MAX_LEN - len // written len
467             };
468 
469             incomming_bytes = &incomming_bytes[written_len..];
470         }
471 
472         Ok(())
473     }
474 }
475 
476 /// Send a log record to Android logging backend.
477 ///
478 /// This action does not require initialization. However, without initialization it
479 /// will use the default filter, which allows all logs.
log(record: &Record)480 pub fn log(record: &Record) {
481     ANDROID_LOGGER
482         .get_or_init(AndroidLogger::default)
483         .log(record)
484 }
485 
486 /// Initializes the global logger with an android logger.
487 ///
488 /// This can be called many times, but will only initialize logging once,
489 /// and will not replace any other previously initialized logger.
490 ///
491 /// It is ok to call this at the activity creation, and it will be
492 /// repeatedly called on every lifecycle restart (i.e. screen rotation).
init_once(config: Config)493 pub fn init_once(config: Config) {
494     let log_level = config.log_level;
495     let logger = ANDROID_LOGGER.get_or_init(|| AndroidLogger::new(config));
496 
497     if let Err(err) = log::set_logger(logger) {
498         debug!("android_logger: log::set_logger failed: {}", err);
499     } else if let Some(level) = log_level {
500         log::set_max_level(level);
501     }
502 }
503 
504 // FIXME: When `maybe_uninit_uninit_array ` is stabilized, use it instead of this helper
uninit_array<const N: usize, T>() -> [MaybeUninit<T>; N]505 fn uninit_array<const N: usize, T>() -> [MaybeUninit<T>; N] {
506     // SAFETY: Array contains MaybeUninit, which is fine to be uninit
507     unsafe { MaybeUninit::uninit().assume_init() }
508 }
509 
510 #[cfg(test)]
511 mod tests {
512     use super::*;
513     use std::fmt::Write;
514     use std::sync::atomic::{AtomicBool, Ordering};
515 
516     #[test]
check_config_values()517     fn check_config_values() {
518         // Filter is checked in config_filter_match below.
519         let config = Config::default()
520             .with_max_level(LevelFilter::Trace)
521             .with_log_id(LogId::System)
522             .with_tag("my_app");
523 
524         assert_eq!(config.log_level, Some(LevelFilter::Trace));
525         assert_eq!(config.log_id, Some(LogId::System));
526         assert_eq!(config.tag, Some(CString::new("my_app").unwrap()));
527     }
528 
529     #[test]
log_calls_formatter()530     fn log_calls_formatter() {
531         static FORMAT_FN_WAS_CALLED: AtomicBool = AtomicBool::new(false);
532         let config = Config::default()
533             .with_max_level(LevelFilter::Info)
534             .format(|_, _| {
535                 FORMAT_FN_WAS_CALLED.store(true, Ordering::SeqCst);
536                 Ok(())
537             });
538         let logger = AndroidLogger::new(config);
539 
540         logger.log(&Record::builder().level(Level::Info).build());
541 
542         assert!(FORMAT_FN_WAS_CALLED.load(Ordering::SeqCst));
543     }
544 
545     #[test]
logger_enabled_threshold()546     fn logger_enabled_threshold() {
547         let logger = AndroidLogger::new(Config::default().with_max_level(LevelFilter::Info));
548 
549         assert!(logger.enabled(&log::MetadataBuilder::new().level(Level::Warn).build()));
550         assert!(logger.enabled(&log::MetadataBuilder::new().level(Level::Info).build()));
551         assert!(!logger.enabled(&log::MetadataBuilder::new().level(Level::Debug).build()));
552     }
553 
554     // Test whether the filter gets called correctly. Not meant to be exhaustive for all filter
555     // options, as these are handled directly by the filter itself.
556     #[test]
config_filter_match()557     fn config_filter_match() {
558         let info_record = Record::builder().level(Level::Info).build();
559         let debug_record = Record::builder().level(Level::Debug).build();
560 
561         let info_all_filter = env_logger::filter::Builder::new().parse("info").build();
562         let info_all_config = Config::default().with_filter(info_all_filter);
563 
564         assert!(info_all_config.filter_matches(&info_record));
565         assert!(!info_all_config.filter_matches(&debug_record));
566     }
567 
568     #[test]
fill_tag_bytes_truncates_long_tag()569     fn fill_tag_bytes_truncates_long_tag() {
570         let logger = AndroidLogger::new(Config::default());
571         let too_long_tag: [u8; LOGGING_TAG_MAX_LEN + 20] = [b'a'; LOGGING_TAG_MAX_LEN + 20];
572 
573         let mut result: [MaybeUninit<u8>; LOGGING_TAG_MAX_LEN + 1] = uninit_array();
574         logger.fill_tag_bytes(&mut result, &too_long_tag);
575 
576         let mut expected_result = [b'a'; LOGGING_TAG_MAX_LEN - 2].to_vec();
577         expected_result.extend("..\0".as_bytes());
578         assert_eq!(unsafe { assume_init_slice(&result) }, expected_result);
579     }
580 
581     #[test]
fill_tag_bytes_keeps_short_tag()582     fn fill_tag_bytes_keeps_short_tag() {
583         let logger = AndroidLogger::new(Config::default());
584         let short_tag: [u8; 3] = [b'a'; 3];
585 
586         let mut result: [MaybeUninit<u8>; LOGGING_TAG_MAX_LEN + 1] = uninit_array();
587         logger.fill_tag_bytes(&mut result, &short_tag);
588 
589         let mut expected_result = short_tag.to_vec();
590         expected_result.push(0);
591         assert_eq!(unsafe { assume_init_slice(&result[..4]) }, expected_result);
592     }
593 
594     #[test]
platform_log_writer_init_values()595     fn platform_log_writer_init_values() {
596         let tag = CStr::from_bytes_with_nul(b"tag\0").unwrap();
597 
598         let writer = PlatformLogWriter::new(None, Level::Warn, tag);
599 
600         assert_eq!(writer.tag, tag);
601         // Android uses LogPriority instead, which doesn't implement equality checks
602         #[cfg(not(target_os = "android"))]
603         assert_eq!(writer.priority, Level::Warn);
604     }
605 
606     #[test]
temporal_flush()607     fn temporal_flush() {
608         let mut writer = get_tag_writer();
609 
610         writer
611             .write_str("12\n\n567\n90")
612             .expect("Unable to write to PlatformLogWriter");
613 
614         assert_eq!(writer.len, 10);
615         writer.temporal_flush();
616         // Should have flushed up until the last newline.
617         assert_eq!(writer.len, 3);
618         assert_eq!(writer.last_newline_index, 0);
619         assert_eq!(
620             unsafe { assume_init_slice(&writer.buffer[..writer.len]) },
621             "\n90".as_bytes()
622         );
623 
624         writer.temporal_flush();
625         // Should have flushed all remaining bytes.
626         assert_eq!(writer.len, 0);
627         assert_eq!(writer.last_newline_index, 0);
628     }
629 
630     #[test]
flush()631     fn flush() {
632         let mut writer = get_tag_writer();
633         writer
634             .write_str("abcdefghij\n\nklm\nnopqr\nstuvwxyz")
635             .expect("Unable to write to PlatformLogWriter");
636 
637         writer.flush();
638 
639         assert_eq!(writer.last_newline_index, 0);
640         assert_eq!(writer.len, 0);
641     }
642 
643     #[test]
last_newline_index()644     fn last_newline_index() {
645         let mut writer = get_tag_writer();
646 
647         writer
648             .write_str("12\n\n567\n90")
649             .expect("Unable to write to PlatformLogWriter");
650 
651         assert_eq!(writer.last_newline_index, 7);
652     }
653 
654     #[test]
output_specified_len_leaves_buffer_unchanged()655     fn output_specified_len_leaves_buffer_unchanged() {
656         let mut writer = get_tag_writer();
657         let log_string = "abcdefghij\n\nklm\nnopqr\nstuvwxyz";
658         writer
659             .write_str(log_string)
660             .expect("Unable to write to PlatformLogWriter");
661 
662         writer.output_specified_len(5);
663 
664         assert_eq!(
665             unsafe { assume_init_slice(&writer.buffer[..log_string.len()]) },
666             log_string.as_bytes()
667         );
668     }
669 
670     #[test]
copy_bytes_to_start()671     fn copy_bytes_to_start() {
672         let mut writer = get_tag_writer();
673         writer
674             .write_str("0123456789")
675             .expect("Unable to write to PlatformLogWriter");
676 
677         writer.copy_bytes_to_start(3, 2);
678 
679         assert_eq!(
680             unsafe { assume_init_slice(&writer.buffer[..10]) },
681             "3423456789".as_bytes()
682         );
683     }
684 
685     #[test]
copy_bytes_to_start_nop()686     fn copy_bytes_to_start_nop() {
687         let test_string = "Test_string_with\n\n\n\nnewlines\n";
688         let mut writer = get_tag_writer();
689         writer
690             .write_str(test_string)
691             .expect("Unable to write to PlatformLogWriter");
692 
693         writer.copy_bytes_to_start(0, 20);
694         writer.copy_bytes_to_start(10, 0);
695 
696         assert_eq!(
697             unsafe { assume_init_slice(&writer.buffer[..test_string.len()]) },
698             test_string.as_bytes()
699         );
700     }
701 
get_tag_writer() -> PlatformLogWriter<'static>702     fn get_tag_writer() -> PlatformLogWriter<'static> {
703         PlatformLogWriter::new(None, Level::Warn, CStr::from_bytes_with_nul(b"tag\0").unwrap())
704     }
705 
assume_init_slice<T>(slice: &[MaybeUninit<T>]) -> &[T]706     unsafe fn assume_init_slice<T>(slice: &[MaybeUninit<T>]) -> &[T] {
707         &*(slice as *const [MaybeUninit<T>] as *const [T])
708     }
709 }
710