Skip to content

Commit f2ff257

Browse files
committed
Remove max_level in favour of extracting it from the Filter
Documentation for `log` specifies that all implementations must have a way to initialize `log::set_max_level()` (because it otherwise defaults to `Off`), in our case via the `with_max_level()` builder - and by calling `init_once()`. Over time `android_logger` received `env_filter` support for much more fine-grained control over filtering, making `android_logger` similar to `env_logger` in its implementation. With this change however, ambiguity arises when independently configuring log levels through `.with_max_level()` and `env_filter::Filter`. The former acts as an early performance save directly in `log::log!()` macros while the latter applies additional filtering inside `android_logger`. In short: `log::max_level()` must be at least as high as what `Filter` can process, otherwise messages get lost before they reach the `Filter`. `env_logger` solved this ambiguity by hiding direct access to `log::set_max_level()` as an implementation detail and initializing it to the highest level that could possibly be processed by the `Filter`. Mimick this inside `android_logger` by removing the `with_max_level()` setter. In addition all internal `is_enabled()` filtering and related tests are removed because we rely on the `log` macros themselves to filter, and no longer track `max_level` inside. Keeping in mind that any user can techincally change `log::set_max_level()` to increase or decrease global verbosity. The only complexity remains around `__android_log_is_loggable_len()` on Android 30 and above (if enabled on the crate by the user - not necessarily based on the actual minimum nor target SDK level yet!) which asks the Android system for `env_filter`-like per-module (tag) level overrides.
1 parent 71fd4fd commit f2ff257

File tree

9 files changed

+109
-92
lines changed

9 files changed

+109
-92
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ jobs:
4141
components: rustfmt, clippy
4242

4343
- run: cargo fmt --check
44+
- run: cargo clippy --target=${{ matrix.target }} ${{ matrix.features }} -- -Dwarnings
4445
- run: cargo build --target=${{ matrix.target }} ${{ matrix.features }}
4546
- run: cargo doc --target=${{ matrix.target }} ${{ matrix.features }}
4647
env:

