Skip to content

Commit d829810

Browse files
authored
Merge pull request #84 from dextero/is-loggable
Use __android_log_is_loggable in AndroidLogger::enabled
2 parents 63b2e43 + e6931cf commit d829810

File tree

5 files changed

+110
-8
lines changed

5 files changed

+110
-8
lines changed

.github/workflows/ci.yml

+7-3
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ jobs:
2626
- armv7-linux-androideabi
2727
- aarch64-linux-android
2828
- i686-linux-android
29+
features:
30+
- ""
31+
- --no-default-features
32+
- --all-features
2933

3034
steps:
3135
- uses: actions/checkout@v3
@@ -37,9 +41,9 @@ jobs:
3741
components: rustfmt, clippy
3842

3943
- run: cargo fmt --check
40-
- run: cargo clippy --target=${{ matrix.target }} -- -Dwarnings
41-
- run: cargo build --target=${{ matrix.target }}
42-
- run: cargo doc --target=${{ matrix.target }}
44+
- run: cargo clippy --target=${{ matrix.target }} ${{ matrix.features }} -- -Dwarnings
45+
- run: cargo build --target=${{ matrix.target }} ${{ matrix.features }}
46+
- run: cargo doc --target=${{ matrix.target }} ${{ matrix.features }}
4347
env:
4448
RUSTDOCFLAGS: -Dwarnings
4549
# Temporary test non-target only.

Cargo.toml

+2-1
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,13 @@ targets = [
2525
[features]
2626
default = ["regex"]
2727
regex = ["env_filter/regex"]
28+
android-api-30 = []
2829

2930
[dependencies.log]
3031
version = "0.4"
3132

3233
[dependencies.android_log-sys]
33-
version = "0.3"
34+
version = "0.3.2"
3435

3536
[dependencies.env_filter]
3637
version = "0.1"

README.md

+42
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,48 @@ Therefore this library will only log a warning for subsequent `init_once` calls.
6565
This library ensures that logged messages do not overflow Android log message limits
6666
by efficiently splitting messages into chunks.
6767

68+
## Consistent log filtering in mixed Rust/C/C++ apps
69+
70+
Android's C logging API determines the effective log level based on [a
71+
combination](https://cs.android.com/android/platform/superproject/main/+/main:system/logging/liblog/properties.cpp;l=243;drc=b74a506c1b69f5b295a8cdfd7e2da3b16db15934)
72+
of a process-wide global variable, [system-wide
73+
properties](https://cs.android.com/android/platform/superproject/main/+/main:system/logging/logd/README.property;l=45;drc=99c545d3098018a544cb292e1501daca694bee0f),
74+
and call-specific default. `log` + `android_logger` crates add another layer of
75+
log filtering on top of that, independent from the C API.
76+
77+
```
78+
.-----.
79+
| app |
80+
'-----' Rust
81+
C/C++ | '--------------.
82+
| v
83+
| .-----------. filter by log::STATIC_MAX_LEVEL +
84+
| | log crate | - log::MAX_LOG_LEVEL_FILTER,
85+
| '-----------' overrideable via log::set_max_level
86+
| |
87+
| v
88+
| .----------------------.
89+
| | android_logger crate | - filter by Config::max_level
90+
| '----------------------'
91+
| |
92+
| .------------'
93+
v v
94+
.--------.
95+
| liblog | - filter by global state or system-wide properties
96+
'--------'
97+
```
98+
99+
`liblog` APIs introduced in Android API 30 let `android_logger` delegate log
100+
filtering decision to `liblog`, making the log level consistent across C, C++
101+
and Rust calls.
102+
103+
If you build `android_logger` with `android-api-30` feature enabled, the logger
104+
will consider the process-wide global state (set via
105+
[`__android_log_set_minimum_priority`](https://cs.android.com/android/platform/superproject/main/+/main:prebuilts/runtime/mainline/runtime/sdk/common_os/include/system/logging/liblog/include/android/log.h;l=364;drc=4cf460634134d51dba174f8af60dffb10f703f51))
106+
and Android system properties when deciding if a message should be logged or
107+
not. In this case, the effective log level is the _least verbose_ of the levels
108+
set between those and Rust log facilities.
109+
68110
## License
69111

70112
Licensed under either of

src/config.rs

+57-3
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,56 @@ impl fmt::Debug for Config {
3131
}
3232
}
3333

34+
#[cfg(all(target_os = "android", feature = "android-api-30"))]
35+
fn android_log_priority_from_level(level: Level) -> android_log_sys::LogPriority {
36+
match level {
37+
Level::Warn => android_log_sys::LogPriority::WARN,
38+
Level::Info => android_log_sys::LogPriority::INFO,
39+
Level::Debug => android_log_sys::LogPriority::DEBUG,
40+
Level::Error => android_log_sys::LogPriority::ERROR,
41+
Level::Trace => android_log_sys::LogPriority::VERBOSE,
42+
}
43+
}
44+
45+
/// Asks Android liblog if a message with given `tag` and `priority` should be logged, using
46+
/// `default_prio` as the level filter in case no system- or process-wide overrides are set.
47+
#[cfg(all(target_os = "android", feature = "android-api-30"))]
48+
fn android_is_loggable_len(
49+
prio: log_ffi::LogPriority,
50+
tag: &str,
51+
default_prio: log_ffi::LogPriority,
52+
) -> bool {
53+
// SAFETY: tag points to a valid string tag.len() bytes long.
54+
unsafe {
55+
log_ffi::__android_log_is_loggable_len(
56+
prio as log_ffi::c_int,
57+
tag.as_ptr() as *const log_ffi::c_char,
58+
tag.len() as log_ffi::c_size_t,
59+
default_prio as log_ffi::c_int,
60+
) != 0
61+
}
62+
}
63+
64+
#[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)
67+
}
68+
69+
#[cfg(all(target_os = "android", feature = "android-api-30"))]
70+
fn android_is_loggable(tag: &str, record_level: Level, config_level: Option<LevelFilter>) -> bool {
71+
let prio = android_log_priority_from_level(record_level);
72+
// 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,
80+
};
81+
android_is_loggable_len(prio, tag, default_prio)
82+
}
83+
3484
impl Config {
3585
/// Changes the maximum log level.
3686
///
@@ -64,9 +114,13 @@ impl Config {
64114
}
65115
}
66116

67-
pub(crate) fn is_loggable(&self, level: Level) -> bool {
68-
// todo: consider __android_log_is_loggable.
69-
level <= self.log_level.unwrap_or_else(log::max_level)
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)
70124
}
71125

72126
pub fn with_filter(mut self, filter: env_filter::Filter) -> Self {

src/lib.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,8 @@ const LOGGING_MSG_MAX_LEN: usize = 4000;
146146

147147
impl Log for AndroidLogger {
148148
fn enabled(&self, metadata: &Metadata) -> bool {
149-
self.config().is_loggable(metadata.level())
149+
self.config()
150+
.is_loggable(metadata.target(), metadata.level())
150151
}
151152

152153
fn log(&self, record: &Record) {

0 commit comments

Comments
 (0)