Skip to content

Commit 18c0fb6

Browse files
committed
feat(logging): add extented log filtering
The new environment variable `HERMIT_LOG_EXTENDED_FILTER` allows log filters of different levels to be applied on a per module basis. It also allows filtering by regex.
1 parent 1946d9f commit 18c0fb6

5 files changed

Lines changed: 106 additions & 9 deletions

File tree

Cargo.lock

Lines changed: 48 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -332,7 +332,7 @@ heapless = "0.9"
332332
hermit-entry = { version = "0.10.9", features = ["kernel"] }
333333
hermit-sync = "0.1"
334334
lock_api = "0.4"
335-
log = { version = "0.4", default-features = false }
335+
log = { version = "0.4", default-features = false, features = ["kv"] }
336336
mem-barrier = { version = "0.1.0", optional = true, features = ["nightly"] }
337337
num_enum = { version = "0.7", default-features = false }
338338
pci-ids = { version = "0.2", optional = true }
@@ -391,6 +391,11 @@ trapframe = "0.10"
391391
memory_addresses = { version = "0.3", default-features = false, features = [
392392
"riscv64",
393393
] }
394+
env_filter = { version = "1.0.0", default-features = false }
395+
396+
# regex breaks linking for riscv64 on debug builds
397+
[target.'cfg(not(target_arch = "riscv64"))'.dependencies]
398+
env_filter = { version = "1.0.0", default-features = false, features = ["regex"] }
394399

395400
[dev-dependencies]
396401
float-cmp = "0.10"
@@ -412,6 +417,7 @@ exclude = [
412417

413418
[patch.crates-io]
414419
safe-mmio = { git = "https://github.com/hermit-os/safe-mmio", branch = "be" }
420+
env_filter = { git = "https://github.com/cagatay-y/env_logger", branch = "feat-no_std-alloc" }
415421

416422
[profile.profiling]
417423
inherits = "release"

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,20 @@ If the variable is not set, or the name doesn't match, then `LevelFilter::Info`
4646
$ HERMIT_LOG_LEVEL_FILTER=Debug cargo xtask build --arch x86_64
4747
```
4848

49+
Additionally, logging level can be adjusted by module and regex using the `HERMIT_LOG_EXTENDED_FILTER` environment
50+
variable. The format is `[target][=level][,...]`. If the target is omitted, the level is set as the global level. If the
51+
level is omitted, logs of all levels are printed for the target. A regular expression that will in be effect for all
52+
modules can be provided at the end after a `/`. If a new global level is not provided, `HERMIT_LOG_LEVEL_FILTER` (or its
53+
default) will be the global level.
54+
55+
```sh
56+
HERMIT_LOG_LEVEL_FILTER=warn HERMIT_LOG_EXTENDED_FILTER='smoltcp,virtio=info/feature|send' cargo xtask build --arch x86_64
57+
```
58+
59+
Note that `HERMIT_LOG_EXTENDED_FILTER` is activated relatively late in the boot process due to its dynamic allocation
60+
requirement. Early boot logging can only be influenced with `HERMIT_LOG_LEVEL_FILTER`.
61+
62+
4963
## Credits
5064

5165
This kernel is derived from following tutorials and software distributions:

src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,9 @@ fn boot_processor_main() -> ! {
273273
info!("tls_info = {:#x?}", env::boot_info().load_info.tls_info);
274274
arch::boot_processor_init();
275275

276+
// We can't initialize the module filter during logger initialization as the allocations fail at that point.
277+
logging::init_module_filter();
278+
276279
#[cfg(not(target_arch = "riscv64"))]
277280
scheduler::add_current_core();
278281
interrupts::enable();

src/logging.rs

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use core::sync::atomic::{AtomicBool, Ordering};
33
use core::time::Duration;
44

55
use anstyle::AnsiColor;
6+
use hermit_sync::OnceCell;
67
use log::{Level, LevelFilter, Metadata, Record};
78

89
pub static KERNEL_LOGGER: KernelLogger = KernelLogger::new();
@@ -13,12 +14,14 @@ const TIME_SUBSEC_WIDTH: usize = 6;
1314
/// Data structure to filter kernel messages
1415
pub struct KernelLogger {
1516
time: AtomicBool,
17+
filter: OnceCell<env_filter::Filter>,
1618
}
1719

1820
impl KernelLogger {
1921
pub const fn new() -> Self {
2022
Self {
2123
time: AtomicBool::new(false),
24+
filter: OnceCell::new(),
2225
}
2326
}
2427

@@ -45,6 +48,23 @@ impl log::Log for KernelLogger {
4548
return;
4649
}
4750

51+
let target = record.target();
52+
let (crate_, modules) = target.split_once("::").unwrap_or((target, ""));
53+
let (_modules, module) = modules.rsplit_once("::").unwrap_or(("", modules));
54+
let target = if !module.is_empty() && crate_ == "hermit" {
55+
module
56+
} else {
57+
crate_
58+
};
59+
60+
if self.filter.get().is_some_and(|filter| {
61+
// We want the target that we match against to match the target we display.
62+
let record = record.to_builder().target(target).build();
63+
!filter.matches(&record)
64+
}) {
65+
return;
66+
}
67+
4868
let format_time = if self.time() {
4969
let time = Duration::from_micros(crate::processor::get_timer_ticks());
5070
format_args!(
@@ -58,14 +78,6 @@ impl log::Log for KernelLogger {
5878
let core_id = crate::arch::core_local::core_id();
5979
let level = ColorLevel(record.level());
6080

61-
let target = record.target();
62-
let (crate_, modules) = target.split_once("::").unwrap_or((target, ""));
63-
let (_modules, module) = modules.rsplit_once("::").unwrap_or(("", modules));
64-
let target = if !module.is_empty() && crate_ == "hermit" {
65-
module
66-
} else {
67-
crate_
68-
};
6981
let format_target = format_args!(" {target:<10}");
7082

7183
let args = record.args();
@@ -118,6 +130,20 @@ pub unsafe fn init() {
118130
log::set_max_level(max_level);
119131
}
120132

133+
pub(crate) fn init_module_filter() {
134+
// We use a separate environment variable in order to avoid unnecessary cost when the basic filter is sufficient.
135+
let Some(log_level) = option_env!("HERMIT_LOG_EXTENDED_FILTER") else {
136+
return;
137+
};
138+
let filter = env_filter::Builder::new()
139+
// What was set via `HERMIT_LOG_LEVEL_FILTER` or the default. It may get overwritten by the parsed filter if it has a global level.
140+
.filter_level(log::max_level())
141+
.parse(log_level)
142+
.build();
143+
log::set_max_level(filter.filter());
144+
KERNEL_LOGGER.filter.set(filter).unwrap();
145+
}
146+
121147
#[cfg_attr(target_arch = "riscv64", allow(unused))]
122148
macro_rules! infoheader {
123149
// This should work on paper, but it's currently not supported :(

0 commit comments

Comments
 (0)