examples/system_log_level_overrides.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -68,16 +68,16 @@
6868
//! ```
6969
7070
fn main() {
71+
todo!("This test and its comments are no longer true after with_max_level is removed");
7172
android_logger::init_once(
72-
android_logger::Config::default()
73-
.with_tag("log_test")
74-
// If set, this is the highest level to log unless overriddeby by the system.
75-
// Note the verbosity can be *increased* through system properties.
76-
.with_max_level(log::LevelFilter::Info),
73+
android_logger::Config::default().with_tag("log_test"),
74+
// If set, this is the highest level to log unless overridden by by the system.
75+
// Note the verbosity can be *increased* through system properties.
76+
// .with_max_level(log::LevelFilter::Info),
7777
);
7878
// The log crate applies its filtering before we even get to android_logger.
7979
// Pass everything down so that Android's liblog can determine the log level instead.
80-
log::set_max_level(log::LevelFilter::Trace);
80+
// log::set_max_level(log::LevelFilter::Trace);
8181

8282
log::trace!("trace");
8383
log::debug!("debug");

src/config.rs

Lines changed: 34 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,22 @@
11
use crate::{FormatFn, LogId};
2-
use log::{Level, LevelFilter, Record};
2+
#[cfg(all(target_os = "android", feature = "android-api-30"))]
3+
use log::LevelFilter;
4+
use log::{Level, Record};
35
use std::ffi::CString;
46
use std::fmt;
57

68
/// Filter for android logger.
79
#[derive(Default)]
810
pub struct Config {
9-
pub(crate) log_level: Option<LevelFilter>,
1011
pub(crate) buf_id: Option<LogId>,
11-
filter: Option<env_filter::Filter>,
12+
pub(crate) filter: Option<env_filter::Filter>,
1213
pub(crate) tag: Option<CString>,
1314
pub(crate) custom_format: Option<FormatFn>,
1415
}
1516

1617
impl fmt::Debug for Config {
1718
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1819
f.debug_struct("Config")
19-
.field("log_level", &self.log_level)
2020
.field("buf_id", &self.buf_id)
2121
.field("filter", &self.filter)
2222
.field("tag", &self.tag)
@@ -62,38 +62,44 @@ fn android_is_loggable_len(
6262
}
6363

6464
#[cfg(not(all(target_os = "android", feature = "android-api-30")))]
65-
fn default_is_loggable(_tag: &str, record_level: Level, config_level: Option<LevelFilter>) -> bool {
66-
record_level <= config_level.unwrap_or_else(log::max_level)
65+
pub(crate) fn is_loggable(_tag: &str, _record_level: Level) -> bool {
66+
// There is nothing to test here, the `log` macros already checked the variable
67+
// `log::max_level()` before calling into the implementation.
68+
// The tests ensure this by creating and calling into `AndroidLogger::log()` without
69+
// `set_max_level()` from `init_once()`, and expect the message to be logged.
70+
true
6771
}
6872

6973
#[cfg(all(target_os = "android", feature = "android-api-30"))]
70-
fn android_is_loggable(tag: &str, record_level: Level, config_level: Option<LevelFilter>) -> bool {
74+
pub(crate) fn is_loggable(tag: &str, record_level: Level) -> bool {
7175
let prio = android_log_priority_from_level(record_level);
7276
// Priority to use in case no system-wide or process-wide overrides are set.
73-
let default_prio = match config_level {
74-
Some(level_filter) => match level_filter.to_level() {
75-
Some(level) => android_log_priority_from_level(level),
76-
// LevelFilter::to_level() returns None only for LevelFilter::Off
77-
None => android_log_sys::LogPriority::SILENT,
78-
},
79-
None => android_log_sys::LogPriority::INFO,
77+
// WARNING: Reading live `log::max_level()` state here would break tests, for example when
78+
// `AndroidLogger` is constructed and `AndroidLogger::log()` is called _without_ going through
79+
// `init_once()` which would call `log::set_max_level()`, leaving this at `Off`. Currently no
80+
// tests exist that run on live Android devices and/or mock `__android_log_is_loggable_len()`
81+
// such that this function can be called.
82+
let default_prio = match log::max_level().to_level() {
83+
Some(level) => android_log_priority_from_level(level),
84+
// LevelFilter::to_level() returns None only for LevelFilter::Off
85+
None => android_log_sys::LogPriority::SILENT,
8086
};
8187
android_is_loggable_len(prio, tag, default_prio)
8288
}
8389

8490
impl Config {
85-
/// Changes the maximum log level.
86-
///
87-
/// Note, that `Trace` is the maximum level, because it provides the
88-
/// maximum amount of detail in the emitted logs.
89-
///
90-
/// If `Off` level is provided, then nothing is logged at all.
91-
///
92-
/// [`log::max_level()`] is considered as the default level.
93-
pub fn with_max_level(mut self, level: LevelFilter) -> Self {
94-
self.log_level = Some(level);
95-
self
96-
}
91+
// /// Changes the maximum log level.
92+
// ///
93+
// /// Note, that `Trace` is the maximum level, because it provides the
94+
// /// maximum amount of detail in the emitted logs.
95+
// ///
96+
// /// If `Off` level is provided, then nothing is logged at all.
97+
// ///
98+
// /// [`log::max_level()`] is considered as the default level.
99+
// pub fn with_max_level(mut self, level: LevelFilter) -> Self {
100+
// self.log_level = Some(level);
101+
// self
102+
// }
97103

98104
/// Changes the Android logging system buffer to be used.
99105
///
@@ -114,15 +120,8 @@ impl Config {
114120
}
115121
}
116122

117-
pub(crate) fn is_loggable(&self, tag: &str, level: Level) -> bool {
118-
#[cfg(all(target_os = "android", feature = "android-api-30"))]
119-
use android_is_loggable as is_loggable;
120-
#[cfg(not(all(target_os = "android", feature = "android-api-30")))]
121-
use default_is_loggable as is_loggable;
122-
123-
is_loggable(tag, level, self.log_level)
124-
}
125-
123+
// TODO: Replace this with env_logger-like constructors:
124+
// https://docs.rs/env_logger/latest/env_logger/struct.Builder.html
126125
pub fn with_filter(mut self, filter: env_filter::Filter) -> Self {
127126
self.filter = Some(filter);
128127
self

src/lib.rs

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,8 @@
6666
#[cfg(target_os = "android")]
6767
extern crate android_log_sys as log_ffi;
6868

69-
use log::{Log, Metadata, Record};
69+
use config::is_loggable;
70+
use log::{LevelFilter, Log, Metadata, Record};
7071
use std::ffi::{CStr, CString};
7172
use std::fmt;
7273
use std::mem::MaybeUninit;
@@ -142,7 +143,7 @@ static ANDROID_LOGGER: OnceLock<AndroidLogger> = OnceLock::new();
142143

143144
/// Maximum length of a tag that does not require allocation.
144145
///
145-
/// Tags configured explicitly in [Config] will not cause an extra allocation. When the tag is
146+
/// Tags configured explicitly in [`Config`] will not cause an extra allocation. When the tag is
146147
/// derived from the module path, paths longer than this limit will trigger an allocation for each
147148
/// log statement.
148149
///
@@ -151,9 +152,12 @@ const LOGGING_TAG_MAX_LEN: usize = 127;
151152
const LOGGING_MSG_MAX_LEN: usize = 4000;
152153

153154
impl Log for AndroidLogger {
155+
/// # Warning
156+
/// This method relies on stateful data when `android-api-30` is enabled, including
157+
/// [`log::max_level()`] which we only initialize when [`init_once()`] is called and which can
158+
/// be changed by the user at any point via [`log::set_max_level()`].
154159
fn enabled(&self, metadata: &Metadata) -> bool {
155-
self.config()
156-
.is_loggable(metadata.target(), metadata.level())
160+
is_loggable(metadata.target(), metadata.level())
157161
}
158162

159163
fn log(&self, record: &Record) {
@@ -194,12 +198,10 @@ impl Log for AndroidLogger {
194198

195199
// If a custom tag is used, add the module path to the message.
196200
// Use PlatformLogWriter to output chunks if they exceed max size.
201+
use std::fmt::Write;
197202
let _ = match (&config.tag, &config.custom_format) {
198203
(_, Some(format)) => format(&mut writer, record),
199-
(Some(_), _) => fmt::write(
200-
&mut writer,
201-
format_args!("{}: {}", module_path, *record.args()),
202-
),
204+
(Some(_), _) => write!(&mut writer, "{}: {}", module_path, *record.args()),
203205
_ => fmt::write(&mut writer, *record.args()),
204206
};
205207

@@ -213,7 +215,7 @@ impl Log for AndroidLogger {
213215
/// Send a log record to Android logging backend.
214216
///
215217
/// This action does not require initialization. However, without initialization it
216-
/// will use the default filter, which allows all logs.
218+
/// will use the default filter, TODO Error default.
217219
pub fn log(record: &Record) {
218220
ANDROID_LOGGER
219221
.get_or_init(AndroidLogger::default)
@@ -228,12 +230,19 @@ pub fn log(record: &Record) {
228230
/// It is ok to call this at the activity creation, and it will be
229231
/// repeatedly called on every lifecycle restart (i.e. screen rotation).
230232
pub fn init_once(config: Config) {
231-
let log_level = config.log_level;
233+
// let log_level = config.log_level;
234+
let log_level = config
235+
.filter
236+
.as_ref()
237+
// Match the default Error level used by env_logger
238+
.map_or(LevelFilter::Error, |f| f.filter());
232239
let logger = ANDROID_LOGGER.get_or_init(|| AndroidLogger::new(config));
233240

234241
if let Err(err) = log::set_logger(logger) {
235-
log::debug!("android_logger: log::set_logger failed: {}", err);
236-
} else if let Some(level) = log_level {
237-
log::set_max_level(level);
242+
// TODO: Bubble up the error (try_init()) or panic (init()), as suggested
243+
// by the `log` crate and as implemented by `env_logger`.
244+
log::debug!("android_logger: log::set_logger failed: {err}");
245+
} else {
246+
log::set_max_level(log_level);
238247
}
239248
}

src/platform_log_writer.rs

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -192,12 +192,11 @@ pub mod tests {
192192
use crate::arrays::slice_assume_init_ref;
193193
use crate::platform_log_writer::PlatformLogWriter;
194194
use log::Level;
195-
use std::ffi::CStr;
196195
use std::fmt::Write;
197196

198197
#[test]
199198
fn platform_log_writer_init_values() {
200-
let tag = CStr::from_bytes_with_nul(b"tag\0").unwrap();
199+
let tag = c"tag";
201200

202201
let writer = PlatformLogWriter::new(None, Level::Warn, tag);
203202

@@ -318,10 +317,6 @@ pub mod tests {
318317
}
319318

320319
fn get_tag_writer() -> PlatformLogWriter<'static> {
321-
PlatformLogWriter::new(
322-
None,
323-
Level::Warn,
324-
CStr::from_bytes_with_nul(b"tag\0").unwrap(),
325-
)
320+
PlatformLogWriter::new(None, Level::Warn, c"tag")
326321
}
327322
}

src/tests.rs

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,47 @@
11
use super::*;
2-
use log::LevelFilter;
32
use std::sync::atomic::{AtomicBool, Ordering};
43

54
#[test]
65
fn check_config_values() {
76
// Filter is checked in config_filter_match below.
87
let config = Config::default()
9-
.with_max_level(LevelFilter::Trace)
108
.with_log_buffer(LogId::System)
119
.with_tag("my_app");
1210

13-
assert_eq!(config.log_level, Some(LevelFilter::Trace));
1411
assert_eq!(config.buf_id, Some(LogId::System));
1512
assert_eq!(config.tag, Some(CString::new("my_app").unwrap()));
1613
}
1714

1815
#[test]
1916
fn log_calls_formatter() {
2017
static FORMAT_FN_WAS_CALLED: AtomicBool = AtomicBool::new(false);
21-
let config = Config::default()
22-
.with_max_level(LevelFilter::Info)
23-
.format(|_, _| {
24-
FORMAT_FN_WAS_CALLED.store(true, Ordering::SeqCst);
25-
Ok(())
26-
});
18+
let config = Config::default().format(|_, _| {
19+
FORMAT_FN_WAS_CALLED.store(true, Ordering::SeqCst);
20+
Ok(())
21+
});
2722
let logger = AndroidLogger::new(config);
2823

2924
logger.log(&Record::builder().level(log::Level::Info).build());
3025

3126
assert!(FORMAT_FN_WAS_CALLED.load(Ordering::SeqCst));
3227
}
3328

34-
#[test]
35-
fn logger_enabled_threshold() {
36-
let logger = AndroidLogger::new(Config::default().with_max_level(LevelFilter::Info));
37-
38-
assert!(logger.enabled(&log::MetadataBuilder::new().level(log::Level::Warn).build()));
39-
assert!(logger.enabled(&log::MetadataBuilder::new().level(log::Level::Info).build()));
40-
assert!(!logger.enabled(&log::MetadataBuilder::new().level(log::Level::Debug).build()));
41-
}
29+
// TODO: How about deleting `fn enabled()` pretty much entirely? It's only useful for implementing
30+
// the new `android-api-30` `__android_log_is_loggable_len()` and without tests mocking that,
31+
// bogus to call. All filtering based on `log::max_level()` happens inside the `log::log!()`
32+
// macros already, and otherwise relies on the tests to call `init_once()` to ensure we call
33+
// `log::set_max_level()`.
34+
//
35+
// #[test]
36+
// fn logger_enabled_threshold() {
37+
// let logger = AndroidLogger::new(
38+
// Config::default().with_filter(FilterBuilder::new().filter_level(LevelFilter::Info).build()),
39+
// );
40+
41+
// assert!(logger.enabled(&log::MetadataBuilder::new().level(log::Level::Warn).build()));
42+
// assert!(logger.enabled(&log::MetadataBuilder::new().level(log::Level::Info).build()));
43+
// assert!(!logger.enabled(&log::MetadataBuilder::new().level(log::Level::Debug).build()));
44+
// }
4245

4346
// Test whether the filter gets called correctly. Not meant to be exhaustive for all filter
4447
// options, as these are handled directly by the filter itself.
@@ -47,7 +50,7 @@ fn config_filter_match() {
4750
let info_record = Record::builder().level(log::Level::Info).build();
4851
let debug_record = Record::builder().level(log::Level::Debug).build();
4952

50-
let info_all_filter = env_filter::Builder::new().parse("info").build();
53+
let info_all_filter = FilterBuilder::new().parse("info").build();
5154
let info_all_config = Config::default().with_filter(info_all_filter);
5255

5356
assert!(info_all_config.filter_matches(&info_record));

tests/config_log_level.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
1-
extern crate android_logger;
2-
extern crate log;
1+
use android_logger::FilterBuilder;
2+
use log::LevelFilter;
33

44
#[test]
55
fn config_log_level() {
66
android_logger::init_once(
7-
android_logger::Config::default().with_max_level(log::LevelFilter::Trace),
7+
android_logger::Config::default().with_filter(
8+
FilterBuilder::new()
9+
.filter_level(LevelFilter::Trace)
10+
.build(),
11+
),
812
);
913

1014
assert_eq!(log::max_level(), log::LevelFilter::Trace);

tests/default_init.rs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
1-
extern crate android_logger;
2-
extern crate log;
3-
41
#[test]
52
fn default_init() {
63
android_logger::init_once(Default::default());
74

8-
// android_logger has default log level "off"
9-
assert_eq!(log::max_level(), log::LevelFilter::Off);
5+
// android_logger has default log level of Error
6+
// TODO: env_logger/env_filter have this too, but I cannot find it in the source code
7+
assert_eq!(log::max_level(), log::LevelFilter::Error);
108
}

tests/multiple_init.rs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,24 @@
1-
extern crate android_logger;
2-
extern crate log;
1+
use android_logger::FilterBuilder;
2+
use log::LevelFilter;
33

44
#[test]
55
fn multiple_init() {
66
android_logger::init_once(
7-
android_logger::Config::default().with_max_level(log::LevelFilter::Trace),
7+
android_logger::Config::default().with_filter(
8+
FilterBuilder::new()
9+
.filter_level(LevelFilter::Trace)
10+
.build(),
11+
),
812
);
913

1014
// Second initialization should be silently ignored
1115
android_logger::init_once(
12-
android_logger::Config::default().with_max_level(log::LevelFilter::Error),
16+
android_logger::Config::default().with_filter(
17+
FilterBuilder::new()
18+
.filter_level(LevelFilter::Error)
19+
.build(),
20+
),
1321
);
1422

15-
assert_eq!(log::max_level(), log::LevelFilter::Trace);
23+
assert_eq!(log::max_level(), LevelFilter::Trace);
1624
}

0 commit comments

Comments
 (0)