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 #[cfg(default_log_impl)]
71 use crate as log;
72 #[cfg(not(default_log_impl))]
73 #[macro_use]
74 extern crate log;
75
76 #[cfg(not(default_log_impl))]
77 extern crate env_logger;
78
79 use self::log::{Level, LevelFilter, Log, Metadata, Record};
80 #[cfg(target_os = "android")]
81 use self::log_ffi::LogPriority;
82 use std::ffi::{CStr, CString};
83 use std::fmt;
84 use std::mem::{self, MaybeUninit};
85 use std::ptr;
86
87 #[cfg(default_log_impl)]
88 pub mod env_logger {
89 pub mod filter {
90 pub struct Filter;
91 impl Filter {
matches(&self, _: &crate::Record) -> bool92 pub fn matches(&self, _: &crate::Record) -> bool { true }
93 }
94 }
95 }
96 #[cfg(not(default_log_impl))]
97 pub use env_logger::filter::{Builder as FilterBuilder, Filter};
98 #[cfg(not(default_log_impl))]
99 pub use env_logger::fmt::Formatter;
100
101 pub(crate) type FormatFn = Box<dyn Fn(&mut dyn fmt::Write, &Record) -> fmt::Result + Sync + Send>;
102
103 /// Possible identifiers of a specific buffer of Android logging system for
104 /// logging a message.
105 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
106 pub enum LogId {
107 /// Main log buffer.
108 ///
109 /// This is the only log buffer available to apps.
110 Main,
111
112 /// Radio log buffer.
113 Radio,
114
115 /// Event log buffer.
116 Events,
117
118 /// System log buffer.
119 System,
120
121 /// Crash log buffer.
122 Crash,
123
124 /// Kernel log buffer.
125 Kernel,
126
127 /// Security log buffer.
128 Security,
129
130 /// Statistics log buffer.
131 Stats,
132 }
133
134 #[cfg(target_os = "android")]
135 impl LogId {
to_native(log_id: Option<Self>) -> Option<log_ffi::log_id_t>136 const fn to_native(log_id: Option<Self>) -> Option<log_ffi::log_id_t> {
137 match log_id {
138 Some(Self::Main) => Some(log_ffi::log_id_t::MAIN),
139 Some(Self::Radio) => Some(log_ffi::log_id_t::RADIO),
140 Some(Self::Events) => Some(log_ffi::log_id_t::EVENTS),
141 Some(Self::System) => Some(log_ffi::log_id_t::SYSTEM),
142 Some(Self::Crash) => Some(log_ffi::log_id_t::CRASH),
143 Some(Self::Kernel) => Some(log_ffi::log_id_t::KERNEL),
144 Some(Self::Security) => Some(log_ffi::log_id_t::SECURITY),
145 Some(Self::Stats) => Some(log_ffi::log_id_t::STATS),
146 None => None,
147 }
148 }
149 }
150
151 /// Outputs log to Android system.
152 #[cfg(target_os = "android")]
android_log( buf_id: Option<log_ffi::log_id_t>, prio: log_ffi::LogPriority, tag: &CStr, msg: &CStr, )153 fn android_log(
154 buf_id: Option<log_ffi::log_id_t>,
155 prio: log_ffi::LogPriority,
156 tag: &CStr,
157 msg: &CStr,
158 ) {
159 if let Some(buf_id) = buf_id {
160 unsafe {
161 log_ffi::__android_log_buf_write(
162 buf_id as log_ffi::c_int,
163 prio as log_ffi::c_int,
164 tag.as_ptr() as *const log_ffi::c_char,
165 msg.as_ptr() as *const log_ffi::c_char,
166 );
167 };
168 } else {
169 unsafe {
170 log_ffi::__android_log_write(
171 prio as log_ffi::c_int,
172 tag.as_ptr() as *const log_ffi::c_char,
173 msg.as_ptr() as *const log_ffi::c_char,
174 );
175 };
176 }
177 }
178
179 /// Dummy output placeholder for tests.
180 #[cfg(not(target_os = "android"))]
android_log(_buf_id: Option<LogId>, _priority: Level, _tag: &CStr, _msg: &CStr)181 fn android_log(_buf_id: Option<LogId>, _priority: Level, _tag: &CStr, _msg: &CStr) {}
182
183 /// Underlying android logger backend
184 pub struct AndroidLogger {
185 config: OnceCell<Config>,
186 }
187
188 impl AndroidLogger {
189 /// Create new logger instance from config
new(config: Config) -> AndroidLogger190 pub fn new(config: Config) -> AndroidLogger {
191 AndroidLogger {
192 config: OnceCell::from(config),
193 }
194 }
195
config(&self) -> &Config196 fn config(&self) -> &Config {
197 self.config.get_or_init(Config::default)
198 }
199 }
200
201 static ANDROID_LOGGER: OnceCell<AndroidLogger> = OnceCell::new();
202
203 const LOGGING_TAG_MAX_LEN: usize = 23;
204 const LOGGING_MSG_MAX_LEN: usize = 4000;
205
206 impl Default for AndroidLogger {
207 /// Create a new logger with default config
default() -> AndroidLogger208 fn default() -> AndroidLogger {
209 AndroidLogger {
210 config: OnceCell::from(Config::default()),
211 }
212 }
213 }
214
215 impl Log for AndroidLogger {
enabled(&self, metadata: &Metadata) -> bool216 fn enabled(&self, metadata: &Metadata) -> bool {
217 let config = self.config();
218 // todo: consider __android_log_is_loggable.
219 metadata.level() <= config.log_level.unwrap_or_else(log::max_level)
220 }
221
log(&self, record: &Record)222 fn log(&self, record: &Record) {
223 let config = self.config();
224
225 if !self.enabled(record.metadata()) {
226 return;
227 }
228
229 // this also checks the level, but only if a filter was
230 // installed.
231 if !config.filter_matches(record) {
232 return;
233 }
234
235 // tag must not exceed LOGGING_TAG_MAX_LEN
236 let mut tag_bytes: [MaybeUninit<u8>; LOGGING_TAG_MAX_LEN + 1] = uninit_array();
237
238 let module_path = record.module_path().unwrap_or_default().to_owned();
239
240 // If no tag was specified, use module name
241 let custom_tag = &config.tag;
242 let tag = custom_tag
243 .as_ref()
244 .map(|s| s.as_bytes())
245 .unwrap_or_else(|| module_path.as_bytes());
246
247 // truncate the tag here to fit into LOGGING_TAG_MAX_LEN
248 self.fill_tag_bytes(&mut tag_bytes, tag);
249 // use stack array as C string
250 let tag: &CStr = unsafe { CStr::from_ptr(mem::transmute(tag_bytes.as_ptr())) };
251
252 // message must not exceed LOGGING_MSG_MAX_LEN
253 // therefore split log message into multiple log calls
254 let mut writer = PlatformLogWriter::new(config.buf_id, record.level(), tag);
255
256 // If a custom tag is used, add the module path to the message.
257 // Use PlatformLogWriter to output chunks if they exceed max size.
258 let _ = match (custom_tag, &config.custom_format) {
259 (_, Some(format)) => format(&mut writer, record),
260 (Some(_), _) => fmt::write(
261 &mut writer,
262 format_args!("{}: {}", module_path, *record.args()),
263 ),
264 _ => fmt::write(&mut writer, *record.args()),
265 };
266
267 // output the remaining message (this would usually be the most common case)
268 writer.flush();
269 }
270
flush(&self)271 fn flush(&self) {}
272 }
273
274 impl AndroidLogger {
fill_tag_bytes(&self, array: &mut [MaybeUninit<u8>], tag: &[u8])275 fn fill_tag_bytes(&self, array: &mut [MaybeUninit<u8>], tag: &[u8]) {
276 if tag.len() > LOGGING_TAG_MAX_LEN {
277 for (input, output) in tag
278 .iter()
279 .take(LOGGING_TAG_MAX_LEN - 2)
280 .chain(b"..\0".iter())
281 .zip(array.iter_mut())
282 {
283 output.write(*input);
284 }
285 } else {
286 for (input, output) in tag.iter().chain(b"\0".iter()).zip(array.iter_mut()) {
287 output.write(*input);
288 }
289 }
290 }
291 }
292
293 /// Filter for android logger.
294 #[derive(Default)]
295 pub struct Config {
296 log_level: Option<LevelFilter>,
297 buf_id: Option<LogId>,
298 filter: Option<env_logger::filter::Filter>,
299 tag: Option<CString>,
300 custom_format: Option<FormatFn>,
301 }
302
303 impl Config {
304 /// Changes the maximum log level.
305 ///
306 /// Note, that `Trace` is the maximum level, because it provides the
307 /// maximum amount of detail in the emitted logs.
308 ///
309 /// If `Off` level is provided, then nothing is logged at all.
310 ///
311 /// [`log::max_level()`] is considered as the default level.
with_max_level(mut self, level: LevelFilter) -> Self312 pub fn with_max_level(mut self, level: LevelFilter) -> Self {
313 self.log_level = Some(level);
314 self
315 }
316
317 /// Changes the Android logging system buffer to be used.
318 ///
319 /// By default, logs are sent to the [`Main`] log. Other logging buffers may
320 /// only be accessible to certain processes.
321 ///
322 /// [`Main`]: LogId::Main
with_log_buffer(mut self, buf_id: LogId) -> Self323 pub fn with_log_buffer(mut self, buf_id: LogId) -> Self {
324 self.buf_id = Some(buf_id);
325 self
326 }
327
filter_matches(&self, record: &Record) -> bool328 fn filter_matches(&self, record: &Record) -> bool {
329 if let Some(ref filter) = self.filter {
330 filter.matches(record)
331 } else {
332 true
333 }
334 }
335
with_filter(mut self, filter: env_logger::filter::Filter) -> Self336 pub fn with_filter(mut self, filter: env_logger::filter::Filter) -> Self {
337 self.filter = Some(filter);
338 self
339 }
340
with_tag<S: Into<Vec<u8>>>(mut self, tag: S) -> Self341 pub fn with_tag<S: Into<Vec<u8>>>(mut self, tag: S) -> Self {
342 self.tag = Some(CString::new(tag).expect("Can't convert tag to CString"));
343 self
344 }
345
346 /// Sets the format function for formatting the log output.
347 /// ```
348 /// # use android_logger::Config;
349 /// android_logger::init_once(
350 /// Config::default()
351 /// .with_max_level(log::LevelFilter::Trace)
352 /// .format(|f, record| write!(f, "my_app: {}", record.args()))
353 /// )
354 /// ```
format<F>(mut self, format: F) -> Self where F: Fn(&mut dyn fmt::Write, &Record) -> fmt::Result + Sync + Send + 'static,355 pub fn format<F>(mut self, format: F) -> Self
356 where
357 F: Fn(&mut dyn fmt::Write, &Record) -> fmt::Result + Sync + Send + 'static,
358 {
359 self.custom_format = Some(Box::new(format));
360 self
361 }
362 }
363
364 pub struct PlatformLogWriter<'a> {
365 #[cfg(target_os = "android")]
366 priority: LogPriority,
367 #[cfg(not(target_os = "android"))]
368 priority: Level,
369 #[cfg(target_os = "android")]
370 buf_id: Option<log_ffi::log_id_t>,
371 #[cfg(not(target_os = "android"))]
372 buf_id: Option<LogId>,
373 len: usize,
374 last_newline_index: usize,
375 tag: &'a CStr,
376 buffer: [MaybeUninit<u8>; LOGGING_MSG_MAX_LEN + 1],
377 }
378
379 impl<'a> PlatformLogWriter<'a> {
380 #[cfg(target_os = "android")]
new_with_priority( buf_id: Option<LogId>, priority: log_ffi::LogPriority, tag: &CStr, ) -> PlatformLogWriter<'_>381 pub fn new_with_priority(
382 buf_id: Option<LogId>,
383 priority: log_ffi::LogPriority,
384 tag: &CStr,
385 ) -> PlatformLogWriter<'_> {
386 #[allow(deprecated)] // created an issue #35 for this
387 PlatformLogWriter {
388 priority,
389 buf_id: LogId::to_native(buf_id),
390 len: 0,
391 last_newline_index: 0,
392 tag,
393 buffer: uninit_array(),
394 }
395 }
396
397 #[cfg(target_os = "android")]
new(buf_id: Option<LogId>, level: Level, tag: &CStr) -> PlatformLogWriter<'_>398 pub fn new(buf_id: Option<LogId>, level: Level, tag: &CStr) -> PlatformLogWriter<'_> {
399 PlatformLogWriter::new_with_priority(
400 buf_id,
401 match level {
402 Level::Warn => LogPriority::WARN,
403 Level::Info => LogPriority::INFO,
404 Level::Debug => LogPriority::DEBUG,
405 Level::Error => LogPriority::ERROR,
406 Level::Trace => LogPriority::VERBOSE,
407 },
408 tag,
409 )
410 }
411
412 #[cfg(not(target_os = "android"))]
new(buf_id: Option<LogId>, level: Level, tag: &CStr) -> PlatformLogWriter<'_>413 pub fn new(buf_id: Option<LogId>, level: Level, tag: &CStr) -> PlatformLogWriter<'_> {
414 #[allow(deprecated)] // created an issue #35 for this
415 PlatformLogWriter {
416 priority: level,
417 buf_id,
418 len: 0,
419 last_newline_index: 0,
420 tag,
421 buffer: uninit_array(),
422 }
423 }
424
425 /// Flush some bytes to android logger.
426 ///
427 /// If there is a newline, flush up to it.
428 /// If ther was no newline, flush all.
429 ///
430 /// Not guaranteed to flush everything.
temporal_flush(&mut self)431 fn temporal_flush(&mut self) {
432 let total_len = self.len;
433
434 if total_len == 0 {
435 return;
436 }
437
438 if self.last_newline_index > 0 {
439 let copy_from_index = self.last_newline_index;
440 let remaining_chunk_len = total_len - copy_from_index;
441
442 self.output_specified_len(copy_from_index);
443 self.copy_bytes_to_start(copy_from_index, remaining_chunk_len);
444 self.len = remaining_chunk_len;
445 } else {
446 self.output_specified_len(total_len);
447 self.len = 0;
448 }
449 self.last_newline_index = 0;
450 }
451
452 /// Flush everything remaining to android logger.
flush(&mut self)453 pub fn flush(&mut self) {
454 let total_len = self.len;
455
456 if total_len == 0 {
457 return;
458 }
459
460 self.output_specified_len(total_len);
461 self.len = 0;
462 self.last_newline_index = 0;
463 }
464
465 /// Output buffer up until the \0 which will be placed at `len` position.
output_specified_len(&mut self, len: usize)466 fn output_specified_len(&mut self, len: usize) {
467 let mut last_byte = MaybeUninit::new(b'\0');
468
469 mem::swap(&mut last_byte, unsafe {
470 self.buffer.get_unchecked_mut(len)
471 });
472
473 let msg: &CStr = unsafe { CStr::from_ptr(self.buffer.as_ptr().cast()) };
474 android_log(self.buf_id, self.priority, self.tag, msg);
475
476 unsafe { *self.buffer.get_unchecked_mut(len) = last_byte };
477 }
478
479 /// Copy `len` bytes from `index` position to starting position.
copy_bytes_to_start(&mut self, index: usize, len: usize)480 fn copy_bytes_to_start(&mut self, index: usize, len: usize) {
481 let dst = self.buffer.as_mut_ptr();
482 let src = unsafe { self.buffer.as_ptr().add(index) };
483 unsafe { ptr::copy(src, dst, len) };
484 }
485 }
486
487 impl<'a> fmt::Write for PlatformLogWriter<'a> {
write_str(&mut self, s: &str) -> fmt::Result488 fn write_str(&mut self, s: &str) -> fmt::Result {
489 let mut incomming_bytes = s.as_bytes();
490
491 while !incomming_bytes.is_empty() {
492 let len = self.len;
493
494 // write everything possible to buffer and mark last \n
495 let new_len = len + incomming_bytes.len();
496 let last_newline = self.buffer[len..LOGGING_MSG_MAX_LEN]
497 .iter_mut()
498 .zip(incomming_bytes)
499 .enumerate()
500 .fold(None, |acc, (i, (output, input))| {
501 output.write(*input);
502 if *input == b'\n' {
503 Some(i)
504 } else {
505 acc
506 }
507 });
508
509 // update last \n index
510 if let Some(newline) = last_newline {
511 self.last_newline_index = len + newline;
512 }
513
514 // calculate how many bytes were written
515 let written_len = if new_len <= LOGGING_MSG_MAX_LEN {
516 // if the len was not exceeded
517 self.len = new_len;
518 new_len - len // written len
519 } else {
520 // if new length was exceeded
521 self.len = LOGGING_MSG_MAX_LEN;
522 self.temporal_flush();
523
524 LOGGING_MSG_MAX_LEN - len // written len
525 };
526
527 incomming_bytes = &incomming_bytes[written_len..];
528 }
529
530 Ok(())
531 }
532 }
533
534 /// Send a log record to Android logging backend.
535 ///
536 /// This action does not require initialization. However, without initialization it
537 /// will use the default filter, which allows all logs.
log(record: &Record)538 pub fn log(record: &Record) {
539 ANDROID_LOGGER
540 .get_or_init(AndroidLogger::default)
541 .log(record)
542 }
543
544 /// Initializes the global logger with an android logger.
545 ///
546 /// This can be called many times, but will only initialize logging once,
547 /// and will not replace any other previously initialized logger.
548 ///
549 /// It is ok to call this at the activity creation, and it will be
550 /// repeatedly called on every lifecycle restart (i.e. screen rotation).
init_once(config: Config)551 pub fn init_once(config: Config) {
552 let log_level = config.log_level;
553 let logger = ANDROID_LOGGER.get_or_init(|| AndroidLogger::new(config));
554
555 if let Err(err) = log::set_logger(logger) {
556 debug!("android_logger: log::set_logger failed: {}", err);
557 } else if let Some(level) = log_level {
558 log::set_max_level(level);
559 }
560 // On Android, log crate is patched to default to LevelFilter::Trace rather than Off. Preserve
561 // the existing "android_logger default level is Off" behavior by explicitly setting the level.
562 #[cfg(target_os = "android")]
563 if log_level.is_none() {
564 log::set_max_level(LevelFilter::Off);
565 }
566 }
567
568 // FIXME: When `maybe_uninit_uninit_array ` is stabilized, use it instead of this helper
uninit_array<const N: usize, T>() -> [MaybeUninit<T>; N]569 fn uninit_array<const N: usize, T>() -> [MaybeUninit<T>; N] {
570 // SAFETY: Array contains MaybeUninit, which is fine to be uninit
571 unsafe { MaybeUninit::uninit().assume_init() }
572 }
573
574 #[cfg(test)]
575 mod tests {
576 use super::*;
577 use std::fmt::Write;
578 use std::sync::atomic::{AtomicBool, Ordering};
579
580 #[test]
check_config_values()581 fn check_config_values() {
582 // Filter is checked in config_filter_match below.
583 let config = Config::default()
584 .with_max_level(LevelFilter::Trace)
585 .with_log_buffer(LogId::System)
586 .with_tag("my_app");
587
588 assert_eq!(config.log_level, Some(LevelFilter::Trace));
589 assert_eq!(config.buf_id, Some(LogId::System));
590 assert_eq!(config.tag, Some(CString::new("my_app").unwrap()));
591 }
592
593 #[test]
log_calls_formatter()594 fn log_calls_formatter() {
595 static FORMAT_FN_WAS_CALLED: AtomicBool = AtomicBool::new(false);
596 let config = Config::default()
597 .with_max_level(LevelFilter::Info)
598 .format(|_, _| {
599 FORMAT_FN_WAS_CALLED.store(true, Ordering::SeqCst);
600 Ok(())
601 });
602 let logger = AndroidLogger::new(config);
603
604 logger.log(&Record::builder().level(Level::Info).build());
605
606 assert!(FORMAT_FN_WAS_CALLED.load(Ordering::SeqCst));
607 }
608
609 #[test]
logger_enabled_threshold()610 fn logger_enabled_threshold() {
611 let logger = AndroidLogger::new(Config::default().with_max_level(LevelFilter::Info));
612
613 assert!(logger.enabled(&log::MetadataBuilder::new().level(Level::Warn).build()));
614 assert!(logger.enabled(&log::MetadataBuilder::new().level(Level::Info).build()));
615 assert!(!logger.enabled(&log::MetadataBuilder::new().level(Level::Debug).build()));
616 }
617
618 // Test whether the filter gets called correctly. Not meant to be exhaustive for all filter
619 // options, as these are handled directly by the filter itself.
620 #[cfg(not(default_log_impl))]
621 #[test]
config_filter_match()622 fn config_filter_match() {
623 let info_record = Record::builder().level(Level::Info).build();
624 let debug_record = Record::builder().level(Level::Debug).build();
625
626 let info_all_filter = env_logger::filter::Builder::new().parse("info").build();
627 let info_all_config = Config::default().with_filter(info_all_filter);
628
629 assert!(info_all_config.filter_matches(&info_record));
630 assert!(!info_all_config.filter_matches(&debug_record));
631 }
632
633 #[test]
fill_tag_bytes_truncates_long_tag()634 fn fill_tag_bytes_truncates_long_tag() {
635 let logger = AndroidLogger::new(Config::default());
636 let too_long_tag: [u8; LOGGING_TAG_MAX_LEN + 20] = [b'a'; LOGGING_TAG_MAX_LEN + 20];
637
638 let mut result: [MaybeUninit<u8>; LOGGING_TAG_MAX_LEN + 1] = uninit_array();
639 logger.fill_tag_bytes(&mut result, &too_long_tag);
640
641 let mut expected_result = [b'a'; LOGGING_TAG_MAX_LEN - 2].to_vec();
642 expected_result.extend("..\0".as_bytes());
643 assert_eq!(unsafe { assume_init_slice(&result) }, expected_result);
644 }
645
646 #[test]
fill_tag_bytes_keeps_short_tag()647 fn fill_tag_bytes_keeps_short_tag() {
648 let logger = AndroidLogger::new(Config::default());
649 let short_tag: [u8; 3] = [b'a'; 3];
650
651 let mut result: [MaybeUninit<u8>; LOGGING_TAG_MAX_LEN + 1] = uninit_array();
652 logger.fill_tag_bytes(&mut result, &short_tag);
653
654 let mut expected_result = short_tag.to_vec();
655 expected_result.push(0);
656 assert_eq!(unsafe { assume_init_slice(&result[..4]) }, expected_result);
657 }
658
659 #[test]
platform_log_writer_init_values()660 fn platform_log_writer_init_values() {
661 let tag = CStr::from_bytes_with_nul(b"tag\0").unwrap();
662
663 let writer = PlatformLogWriter::new(None, Level::Warn, tag);
664
665 assert_eq!(writer.tag, tag);
666 // Android uses LogPriority instead, which doesn't implement equality checks
667 #[cfg(not(target_os = "android"))]
668 assert_eq!(writer.priority, Level::Warn);
669 }
670
671 #[test]
temporal_flush()672 fn temporal_flush() {
673 let mut writer = get_tag_writer();
674
675 writer
676 .write_str("12\n\n567\n90")
677 .expect("Unable to write to PlatformLogWriter");
678
679 assert_eq!(writer.len, 10);
680 writer.temporal_flush();
681 // Should have flushed up until the last newline.
682 assert_eq!(writer.len, 3);
683 assert_eq!(writer.last_newline_index, 0);
684 assert_eq!(
685 unsafe { assume_init_slice(&writer.buffer[..writer.len]) },
686 "\n90".as_bytes()
687 );
688
689 writer.temporal_flush();
690 // Should have flushed all remaining bytes.
691 assert_eq!(writer.len, 0);
692 assert_eq!(writer.last_newline_index, 0);
693 }
694
695 #[test]
flush()696 fn flush() {
697 let mut writer = get_tag_writer();
698 writer
699 .write_str("abcdefghij\n\nklm\nnopqr\nstuvwxyz")
700 .expect("Unable to write to PlatformLogWriter");
701
702 writer.flush();
703
704 assert_eq!(writer.last_newline_index, 0);
705 assert_eq!(writer.len, 0);
706 }
707
708 #[test]
last_newline_index()709 fn last_newline_index() {
710 let mut writer = get_tag_writer();
711
712 writer
713 .write_str("12\n\n567\n90")
714 .expect("Unable to write to PlatformLogWriter");
715
716 assert_eq!(writer.last_newline_index, 7);
717 }
718
719 #[test]
output_specified_len_leaves_buffer_unchanged()720 fn output_specified_len_leaves_buffer_unchanged() {
721 let mut writer = get_tag_writer();
722 let log_string = "abcdefghij\n\nklm\nnopqr\nstuvwxyz";
723 writer
724 .write_str(log_string)
725 .expect("Unable to write to PlatformLogWriter");
726
727 writer.output_specified_len(5);
728
729 assert_eq!(
730 unsafe { assume_init_slice(&writer.buffer[..log_string.len()]) },
731 log_string.as_bytes()
732 );
733 }
734
735 #[test]
copy_bytes_to_start()736 fn copy_bytes_to_start() {
737 let mut writer = get_tag_writer();
738 writer
739 .write_str("0123456789")
740 .expect("Unable to write to PlatformLogWriter");
741
742 writer.copy_bytes_to_start(3, 2);
743
744 assert_eq!(
745 unsafe { assume_init_slice(&writer.buffer[..10]) },
746 "3423456789".as_bytes()
747 );
748 }
749
750 #[test]
copy_bytes_to_start_nop()751 fn copy_bytes_to_start_nop() {
752 let test_string = "Test_string_with\n\n\n\nnewlines\n";
753 let mut writer = get_tag_writer();
754 writer
755 .write_str(test_string)
756 .expect("Unable to write to PlatformLogWriter");
757
758 writer.copy_bytes_to_start(0, 20);
759 writer.copy_bytes_to_start(10, 0);
760
761 assert_eq!(
762 unsafe { assume_init_slice(&writer.buffer[..test_string.len()]) },
763 test_string.as_bytes()
764 );
765 }
766
get_tag_writer() -> PlatformLogWriter<'static>767 fn get_tag_writer() -> PlatformLogWriter<'static> {
768 PlatformLogWriter::new(
769 None,
770 Level::Warn,
771 CStr::from_bytes_with_nul(b"tag\0").unwrap(),
772 )
773 }
774
assume_init_slice<T>(slice: &[MaybeUninit<T>]) -> &[T]775 unsafe fn assume_init_slice<T>(slice: &[MaybeUninit<T>]) -> &[T] {
776 &*(slice as *const [MaybeUninit<T>] as *const [T])
777 }
778 }
779