blob: c1a77a8479d50a9ce876b7d1ffbee78d664f4333 [file] [log] [blame]
Bram Bonnéd76ddd62021-03-25 10:29:51 +01001// 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.
19use std::ffi::CString;
20use std::sync::atomic::{AtomicBool, Ordering};
21
22static LOGGER_INITIALIZED: AtomicBool = AtomicBool::new(false);
23
24type 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)]
29pub struct Config<'a> {
Jeff Vander Stoep6a26c182024-01-29 12:54:47 +010030 log_level: Option<log::LevelFilter>,
Bram Bonnéd76ddd62021-03-25 10:29:51 +010031 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
38impl<'a> Config<'a> {
Jeff Vander Stoep6a26c182024-01-29 12:54:47 +010039 /// Changes the maximum log level.
Bram Bonnéd76ddd62021-03-25 10:29:51 +010040 ///
Jeff Vander Stoep6a26c182024-01-29 12:54:47 +010041 /// 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.
47 pub fn with_max_level(mut self, level: log::LevelFilter) -> Self {
Bram Bonnéd76ddd62021-03-25 10:29:51 +010048 self.log_level = Some(level);
49 self
50 }
51
52 /// Set a log tag. Only used on device.
53 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()
Jeff Vander Stoep6a26c182024-01-29 12:54:47 +010063 /// .with_max_level(log::LevelFilter::Trace)
Bram Bonnéd76ddd62021-03-25 10:29:51 +010064 /// .format(|record| format!("my_app: {}", record.args()))
65 /// )
66 /// ```
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.
76 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"))]
85pub 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 {
Jeff Vander Stoep6a26c182024-01-29 12:54:47 +010093 builder.filter_level(log_level);
Bram Bonnéd76ddd62021-03-25 10:29:51 +010094 }
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")]
113pub 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 {
Jeff Vander Stoep6a26c182024-01-29 12:54:47 +0100123 builder = builder.with_max_level(log_level);
Bram Bonnéd76ddd62021-03-25 10:29:51 +0100124 }
125 if let Some(custom_format) = config.custom_format {
126 builder = builder.format(move |f, r| {
127 let formatted = custom_format(r);
128 write!(f, "{}", formatted)
129 });
130 }
131 if let Some(filter_str) = config.filter {
132 let filter = env_logger::filter::Builder::new().parse(filter_str).build();
133 builder = builder.with_filter(filter);
134 }
135 if let Some(tag) = config.tag {
136 builder = builder.with_tag(tag);
137 }
138
139 android_logger::init_once(builder);
140 true
141}
142
143/// Note that the majority of tests checking behavior are under the tests/ folder, as they all
144/// require independent initialization steps. The local test module just performs some basic crash
145/// testing without performing initialization.
146#[cfg(test)]
147mod tests {
148 use super::*;
149
150 #[test]
Jeff Vander Stoep6a26c182024-01-29 12:54:47 +0100151 fn test_with_max_level() {
152 let config = Config::default()
153 .with_max_level(log::LevelFilter::Trace)
154 .with_max_level(log::LevelFilter::Error);
155
156 assert_eq!(config.log_level, Some(log::LevelFilter::Error));
Bram Bonnéd76ddd62021-03-25 10:29:51 +0100157 }
158
159 #[test]
160 fn test_with_filter() {
161 let filter = "debug,hello::crate=trace";
162 let config = Config::default().with_filter(filter);
163
164 assert_eq!(config.filter.unwrap(), filter)
165 }
166
167 #[test]
168 fn test_with_tag_on_device() {
169 let config = Config::default().with_tag_on_device("my_app");
170
171 assert_eq!(config.tag.unwrap(), CString::new("my_app").unwrap());
172 }
173
174 #[test]
175 fn test_format() {
176 let config = Config::default().format(|record| format!("my_app: {}", record.args()));
177
178 assert!(config.custom_format.is_some());
179 }
180}