• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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::LevelFilter>,
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     /// Changes the maximum log level.
40     ///
41     /// Note, that `Trace` is the maximum level, because it provides the
42     /// maximum amount of detail in the emitted logs.
43     ///
44     /// If `Off` level is provided, then nothing is logged at all.
45     ///
46     /// [`log::max_level()`] is considered as the default level.
with_max_level(mut self, level: log::LevelFilter) -> Self47     pub fn with_max_level(mut self, level: log::LevelFilter) -> Self {
48         self.log_level = Some(level);
49         self
50     }
51 
52     /// Set a log tag. Only used on device.
with_tag_on_device<S: Into<Vec<u8>>>(mut self, tag: S) -> Self53     pub fn with_tag_on_device<S: Into<Vec<u8>>>(mut self, tag: S) -> Self {
54         self.tag = Some(CString::new(tag).expect("Can't convert tag to CString"));
55         self
56     }
57 
58     /// Set the format function for formatting the log output.
59     /// ```
60     /// # use universal_logger::Config;
61     /// universal_logger::init(
62     ///     Config::default()
63     ///         .with_max_level(log::LevelFilter::Trace)
64     ///         .format(|record| format!("my_app: {}", record.args()))
65     /// )
66     /// ```
format<F>(mut self, format: F) -> Self where F: Fn(&log::Record) -> String + Sync + Send + 'static,67     pub fn format<F>(mut self, format: F) -> Self
68     where
69         F: Fn(&log::Record) -> String + Sync + Send + 'static,
70     {
71         self.custom_format = Some(Box::new(format));
72         self
73     }
74 
75     /// Set a filter, using the format specified in https://docs.rs/env_logger.
with_filter(mut self, filter: &'a str) -> Self76     pub fn with_filter(mut self, filter: &'a str) -> Self {
77         self.filter = Some(filter);
78         self
79     }
80 }
81 
82 /// Initializes logging on host. Returns false if logging is already initialized.
83 /// Config values take precedence over environment variables for host logging.
84 #[cfg(not(target_os = "android"))]
init(config: Config) -> bool85 pub fn init(config: Config) -> bool {
86     // Return immediately if the logger is already initialized.
87     if LOGGER_INITIALIZED.fetch_or(true, Ordering::SeqCst) {
88         return false;
89     }
90 
91     let mut builder = env_logger::Builder::from_default_env();
92     if let Some(log_level) = config.log_level {
93         builder.filter_level(log_level);
94     }
95     if let Some(custom_format) = config.custom_format {
96         use std::io::Write; // Trait used by write!() macro, but not in Android code
97 
98         builder.format(move |f, r| {
99             let formatted = custom_format(r);
100             writeln!(f, "{}", formatted)
101         });
102     }
103     if let Some(filter_str) = config.filter {
104         builder.parse_filters(filter_str);
105     }
106 
107     builder.init();
108     true
109 }
110 
111 /// Initializes logging on device. Returns false if logging is already initialized.
112 #[cfg(target_os = "android")]
init(config: Config) -> bool113 pub fn init(config: Config) -> bool {
114     // Return immediately if the logger is already initialized.
115     if LOGGER_INITIALIZED.fetch_or(true, Ordering::SeqCst) {
116         return false;
117     }
118 
119     // We do not have access to the private variables in android_logger::Config, so we have to use
120     // the builder instead.
121     let mut builder = android_logger::Config::default();
122     if let Some(log_level) = config.log_level {
123         builder = builder.with_max_level(log_level);
124     } else {
125         builder = builder.with_max_level(log::LevelFilter::Off);
126     }
127     if let Some(custom_format) = config.custom_format {
128         builder = builder.format(move |f, r| {
129             let formatted = custom_format(r);
130             write!(f, "{}", formatted)
131         });
132     }
133     if let Some(filter_str) = config.filter {
134         let filter = env_filter::Builder::new().parse(filter_str).build();
135         builder = builder.with_filter(filter);
136     }
137     if let Some(tag) = config.tag {
138         builder = builder.with_tag(tag);
139     }
140 
141     android_logger::init_once(builder);
142     true
143 }
144 
145 /// Note that the majority of tests checking behavior are under the tests/ folder, as they all
146 /// require independent initialization steps. The local test module just performs some basic crash
147 /// testing without performing initialization.
148 #[cfg(test)]
149 mod tests {
150     use super::*;
151 
152     #[test]
test_with_max_level()153     fn test_with_max_level() {
154         let config = Config::default()
155             .with_max_level(log::LevelFilter::Trace)
156             .with_max_level(log::LevelFilter::Error);
157 
158         assert_eq!(config.log_level, Some(log::LevelFilter::Error));
159     }
160 
161     #[test]
test_with_filter()162     fn test_with_filter() {
163         let filter = "debug,hello::crate=trace";
164         let config = Config::default().with_filter(filter);
165 
166         assert_eq!(config.filter.unwrap(), filter)
167     }
168 
169     #[test]
test_with_tag_on_device()170     fn test_with_tag_on_device() {
171         let config = Config::default().with_tag_on_device("my_app");
172 
173         assert_eq!(config.tag.unwrap(), CString::new("my_app").unwrap());
174     }
175 
176     #[test]
test_format()177     fn test_format() {
178         let config = Config::default().format(|record| format!("my_app: {}", record.args()));
179 
180         assert!(config.custom_format.is_some());
181     }
182 }
183