android_logger/
lib.rs

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")]
67extern crate android_log_sys as log_ffi;
68
69use log::{Log, Metadata, Record};
70use std::ffi::{CStr, CString};
71use std::fmt;
72use std::mem::MaybeUninit;
73use std::sync::OnceLock;
74
75use crate::arrays::{fill_tag_bytes, uninit_array};
76use crate::platform_log_writer::PlatformLogWriter;
77pub use config::Config;
78pub use env_filter::{Builder as FilterBuilder, Filter};
79pub use id::LogId;
80
81pub(crate) type FormatFn = Box<dyn Fn(&mut dyn fmt::Write, &Record) -> fmt::Result + Sync + Send>;
82
83mod arrays;
84mod config;
85mod id;
86mod platform_log_writer;
87#[cfg(test)]
88mod tests;
89
90/// Outputs log to Android system.
91#[cfg(target_os = "android")]
92fn android_log(
93    buf_id: Option<log_ffi::log_id_t>,
94    prio: log_ffi::LogPriority,
95    tag: &CStr,
96    msg: &CStr,
97) {
98    if let Some(buf_id) = buf_id {
99        unsafe {
100            log_ffi::__android_log_buf_write(
101                buf_id as log_ffi::c_int,
102                prio as log_ffi::c_int,
103                tag.as_ptr() as *const log_ffi::c_char,
104                msg.as_ptr() as *const log_ffi::c_char,
105            );
106        };
107    } else {
108        unsafe {
109            log_ffi::__android_log_write(
110                prio as log_ffi::c_int,
111                tag.as_ptr() as *const log_ffi::c_char,
112                msg.as_ptr() as *const log_ffi::c_char,
113            );
114        };
115    }
116}
117
118/// Dummy output placeholder for tests.
119#[cfg(not(target_os = "android"))]
120fn android_log(_buf_id: Option<LogId>, _priority: log::Level, _tag: &CStr, _msg: &CStr) {}
121
122/// Underlying android logger backend
123#[derive(Debug, Default)]
124pub struct AndroidLogger {
125    config: OnceLock<Config>,
126}
127
128impl AndroidLogger {
129    /// Create new logger instance from config
130    pub fn new(config: Config) -> AndroidLogger {
131        AndroidLogger {
132            config: OnceLock::from(config),
133        }
134    }
135
136    fn config(&self) -> &Config {
137        self.config.get_or_init(Config::default)
138    }
139}
140
141static ANDROID_LOGGER: OnceLock<AndroidLogger> = OnceLock::new();
142
143/// Maximum length of a tag that does not require allocation.
144///
145/// Tags configured explicitly in [Config] will not cause an extra allocation. When the tag is
146/// derived from the module path, paths longer than this limit will trigger an allocation for each
147/// log statement.
148///
149/// The terminating nullbyte does not count towards this limit.
150const LOGGING_TAG_MAX_LEN: usize = 127;
151const LOGGING_MSG_MAX_LEN: usize = 4000;
152
153impl Log for AndroidLogger {
154    fn enabled(&self, metadata: &Metadata) -> bool {
155        self.config()
156            .is_loggable(metadata.target(), metadata.level())
157    }
158
159    fn log(&self, record: &Record) {
160        let config = self.config();
161
162        if !self.enabled(record.metadata()) {
163            return;
164        }
165
166        // this also checks the level, but only if a filter was
167        // installed.
168        if !config.filter_matches(record) {
169            return;
170        }
171
172        // Temporary storage for null-terminating record.module_path() if it's needed.
173        // Tags too long to fit here cause allocation.
174        let mut tag_bytes: [MaybeUninit<u8>; LOGGING_TAG_MAX_LEN + 1] = uninit_array();
175        // In case we end up allocating, keep the CString alive.
176        let _owned_tag;
177
178        let module_path = record.module_path().unwrap_or_default();
179
180        let tag = if let Some(tag) = &config.tag {
181            tag
182        } else if module_path.len() < tag_bytes.len() {
183            fill_tag_bytes(&mut tag_bytes, module_path.as_bytes())
184        } else {
185            // Tag longer than available stack buffer; allocate.
186            _owned_tag = CString::new(module_path.as_bytes())
187                .expect("record.module_path() shouldn't contain nullbytes");
188            _owned_tag.as_ref()
189        };
190
191        // message must not exceed LOGGING_MSG_MAX_LEN
192        // therefore split log message into multiple log calls
193        let mut writer = PlatformLogWriter::new(config.buf_id, record.level(), tag);
194
195        // If a custom tag is used, add the module path to the message.
196        // Use PlatformLogWriter to output chunks if they exceed max size.
197        let _ = match (&config.tag, &config.custom_format) {
198            (_, Some(format)) => format(&mut writer, record),
199            (Some(_), _) => fmt::write(
200                &mut writer,
201                format_args!("{}: {}", module_path, *record.args()),
202            ),
203            _ => fmt::write(&mut writer, *record.args()),
204        };
205
206        // output the remaining message (this would usually be the most common case)
207        writer.flush();
208    }
209
210    fn flush(&self) {}
211}
212
213/// Send a log record to Android logging backend.
214///
215/// This action does not require initialization. However, without initialization it
216/// will use the default filter, which allows all logs.
217pub fn log(record: &Record) {
218    ANDROID_LOGGER
219        .get_or_init(AndroidLogger::default)
220        .log(record)
221}
222
223/// Initializes the global logger with an android logger.
224///
225/// This can be called many times, but will only initialize logging once,
226/// and will not replace any other previously initialized logger.
227///
228/// It is ok to call this at the activity creation, and it will be
229/// repeatedly called on every lifecycle restart (i.e. screen rotation).
230pub fn init_once(config: Config) {
231    let log_level = config.log_level;
232    let logger = ANDROID_LOGGER.get_or_init(|| AndroidLogger::new(config));
233
234    if let Err(err) = log::set_logger(logger) {
235        log::debug!("android_logger: log::set_logger failed: {}", err);
236    } else if let Some(level) = log_level {
237        log::set_max_level(level);
238    }
239}