1#[cfg(all(target_os = "linux", target_env = "ohos"))]
67extern crate ohos_hilog_sys as log_ffi;
68extern crate once_cell;
69use once_cell::sync::OnceCell;
70#[macro_use]
71extern crate log;
72
73extern crate env_logger;
74
75use log::{Level, LevelFilter, Log, Metadata, Record};
76#[cfg(all(target_os = "linux", target_env = "ohos"))]
77use log_ffi::LogType;
78#[cfg(all(target_os = "linux", target_env = "ohos"))]
79use log_ffi::LogLevel;
80use std::ffi::{CStr, CString};
81use std::fmt;
82use std::mem::{self, MaybeUninit};
83use std::ptr;
84
85pub use env_logger::filter::{Builder as FilterBuilder, Filter};
86pub use env_logger::fmt::Formatter;
87
88pub(crate) type FormatFn = Box<dyn Fn(&mut dyn fmt::Write, &Record) -> fmt::Result + Sync + Send>;
89
90#[cfg(all(target_os = "linux", target_env = "ohos"))]
92fn ohos_log(
93 buf_id: Option<LogType>,
94 level: LogLevel,
95 tag: &CStr,
96 msg: &CStr,
97) {
98 if let Some(buf_id) = buf_id {
99 unsafe {
100 log_ffi::OH_LOG_Print(
101 buf_id as LogType,
102 level,
103 0 as log_ffi::c_uint,
104 tag.as_ptr() as *const log_ffi::c_char,
105 msg.as_ptr() as *const log_ffi::c_char,
106 );
107 };
108 } else {
109 unsafe {
110 log_ffi::OH_LOG_Print(
111 log_ffi::LogType::LOG_APP,
112 level,
113 0 as log_ffi::c_uint,
114 tag.as_ptr() as *const log_ffi::c_char,
115 msg.as_ptr() as *const log_ffi::c_char,
116 );
117 };
118 }
119}
120
121#[cfg(not(all(target_os = "linux", target_env = "ohos")))]
123fn ohos_log(_buf_id: Option<u32>, _level: Level, _tag: &CStr, _msg: &CStr) {}
124
125pub struct OhosLogger {
127 config: OnceCell<Config>,
128}
129
130impl OhosLogger {
131 pub fn new(config: Config) -> OhosLogger {
133 OhosLogger {
134 config: OnceCell::from(config),
135 }
136 }
137
138 fn config(&self) -> &Config {
139 self.config.get_or_init(Config::default)
140 }
141}
142
143static OHOS_LOGGER: OnceCell<OhosLogger> = OnceCell::new();
144
145const LOGGING_TAG_MAX_LEN: usize = 23;
146const LOGGING_MSG_MAX_LEN: usize = 4000;
147
148impl Default for OhosLogger {
149 fn default() -> OhosLogger {
151 OhosLogger {
152 config: OnceCell::from(Config::default()),
153 }
154 }
155}
156
157impl Log for OhosLogger {
158 fn enabled(&self, metadata: &Metadata) -> bool {
159 let config = self.config();
160 metadata.level() <= config.log_level.unwrap_or_else(log::max_level)
162 }
163
164 fn log(&self, record: &Record) {
165 let config = self.config();
166
167 if !self.enabled(record.metadata()) {
168 return;
169 }
170
171 if !config.filter_matches(record) {
174 return;
175 }
176
177 let mut tag_bytes: [MaybeUninit<u8>; LOGGING_TAG_MAX_LEN + 1] = uninit_array();
179
180 let module_path = record.module_path().unwrap_or_default().to_owned();
181
182 let custom_tag = &config.tag;
184 let tag = custom_tag
185 .as_ref()
186 .map(|s| s.as_bytes())
187 .unwrap_or_else(|| module_path.as_bytes());
188
189 self.fill_tag_bytes(&mut tag_bytes, tag);
191 let tag: &CStr = unsafe { CStr::from_ptr(mem::transmute(tag_bytes.as_ptr())) };
193
194 let mut writer = PlatformLogWriter::new(config.buf_id, record.level(), tag);
197
198 let _ = match (custom_tag, &config.custom_format) {
201 (_, Some(format)) => format(&mut writer, record),
202 (Some(_), _) => fmt::write(
203 &mut writer,
204 format_args!("{}: {}", module_path, *record.args()),
205 ),
206 _ => fmt::write(&mut writer, *record.args()),
207 };
208
209 writer.flush();
211 }
212
213 fn flush(&self) {}
214}
215
216impl OhosLogger {
217 fn fill_tag_bytes(&self, array: &mut [MaybeUninit<u8>], tag: &[u8]) {
218 if tag.len() > LOGGING_TAG_MAX_LEN {
219 for (input, output) in tag
220 .iter()
221 .take(LOGGING_TAG_MAX_LEN - 2)
222 .chain(b"..\0".iter())
223 .zip(array.iter_mut())
224 {
225 output.write(*input);
226 }
227 } else {
228 for (input, output) in tag.iter().chain(b"\0".iter()).zip(array.iter_mut()) {
229 output.write(*input);
230 }
231 }
232 }
233}
234
235#[derive(Default)]
237pub struct Config {
238 log_level: Option<LevelFilter>,
239 #[cfg(all(target_os = "linux", target_env = "ohos"))]
240 buf_id: Option<LogType>,
241 #[cfg(not(all(target_os = "linux", target_env = "ohos")))]
242 buf_id: Option<u32>,
243 filter: Option<env_logger::filter::Filter>,
244 tag: Option<CString>,
245 custom_format: Option<FormatFn>,
246}
247
248impl Config {
249 pub fn with_max_level(mut self, level: LevelFilter) -> Self {
258 self.log_level = Some(level);
259 self
260 }
261
262 #[cfg(all(target_os = "linux", target_env = "ohos"))]
269 pub fn with_log_buffer(mut self, buf_id: LogType) -> Self {
270 self.buf_id = Some(buf_id);
271 self
272 }
273 #[cfg(not(all(target_os = "linux", target_env = "ohos")))]
274 pub fn with_log_buffer(mut self, buf_id: u32) -> Self {
275 self.buf_id = Some(buf_id);
276 self
277 }
278
279 fn filter_matches(&self, record: &Record) -> bool {
280 if let Some(ref filter) = self.filter {
281 filter.matches(record)
282 } else {
283 true
284 }
285 }
286
287 pub fn with_filter(mut self, filter: env_logger::filter::Filter) -> Self {
288 self.filter = Some(filter);
289 self
290 }
291
292 pub fn with_tag<S: Into<Vec<u8>>>(mut self, tag: S) -> Self {
293 self.tag = Some(CString::new(tag).expect("Can't convert tag to CString"));
294 self
295 }
296
297 pub fn format<F>(mut self, format: F) -> Self
307 where
308 F: Fn(&mut dyn fmt::Write, &Record) -> fmt::Result + Sync + Send + 'static,
309 {
310 self.custom_format = Some(Box::new(format));
311 self
312 }
313}
314
315pub struct PlatformLogWriter<'a> {
316 #[cfg(all(target_os = "linux", target_env = "ohos"))]
317 level: LogLevel,
318 #[cfg(not(all(target_os = "linux", target_env = "ohos")))]
319 level: Level,
320 #[cfg(all(target_os = "linux", target_env = "ohos"))]
321 buf_id: Option<LogType>,
322 #[cfg(not(all(target_os = "linux", target_env = "ohos")))]
323 buf_id: Option<u32>,
324 len: usize,
325 last_newline_index: usize,
326 tag: &'a CStr,
327 buffer: [MaybeUninit<u8>; LOGGING_MSG_MAX_LEN + 1],
328}
329
330impl<'a> PlatformLogWriter<'a> {
331 #[cfg(all(target_os = "linux", target_env = "ohos"))]
332 pub fn new_with_level(
333 buf_id: Option<LogType>,
334 level: LogLevel,
335 tag: &CStr,
336 ) -> PlatformLogWriter<'_> {
337 #[allow(deprecated)] PlatformLogWriter {
339 level,
340 buf_id: buf_id,
341 len: 0,
342 last_newline_index: 0,
343 tag,
344 buffer: uninit_array(),
345 }
346 }
347
348 #[cfg(all(target_os = "linux", target_env = "ohos"))]
349 pub fn new(buf_id: Option<LogType>, level: Level, tag: &CStr) -> PlatformLogWriter<'_> {
350 PlatformLogWriter::new_with_level(
351 buf_id,
352 match level {
353 Level::Warn => LogLevel::WARN,
354 Level::Info => LogLevel::INFO,
355 Level::Debug => LogLevel::DEBUG,
356 Level::Error => LogLevel::ERROR,
357 Level::Trace => LogLevel::DEBUG,
358 },
359 tag,
360 )
361 }
362
363 #[cfg(not(all(target_os = "linux", target_env = "ohos")))]
364 pub fn new(buf_id: Option<u32>, level: Level, tag: &CStr) -> PlatformLogWriter<'_> {
365 #[allow(deprecated)] PlatformLogWriter {
367 level: level,
368 buf_id,
369 len: 0,
370 last_newline_index: 0,
371 tag,
372 buffer: uninit_array(),
373 }
374 }
375
376 fn temporal_flush(&mut self) {
383 let total_len = self.len;
384
385 if total_len == 0 {
386 return;
387 }
388
389 if self.last_newline_index > 0 {
390 let copy_from_index = self.last_newline_index;
391 let remaining_chunk_len = total_len - copy_from_index;
392
393 self.output_specified_len(copy_from_index);
394 self.copy_bytes_to_start(copy_from_index, remaining_chunk_len);
395 self.len = remaining_chunk_len;
396 } else {
397 self.output_specified_len(total_len);
398 self.len = 0;
399 }
400 self.last_newline_index = 0;
401 }
402
403 pub fn flush(&mut self) {
405 let total_len = self.len;
406
407 if total_len == 0 {
408 return;
409 }
410
411 self.output_specified_len(total_len);
412 self.len = 0;
413 self.last_newline_index = 0;
414 }
415
416 fn output_specified_len(&mut self, len: usize) {
418 let mut last_byte = MaybeUninit::new(b'\0');
419
420 mem::swap(&mut last_byte, unsafe {
421 self.buffer.get_unchecked_mut(len)
422 });
423
424 let msg: &CStr = unsafe { CStr::from_ptr(self.buffer.as_ptr().cast()) };
425 ohos_log(self.buf_id, self.level, self.tag, msg);
426
427 unsafe { *self.buffer.get_unchecked_mut(len) = last_byte };
428 }
429
430 fn copy_bytes_to_start(&mut self, index: usize, len: usize) {
432 let dst = self.buffer.as_mut_ptr();
433 let src = unsafe { self.buffer.as_ptr().add(index) };
434 unsafe { ptr::copy(src, dst, len) };
435 }
436}
437
438impl<'a> fmt::Write for PlatformLogWriter<'a> {
439 fn write_str(&mut self, s: &str) -> fmt::Result {
440 let mut incomming_bytes = s.as_bytes();
441
442 while !incomming_bytes.is_empty() {
443 let len = self.len;
444
445 let new_len = len + incomming_bytes.len();
447 let last_newline = self.buffer[len..LOGGING_MSG_MAX_LEN]
448 .iter_mut()
449 .zip(incomming_bytes)
450 .enumerate()
451 .fold(None, |acc, (i, (output, input))| {
452 output.write(*input);
453 if *input == b'\n' {
454 Some(i)
455 } else {
456 acc
457 }
458 });
459
460 if let Some(newline) = last_newline {
462 self.last_newline_index = len + newline;
463 }
464
465 let written_len = if new_len <= LOGGING_MSG_MAX_LEN {
467 self.len = new_len;
469 new_len - len } else {
471 self.len = LOGGING_MSG_MAX_LEN;
473 self.temporal_flush();
474
475 LOGGING_MSG_MAX_LEN - len };
477
478 incomming_bytes = &incomming_bytes[written_len..];
479 }
480
481 Ok(())
482 }
483}
484
485pub fn log(record: &Record) {
490 OHOS_LOGGER
491 .get_or_init(OhosLogger::default)
492 .log(record)
493}
494
495pub fn init_once(config: Config) {
503 let log_level = config.log_level;
504 let logger = OHOS_LOGGER.get_or_init(|| OhosLogger::new(config));
505
506 if let Err(err) = log::set_logger(logger) {
507 debug!("ohos_hilog: log::set_logger failed: {}", err);
508 } else if let Some(level) = log_level {
509 log::set_max_level(level);
510 }
511}
512
513fn uninit_array<const N: usize, T>() -> [MaybeUninit<T>; N] {
515 unsafe { MaybeUninit::uninit().assume_init() }
517}
518
519#[cfg(test)]
520mod tests {
521 use super::*;
522 use std::fmt::Write;
523 use std::sync::atomic::{AtomicBool, Ordering};
524
525 #[test]
526 fn check_config_values() {
527 let config = Config::default()
529 .with_max_level(LevelFilter::Trace)
530 .with_log_buffer(0)
531 .with_tag("my_app");
532
533 assert_eq!(config.log_level, Some(LevelFilter::Trace));
534 assert_eq!(config.buf_id, Some(0));
535 assert_eq!(config.tag, Some(CString::new("my_app").unwrap()));
536 }
537
538 #[test]
539 fn log_calls_formatter() {
540 static FORMAT_FN_WAS_CALLED: AtomicBool = AtomicBool::new(false);
541 let config = Config::default()
542 .with_max_level(LevelFilter::Info)
543 .format(|_, _| {
544 FORMAT_FN_WAS_CALLED.store(true, Ordering::SeqCst);
545 Ok(())
546 });
547 let logger = OhosLogger::new(config);
548
549 logger.log(&Record::builder().level(Level::Info).build());
550
551 assert!(FORMAT_FN_WAS_CALLED.load(Ordering::SeqCst));
552 }
553
554 #[test]
555 fn logger_enabled_threshold() {
556 let logger = OhosLogger::new(Config::default().with_max_level(LevelFilter::Info));
557
558 assert!(logger.enabled(&log::MetadataBuilder::new().level(Level::Warn).build()));
559 assert!(logger.enabled(&log::MetadataBuilder::new().level(Level::Info).build()));
560 assert!(!logger.enabled(&log::MetadataBuilder::new().level(Level::Debug).build()));
561 }
562
563 #[test]
566 fn config_filter_match() {
567 let info_record = Record::builder().level(Level::Info).build();
568 let debug_record = Record::builder().level(Level::Debug).build();
569
570 let info_all_filter = env_logger::filter::Builder::new().parse("info").build();
571 let info_all_config = Config::default().with_filter(info_all_filter);
572
573 assert!(info_all_config.filter_matches(&info_record));
574 assert!(!info_all_config.filter_matches(&debug_record));
575 }
576
577 #[test]
578 fn fill_tag_bytes_truncates_long_tag() {
579 let logger = OhosLogger::new(Config::default());
580 let too_long_tag: [u8; LOGGING_TAG_MAX_LEN + 20] = [b'a'; LOGGING_TAG_MAX_LEN + 20];
581
582 let mut result: [MaybeUninit<u8>; LOGGING_TAG_MAX_LEN + 1] = uninit_array();
583 logger.fill_tag_bytes(&mut result, &too_long_tag);
584
585 let mut expected_result = [b'a'; LOGGING_TAG_MAX_LEN - 2].to_vec();
586 expected_result.extend("..\0".as_bytes());
587 assert_eq!(unsafe { assume_init_slice(&result) }, expected_result);
588 }
589
590 #[test]
591 fn fill_tag_bytes_keeps_short_tag() {
592 let logger = OhosLogger::new(Config::default());
593 let short_tag: [u8; 3] = [b'a'; 3];
594
595 let mut result: [MaybeUninit<u8>; LOGGING_TAG_MAX_LEN + 1] = uninit_array();
596 logger.fill_tag_bytes(&mut result, &short_tag);
597
598 let mut expected_result = short_tag.to_vec();
599 expected_result.push(0);
600 assert_eq!(unsafe { assume_init_slice(&result[..4]) }, expected_result);
601 }
602
603 #[test]
604 fn platform_log_writer_init_values() {
605 let tag = CStr::from_bytes_with_nul(b"tag\0").unwrap();
606
607 let writer = PlatformLogWriter::new(None, Level::Warn, tag);
608
609 assert_eq!(writer.tag, tag);
610 #[cfg(not(all(target_os = "linux", target_env = "ohos")))]
612 assert_eq!(writer.level, Level::Warn);
613 }
614
615 #[test]
616 fn temporal_flush() {
617 let mut writer = get_tag_writer();
618
619 writer
620 .write_str("12\n\n567\n90")
621 .expect("Unable to write to PlatformLogWriter");
622
623 assert_eq!(writer.len, 10);
624 writer.temporal_flush();
625 assert_eq!(writer.len, 3);
627 assert_eq!(writer.last_newline_index, 0);
628 assert_eq!(
629 unsafe { assume_init_slice(&writer.buffer[..writer.len]) },
630 "\n90".as_bytes()
631 );
632
633 writer.temporal_flush();
634 assert_eq!(writer.len, 0);
636 assert_eq!(writer.last_newline_index, 0);
637 }
638
639 #[test]
640 fn flush() {
641 let mut writer = get_tag_writer();
642 writer
643 .write_str("abcdefghij\n\nklm\nnopqr\nstuvwxyz")
644 .expect("Unable to write to PlatformLogWriter");
645
646 writer.flush();
647
648 assert_eq!(writer.last_newline_index, 0);
649 assert_eq!(writer.len, 0);
650 }
651
652 #[test]
653 fn last_newline_index() {
654 let mut writer = get_tag_writer();
655
656 writer
657 .write_str("12\n\n567\n90")
658 .expect("Unable to write to PlatformLogWriter");
659
660 assert_eq!(writer.last_newline_index, 7);
661 }
662
663 #[test]
664 fn output_specified_len_leaves_buffer_unchanged() {
665 let mut writer = get_tag_writer();
666 let log_string = "abcdefghij\n\nklm\nnopqr\nstuvwxyz";
667 writer
668 .write_str(log_string)
669 .expect("Unable to write to PlatformLogWriter");
670
671 writer.output_specified_len(5);
672
673 assert_eq!(
674 unsafe { assume_init_slice(&writer.buffer[..log_string.len()]) },
675 log_string.as_bytes()
676 );
677 }
678
679 #[test]
680 fn copy_bytes_to_start() {
681 let mut writer = get_tag_writer();
682 writer
683 .write_str("0123456789")
684 .expect("Unable to write to PlatformLogWriter");
685
686 writer.copy_bytes_to_start(3, 2);
687
688 assert_eq!(
689 unsafe { assume_init_slice(&writer.buffer[..10]) },
690 "3423456789".as_bytes()
691 );
692 }
693
694 #[test]
695 fn copy_bytes_to_start_nop() {
696 let test_string = "Test_string_with\n\n\n\nnewlines\n";
697 let mut writer = get_tag_writer();
698 writer
699 .write_str(test_string)
700 .expect("Unable to write to PlatformLogWriter");
701
702 writer.copy_bytes_to_start(0, 20);
703 writer.copy_bytes_to_start(10, 0);
704
705 assert_eq!(
706 unsafe { assume_init_slice(&writer.buffer[..test_string.len()]) },
707 test_string.as_bytes()
708 );
709 }
710
711 fn get_tag_writer() -> PlatformLogWriter<'static> {
712 PlatformLogWriter::new(
713 None,
714 Level::Warn,
715 CStr::from_bytes_with_nul(b"tag\0").unwrap(),
716 )
717 }
718
719 unsafe fn assume_init_slice<T>(slice: &[MaybeUninit<T>]) -> &[T] {
720 &*(slice as *const [MaybeUninit<T>] as *const [T])
721 }
722}