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}