1 // Copyright 2021, The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 //! Provides a universal logger interface that allows logging both on-device (using android_logger)
16 //! and on-host (using env_logger).
17 //! On-host, this allows the use of the RUST_LOG environment variable as documented in
18 //! https://docs.rs/env_logger.
19 use std::ffi::CString;
20 use std::sync::atomic::{AtomicBool, Ordering};
21
22 static LOGGER_INITIALIZED: AtomicBool = AtomicBool::new(false);
23
24 type FormatFn = Box<dyn Fn(&log::Record) -> String + Sync + Send>;
25
26 /// Logger configuration, opportunistically mapped to configuration parameters for android_logger
27 /// or env_logger where available.
28 #[derive(Default)]
29 pub struct Config<'a> {
30 log_level: Option<log::Level>,
31 custom_format: Option<FormatFn>,
32 filter: Option<&'a str>,
33 #[allow(dead_code)] // Field is only used on device, and ignored on host.
34 tag: Option<CString>,
35 }
36
37 /// Based on android_logger::Config
38 impl<'a> Config<'a> {
39 /// Change the minimum log level.
40 ///
41 /// All values above the set level are logged. For example, if
42 /// `Warn` is set, the `Error` is logged too, but `Info` isn't.
with_min_level(mut self, level: log::Level) -> Self43 pub fn with_min_level(mut self, level: log::Level) -> Self {
44 self.log_level = Some(level);
45 self
46 }
47
48 /// Set a log tag. Only used on device.
with_tag_on_device<S: Into<Vec<u8>>>(mut self, tag: S) -> Self49 pub fn with_tag_on_device<S: Into<Vec<u8>>>(mut self, tag: S) -> Self {
50 self.tag = Some(CString::new(tag).expect("Can't convert tag to CString"));
51 self
52 }
53
54 /// Set the format function for formatting the log output.
55 /// ```
56 /// # use universal_logger::Config;
57 /// universal_logger::init(
58 /// Config::default()
59 /// .with_min_level(log::Level::Trace)
60 /// .format(|record| format!("my_app: {}", record.args()))
61 /// )
62 /// ```
format<F>(mut self, format: F) -> Self where F: Fn(&log::Record) -> String + Sync + Send + 'static,63 pub fn format<F>(mut self, format: F) -> Self
64 where
65 F: Fn(&log::Record) -> String + Sync + Send + 'static,
66 {
67 self.custom_format = Some(Box::new(format));
68 self
69 }
70
71 /// Set a filter, using the format specified in https://docs.rs/env_logger.
with_filter(mut self, filter: &'a str) -> Self72 pub fn with_filter(mut self, filter: &'a str) -> Self {
73 self.filter = Some(filter);
74 self
75 }
76 }
77
78 /// Initializes logging on host. Returns false if logging is already initialized.
79 /// Config values take precedence over environment variables for host logging.
80 #[cfg(not(target_os = "android"))]
init(config: Config) -> bool81 pub fn init(config: Config) -> bool {
82 // Return immediately if the logger is already initialized.
83 if LOGGER_INITIALIZED.fetch_or(true, Ordering::SeqCst) {
84 return false;
85 }
86
87 let mut builder = env_logger::Builder::from_default_env();
88 if let Some(log_level) = config.log_level {
89 builder.filter_level(log_level.to_level_filter());
90 }
91 if let Some(custom_format) = config.custom_format {
92 use std::io::Write; // Trait used by write!() macro, but not in Android code
93
94 builder.format(move |f, r| {
95 let formatted = custom_format(r);
96 writeln!(f, "{}", formatted)
97 });
98 }
99 if let Some(filter_str) = config.filter {
100 builder.parse_filters(filter_str);
101 }
102
103 builder.init();
104 true
105 }
106
107 /// Initializes logging on device. Returns false if logging is already initialized.
108 #[cfg(target_os = "android")]
init(config: Config) -> bool109 pub fn init(config: Config) -> bool {
110 // Return immediately if the logger is already initialized.
111 if LOGGER_INITIALIZED.fetch_or(true, Ordering::SeqCst) {
112 return false;
113 }
114
115 // We do not have access to the private variables in android_logger::Config, so we have to use
116 // the builder instead.
117 let mut builder = android_logger::Config::default();
118 if let Some(log_level) = config.log_level {
119 builder = builder.with_min_level(log_level);
120 }
121 if let Some(custom_format) = config.custom_format {
122 builder = builder.format(move |f, r| {
123 let formatted = custom_format(r);
124 write!(f, "{}", formatted)
125 });
126 }
127 if let Some(filter_str) = config.filter {
128 let filter = env_logger::filter::Builder::new().parse(filter_str).build();
129 builder = builder.with_filter(filter);
130 }
131 if let Some(tag) = config.tag {
132 builder = builder.with_tag(tag);
133 }
134
135 android_logger::init_once(builder);
136 true
137 }
138
139 /// Note that the majority of tests checking behavior are under the tests/ folder, as they all
140 /// require independent initialization steps. The local test module just performs some basic crash
141 /// testing without performing initialization.
142 #[cfg(test)]
143 mod tests {
144 use super::*;
145
146 #[test]
test_with_min_level()147 fn test_with_min_level() {
148 let config = Config::default()
149 .with_min_level(log::Level::Trace)
150 .with_min_level(log::Level::Error);
151
152 assert_eq!(config.log_level, Some(log::Level::Error));
153 }
154
155 #[test]
test_with_filter()156 fn test_with_filter() {
157 let filter = "debug,hello::crate=trace";
158 let config = Config::default().with_filter(filter);
159
160 assert_eq!(config.filter.unwrap(), filter)
161 }
162
163 #[test]
test_with_tag_on_device()164 fn test_with_tag_on_device() {
165 let config = Config::default().with_tag_on_device("my_app");
166
167 assert_eq!(config.tag.unwrap(), CString::new("my_app").unwrap());
168 }
169
170 #[test]
test_format()171 fn test_format() {
172 let config = Config::default().format(|record| format!("my_app: {}", record.args()));
173
174 assert!(config.custom_format.is_some());
175 }
176 }
177