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