# How Target-Based Log Filtering Works in tui-logger: A Complete Configuration Guide

> Master target-based log filtering in tui-logger. Learn how its three-layered system provides programmatic and interactive control over log levels for the buffer and UI. A complete configuration guide.

- Repository: [gin66/tui-logger](https://github.com/gin66/tui-logger)
- Tags: how-to-guide
- Published: 2026-03-02

---

**tui-logger uses a three-layered filtering system—capture, recording, and display—that operates independently on the target string supplied to log macros, allowing both programmatic and interactive control over which log levels appear in the buffer and the UI.**

The `tui-logger` crate provides a specialized logging frontend for terminal user interfaces (TUIs) built with Rust. Unlike standard loggers, it implements **target-based log filtering** across three distinct axes: a hot-path capture filter that decides whether to copy records from the hot buffer, a recording filter that governs persistent storage, and a display filter that controls the UI visibility. This architecture enables sub-millisecond log filtering while still permitting runtime adjustments through the TUI itself.

## The Three Axes of Target-Based Log Filtering

tui-logger organizes log filtering into three independent layers keyed by the *target* string (e.g., `"my_crate::module::submodule"`):

- **Capture filter** – Determines if a record moves from the hot circular buffer into the widget’s main buffer. Stored in `HotSelect::hashtable` (`HashMap<u64, LevelFilter>`) in [`src/logger/inner.rs`](https://github.com/gin66/tui-logger/blob/main/src/logger/inner.rs).
- **Recording filter** – Defines the maximum level actually recorded for a target when writing to files or the main buffer. Stored in `TuiLoggerInner::targets` (`LevelConfig`) in [`src/logger/inner.rs`](https://github.com/gin66/tui-logger/blob/main/src/logger/inner.rs).
- **Display filter** – Controls which levels appear in the *target selector* widget. Stored in `TuiWidgetInnerState::config` in [`src/widget/inner.rs`](https://github.com/gin66/tui-logger/blob/main/src/widget/inner.rs).

These layers work together to ensure that high-frequency logging does not degrade performance while still allowing granular control over output visibility.

## How Capture Filtering Works on the Hot Path

When the `log!` macro fires, `tui_logger::raw_log` pushes an `ExtLogRecord` into the **hot buffer** (see [`src/logger/inner.rs`](https://github.com/gin66/tui-logger/blob/main/src/logger/inner.rs) lines 94-98). Before storage, the `Log::enabled` method performs a fast hash lookup:

```rust
fn enabled(&self, metadata: &Metadata) -> bool {
    let h = fast_str_hash(metadata.target());
    let hs = self.hot_select.lock();
    if let Some(&levelfilter) = hs.hashtable.get(&h) {
        metadata.level() <= levelfilter
    } else if let Some(envfilter) = hs.filter.as_ref() {
        envfilter.enabled(metadata)
    } else {
        metadata.level() <= hs.default
    }
}

```

The logic follows a strict precedence:
1. If the target exists in `hashtable`, compare the record’s level against the stored `LevelFilter`.
2. If absent, consult the optional **env-filter** (configured via `env_filter::Filter`).
3. Fall back to the global default level (`hs.default`).

The first time a new target passes the env-filter check, the `move_events` function (lines 55-86 in [`src/logger/inner.rs`](https://github.com/gin66/tui-logger/blob/main/src/logger/inner.rs)) extracts the most permissive valid level and writes it to both `hashtable` (for fast hot-path checks) and the persistent `targets` table.

## Recording Levels and the Configuration API

To change filtering behavior programmatically, use the public API defined in [`src/logger/api.rs`](https://github.com/gin66/tui-logger/blob/main/src/logger/api.rs). The function `set_level_for_target` synchronizes both the hot-path and persistent storage:

```rust
pub fn set_level_for_target(target: &str, levelfilter: LevelFilter) {
    let h = fast_str_hash(target);
    TUI_LOGGER.inner.lock().targets.set(target, levelfilter);
    let mut hs = TUI_LOGGER.hot_select.lock();
    hs.hashtable.insert(h, levelfilter);
}

```

Key configuration functions include:

- **`tui_logger::init_logger(max_level)`** – Initializes the background thread that moves events from the hot buffer to the main buffer. Call once at startup.
- **`tui_logger::set_default_level(LevelFilter)`** – Sets the global default for unknown targets, stored in `HotSelect::default`.
- **`tui_logger::set_level_for_target(target, level)`** – Overrides the level for a specific target, updating both `inner.targets` and `hot_select.hashtable`.
- **`tui_logger::set_env_filter_from_string(filter)`** – Parses an env-filter string (e.g., `"info,my_module=debug"`) and populates the filter structures.
- **`tui_logger::set_env_filter_from_env(Some("MY_LOG"))`** – Reads an environment variable (defaults to `RUST_LOG`) to build the env-filter.

## Display Filtering in the TUI Widget

The target selector widget ([`src/widget/target.rs`](https://github.com/gin66/tui-logger/blob/main/src/widget/target.rs)) renders each target with symbols **E W I D T** (error, warn, info, debug, trace). During rendering (lines 61-84), it compares two values:

1. `hot_level_filter` – The *recording* filter from `hot_targets`.
2. `level_filter` – The *display* filter from the widget’s private `state.config`.

A symbol appears in **show** style only when both filters permit that level; otherwise, it renders in **hide** style.

Interactive controls function as follows:
- **Left / Right arrow keys** – Adjust the *display* filter via `state.config.set` (affects only UI visibility).
- **+ / - keys** – Adjust the *recording* filter by calling `set_level_for_target` (affects what gets stored).

The `TargetWidget::render` method (lines 49-60) merges the widget’s private configuration with the global `targets` map, ensuring new targets appear in the selector even before they exist in the hot-path hash table.

## Configuration Examples

### Basic Per-Target Filtering

Initialize the logger and restrict a noisy module while keeping the rest at `Info`:

```rust
use log::{info, error, warn, LevelFilter};
use tui_logger::{self, set_level_for_target, set_default_level};

fn main() -> Result<(), tui_logger::TuiLoggerError> {
    // Initialize with global maximum level
    tui_logger::init_logger(LevelFilter::Trace)?;

    // Default for unknown targets
    set_default_level(LevelFilter::Info);

    // Tighten filter for a specific module
    set_level_for_target("my::noisy_module", LevelFilter::Warn);

    // This log is filtered out (Info < Warn)
    info!(target: "my::noisy_module", "this will be filtered out");

    // This log is captured (Warn >= Warn)
    warn!(target: "my::noisy_module", "visible warning");

    Ok(())
}

```

### Using Environment-Filter Syntax

Apply complex filters without recompiling:

```rust
use tui_logger::{init_logger, set_env_filter_from_string};
use log::LevelFilter;

fn main() -> Result<(), tui_logger::TuiLoggerError> {
    init_logger(LevelFilter::Trace)?;

    // Syntax: global level, then target-specific overrides
    set_env_filter_from_string("info,db=warn,net=off");

    // Filtered: level Info is below the db target's Warn threshold
    log::info!(target: "db", "database connected");

    // Shown: Warn meets the threshold
    log::warn!(target: "db", "slow query detected");

    // Filtered: net target is disabled (off)
    log::error!(target: "net", "network unreachable");

    Ok(())
}

```

### Controlling the Widget Display State

Configure the UI to hide debug messages by default while still recording them:

```rust
use tui_logger::{TuiLoggerWidget, TuiWidgetState, LevelFilter, TuiWidgetEvent};
use ratatui::widgets::Block;

let state = TuiWidgetState::new()
    .set_default_display_level(LevelFilter::Warn)
    .set_level_for_target("app::renderer", LevelFilter::Debug);

let widget = TuiLoggerWidget::default()
    .block(Block::default().title("Logs"))
    .state(&state);

// In your event loop, handle key inputs:
// state.transition(TuiWidgetEvent::RightKey); // Increases display level for selected target
// state.transition(TuiWidgetEvent::PlusKey);  // Increases recording level (calls set_level_for_target)

```

## Summary

- **tui-logger** implements target-based log filtering through three layers: capture (hot-path hash table), recording (persistent `LevelConfig`), and display (widget state).
- The `Log::enabled` implementation in [`src/logger/inner.rs`](https://github.com/gin66/tui-logger/blob/main/src/logger/inner.rs) uses a `HashMap<u64, LevelFilter>` keyed by `fast_str_hash(target)` to achieve O(1) lookup during log emission.
- Use **`set_level_for_target`** to programmatically adjust both capture and recording filters simultaneously.
- Use **`set_env_filter_from_string`** or **`set_env_filter_from_env`** to configure filters at runtime using standard env-filter syntax.
- The target selector widget maintains a separate display filter (`state.config`) that interacts with recording levels in [`src/widget/target.rs`](https://github.com/gin66/tui-logger/blob/main/src/widget/target.rs), allowing users to hide levels in the UI without losing data.

## Frequently Asked Questions

### How do I completely disable logging for a specific target in tui-logger?

Set the target’s level filter to `Off`. Call `tui_logger::set_level_for_target("target_name", log::LevelFilter::Off)`. This updates the `hashtable` in `HotSelect` (defined in [`src/logger/inner.rs`](https://github.com/gin66/tui-logger/blob/main/src/logger/inner.rs)) to reject all records from that target before they reach the main buffer.

### Why are my log messages appearing in the buffer but not in the TUI widget?

The widget’s **display filter** (stored in `TuiWidgetInnerState::config` in [`src/widget/inner.rs`](https://github.com/gin66/tui-logger/blob/main/src/widget/inner.rs)) likely excludes your message’s level. Use the Left/Right arrow keys in the target selector to adjust the display threshold, or call `state.set_level_for_target()` on the `TuiWidgetState` to change the UI visibility without affecting what gets recorded.

### What is the difference between `set_default_level` and `set_env_filter_from_string`?

`set_default_level` sets a static global fallback used when no per-target rule exists, stored in `HotSelect::default`. In contrast, `set_env_filter_from_string` parses a complex filter string (e.g., `"crate=debug,other=off"`) and installs an `env_filter::Filter` that dynamically evaluates targets; the first time a target passes this filter, its level is cached in the hot-path hash table for performance.

### How does tui-logger maintain performance with thousands of log events per second?

The crate separates concerns into a **hot buffer** and a **main buffer**. The hot-path check in `Log::enabled` (lines 71-80 of [`src/logger/inner.rs`](https://github.com/gin66/tui-logger/blob/main/src/logger/inner.rs)) uses a lock-protected hash map (`HotSelect::hashtable`) to avoid string comparisons during logging, achieving O(1) filtering via 64-bit hash keys generated by `fast_str_hash`.