• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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