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