How to Programmatically Configure Different Log Levels for Different Targets in tui-logger
Use set_level_for_target to update the global logger state and filtering behavior, or call TuiWidgetState::set_level_for_target to adjust display levels in the UI without affecting the underlying log capture.
The tui-logger crate enables runtime adjustment of per-target verbosity through a dual-layer configuration system. To programmatically configure different log levels for different targets, you interact with the LevelConfig table—a HashMap<String, LevelFilter>—that exists in both the global logger state and widget-local state. This guide examines the implementation in gin66/tui-logger to demonstrate how to manipulate these configurations using the public APIs exposed in src/logger/api.rs and src/widget/inner.rs.
The LevelConfig Architecture
At the core of per-target filtering lies the LevelConfig structure, which wraps a HashMap<String, LevelFilter>. This mapping associates Rust module paths (targets) with their respective verbosity thresholds.
The crate maintains two independent copies of this table:
- Global logger state – Stored in
TUI_LOGGER.inner.lock().targets, this instance controls which log events are actually captured and stored by the logger. - Widget-local state – Held in
TuiWidgetInnerState::configand exposed throughTuiWidgetState, this copy influences how events are displayed in the terminal interface.
When the widget renders, it merges the global configuration into its local copy, allowing the UI to reflect the actual logger state while permitting display-only overrides.
Global vs. Widget-Local Configuration
Understanding the distinction between these two configuration layers is critical for effective log management.
Global Logger Configuration
Changes to the global LevelConfig affect both event capture and UI display. When you modify the global state via set_level_for_target, the logger updates its internal hot-select hash table (hot_select.hashtable) to ensure subsequent log! calls are filtered efficiently according to the new level. This operation also increments a generation counter in LevelConfig::set (defined in src/config/level_config.rs), signaling widgets to refresh their cached configuration on the next render.
Widget-Only Overrides
Widget-local configuration changes impact only the visual representation. This enables scenarios where you hide noisy targets from the display (setting them to LevelFilter::Off) while the logger continues recording them in the global buffer. The UI rendering logic in src/widget/target.rs merges global levels with local overrides by calling targets.merge(hot_targets), prioritizing the widget's private configuration for display purposes.
API Implementation and Usage
The crate exposes two primary methods for manipulating these configurations, each targeting a specific layer of the system.
set_level_for_target (Global)
Defined in [src/logger/api.rs](https://github.com/gin66/tui-logger/blob/master/src/logger/api.rs#L24-L31), this function writes the level into TUI_LOGGER.inner.targets and synchronizes the hot-select hash table used by the logger's fast path:
use log::LevelFilter;
use tui_logger::set_level_for_target;
// Set the global level for a specific module path
set_level_for_target("my::crate::network", LevelFilter::Debug);
This call immediately affects what events are captured. The implementation in LevelConfig::set inserts the entry and bumps the generation counter, ensuring that any widget holding a copy of the configuration can detect the change.
TuiWidgetState::set_level_for_target (Local)
Located in [src/widget/inner.rs](https://github.com/gin66/tui-logger/blob/master/src/widget/inner.rs#L33-L36), this method updates only the widget's private LevelConfig:
use tui_logger::TuiWidgetState;
use log::LevelFilter;
let widget_state = TuiWidgetState::new()
.set_level_for_target("my::crate::network", LevelFilter::Off);
Pass this state to your widget constructor to apply display-only filters. The target remains logged globally but disappears from the UI view.
Practical Configuration Examples
Configuring Multiple Targets Globally
Iterate over your target-level pairs to establish comprehensive filtering rules that affect both storage and display:
use log::LevelFilter;
use tui_logger::set_level_for_target;
fn configure_logging() {
let targets = [
("net::client", LevelFilter::Debug),
("db::queries", LevelFilter::Error),
("ui::render", LevelFilter::Trace),
];
for (target, level) in targets {
set_level_for_target(target, level);
}
}
Initializing with Default Levels
When creating a widget, you can set a baseline display level and override specific targets:
use tui_logger::TuiWidgetState;
use log::LevelFilter;
let state = TuiWidgetState::new()
.set_default_display_level(LevelFilter::Info)
.set_level_for_target("audio::decoder", LevelFilter::Off);
This configuration shows only Info and higher for most targets while completely hiding the audio decoder output from the terminal view.
Complete Setup Example
use log::LevelFilter;
use tui_logger::{set_level_for_target, TuiWidgetState};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Initialize with maximum possible level
tui_logger::init_logger(LevelFilter::Trace)?;
// Configure global filtering: record Info and above for this module
set_level_for_target("my::crate::module", LevelFilter::Info);
// Configure widget to display Warn and above for the same module
// (demonstrating the disconnect between global and UI levels)
let widget_state = TuiWidgetState::new()
.set_level_for_target("my::crate::module", LevelFilter::Warn);
// Use widget_state when building your TuiLoggerTargetWidget...
Ok(())
}
Summary
- LevelConfig stores per-target mappings as a
HashMap<String, LevelFilter>in both global and widget-local contexts. - Global changes via
set_level_for_targetinsrc/logger/api.rsaffect event capture and update thehot_select.hashtablefor fast filtering. - Widget changes via
TuiWidgetState::set_level_for_targetinsrc/widget/inner.rsprovide display-only overrides that hide targets without stopping logging. - The generation counter in
LevelConfig::setenables widgets to detect configuration changes and merge updates on the next render cycle. - Use global APIs to control storage and memory usage; use widget APIs to customize the visual noise level in the terminal.
Frequently Asked Questions
What is the difference between global and widget-local log levels?
Global log levels, set via set_level_for_target, determine which events the logger actually captures and stores in the ring buffer located in TUI_LOGGER.inner.targets. Widget-local levels, set via TuiWidgetState::set_level_for_target, only affect which events appear in the terminal UI. A target can be logged at Trace level globally but displayed at Error level (or hidden entirely) in a specific widget instance.
How do I hide a target in the UI without affecting the actual logging?
Use the widget-state API with LevelFilter::Off. Call TuiWidgetState::new().set_level_for_target("noisy::module", LevelFilter::Off) and pass that state to your widget constructor. The module continues logging to the global buffer (subject to its global level), but the widget filters it out during rendering.
Where does tui-logger store the per-target configuration internally?
The configuration resides in the LevelConfig struct defined in src/config/level_config.rs. The global instance lives in TUI_LOGGER.inner.targets, protected by a mutex. Each TuiWidgetState maintains its own LevelConfig in the config field of TuiWidgetInnerState, defined in src/widget/inner.rs.
Does changing a log level require reinitializing the logger?
No. Both set_level_for_target and TuiWidgetState::set_level_for_target apply changes immediately at runtime. The global API updates the hot_select.hashtable directly in the logger's fast path, ensuring that subsequent log calls respect the new level without requiring a restart or reinitialization of the tui_logger system.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →