How to Use TuiTracingSubscriberLayer to Collect Tracing Events in a TUI App

Register TuiTracingSubscriberLayer on a tracing_subscriber::registry after calling tui_logger::init_logger to forward tracing events into the crate's circular buffer for real-time terminal display.

The tui-logger crate provides a tracing-subscriber compatible layer that bridges the tracing ecosystem with its internal log-based renderer. By enabling the tracing-support feature and registering TuiTracingSubscriberLayer, any tracing::info!, tracing::debug!, or other instrumentation events automatically appear in your Ratatui widgets without additional handlers.

Enable the Tracing Support Feature

To access the integration, add the tracing-support feature to your Cargo.toml dependency:

[dependencies]
tui-logger = { version = "*", features = ["tracing-support"] }
tracing = "0.1"
tracing-subscriber = "0.3"

This feature flag exposes TuiTracingSubscriberLayer, which is re-exported from src/lib.rs for convenient access.

Initialize the Logger Before the Registry

The setup requires strict initialization order. You must call tui_logger::init_logger before any tracing events are emitted or the registry is initialized, as noted in the documentation at src/tracing_subscriber.rs#L74-L78.

use tracing_subscriber::prelude::*;
use tui_logger::TuiTracingSubscriberLayer;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Must initialize first to create the circular buffer and background thread
    tui_logger::init_logger(tui_logger::LevelFilter::Trace)?;
    
    // Then register the layer on the registry
    tracing_subscriber::registry()
        .with(TuiTracingSubscriberLayer)
        .init();
        
    tracing::info!("Application started");
    Ok(())
}

The init_logger function, located in src/logger/api.rs, spawns the background thread that moves events from the hot buffer into the circular buffer that feeds the UI.

How the Layer Bridges Events to the UI

TuiTracingSubscriberLayer implements tracing_subscriber::Layer and acts as a converter between the two logging systems. When a tracing::Event is emitted, the layer's on_event method (defined in src/tracing_subscriber.rs#L16-L48) performs three operations:

  1. Extracts metadata including the target, level, file, line, and module path.
  2. Visits structured fields using ToStringVisitor (src/tracing_subscriber.rs#L25-L64) to convert key-value pairs into a formatted string.
  3. Creates a log::Record and forwards it to the global TUI_LOGGER singleton via TUI_LOGGER.log(...) (src/tracing_subscriber.rs#L37-L46).

The TUI_LOGGER instance stores the record in a hot buffer, which the background thread drains into a lock-free circular buffer. The TuiLoggerWidget (implemented in src/widget/standard.rs) reads from this buffer during each render cycle to display the messages.

Complete Integration Example

The following example demonstrates the full setup including structured fields and custom targets:

use tracing_subscriber::prelude::*;
use tui_logger::TuiTracingSubscriberLayer;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // 1. Initialize the logger (creates TUI_LOGGER global)
    tui_logger::init_logger(tui_logger::LevelFilter::Debug)?;
    
    // 2. Register the tracing layer
    tracing_subscriber::registry()
        .with(TuiTracingSubscriberLayer)
        .init();
    
    // 3. Emit events that automatically appear in the TUI
    tracing::info!(target: "app_startup", "Server initializing");
    tracing::debug!(target: "network", bytes_sent = 1024, "Packet transmitted");
    
    // 4. Run your terminal interface with tui_logger widgets
    // tui_logger::demo::run()?;
    Ok(())
}

Key Source Files and Components

Understanding the repository structure helps debug integration issues:

  • src/tracing_subscriber.rs – Contains TuiTracingSubscriberLayer with the on_event handler that performs the tracing::Event to log::Record conversion.
  • src/lib.rs – Re-exports the layer at lines 17-22, making it available as tui_logger::TuiTracingSubscriberLayer.
  • src/logger/api.rs – Defines init_logger and the global TUI_LOGGER singleton that receives the bridged events.
  • src/widget/standard.rs – Implements the default widget that renders buffered log lines from the circular buffer.

Summary

  • Enable the feature: Add tracing-support to your tui-logger dependency to access the layer.
  • Initialize first: Always call tui_logger::init_logger before creating the tracing_subscriber registry to ensure the circular buffer exists.
  • Register the layer: Use .with(TuiTracingSubscriberLayer) on your registry to activate the bridge.
  • Emit normally: Standard tracing macros automatically populate the TUI widget through the internal log translation layer.

Frequently Asked Questions

What happens if I initialize the tracing registry before calling init_logger?

The application will panic or silently drop events. The TUI_LOGGER global instance must exist before TuiTracingSubscriberLayer attempts to forward log::Record objects to it, as the layer directly calls TUI_LOGGER.log() inside its on_event implementation.

Does TuiTracingSubscriberLayer support structured fields and spans?

Yes. The layer uses a ToStringVisitor to serialize structured fields from tracing events into the message string. While it captures span context via on_new_span, the primary output format is a log::Record compatible string suitable for the widget's display buffer.

Can I use TuiTracingSubscriberLayer alongside other tracing layers?

Yes. The layer is composable with other tracing_subscriber::Layer implementations. You can chain it with formatting layers or filtering layers using the registry's .with() method, allowing you to write to both the TUI widget and stdout simultaneously.

Do I need the log crate in my dependencies?

No. While TuiTracingSubscriberLayer creates log::Record structs internally to interface with tui-logger's buffer, the log crate is a dependency of tui-logger itself. You only need tracing and tracing-subscriber in your application code to emit events.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →