How to Use tui-logger with the log Crate and env_logger as Formatter
Yes, you can use tui-logger with the log crate while delegating formatting to env_logger by creating a Drain wrapper that forwards records from env_logger's formatter closure into tui-logger's circular buffers.
The tui-logger crate from gin66/tui-logger provides terminal user interface (TUI) widgets for viewing logs in Rust applications built with ratatui. While it can serve as a standalone logger implementation, you may want to combine it with env_logger to leverage its flexible formatting capabilities. This guide explains how to configure this integration so that env_logger controls output formatting while tui-logger handles UI storage and rendering.
Architecture Overview
When combining these crates, log records flow through a specific pipeline that separates formatting from UI storage:
log macros → env_logger (formatter) → TuiLogger::Drain → TUI_LOGGER (hot buffer → main buffer) → TuiLoggerWidget
- The
logcrate macros (info!,warn!, etc.) producelog::Recordinstances. env_loggerreceives these records and formats them according to your configuration.- Inside the formatter closure, you forward the record to
tui_logger::Drain::log, which pushes the event into the hot circular buffer defined insrc/logger/inner.rs. - A background thread spawned by
init_logger(located insrc/logger/api.rs) periodically moves events from the hot buffer to the main buffer. - The UI widgets (
TuiLoggerWidgetinsrc/widget/standard.rsorTuiLoggerSmartWidgetinsrc/widget/smart.rs) read from the main buffer to render log lines.
Because the log crate permits only one global logger, you must choose between installing tui-logger as the global logger or using env_logger as the global logger with tui-logger as a drain.
Step-by-Step Implementation
To use env_logger as the formatter while capturing output in tui-logger, follow this pattern demonstrated in examples/demo.rs.
Initialize the Background Thread
First, call tui_logger::init_logger to spawn the background worker thread that moves events between buffers. In this configuration, it does not register tui-logger as the global logger.
use log::LevelFilter;
use tui_logger::init_logger;
// Start the background mover thread (move_events loop in src/logger/api.rs)
init_logger(LevelFilter::Trace)?;
This initializes the hot and main circular buffers in src/logger/inner.rs and starts the thread that periodically flushes HotLog events into the main buffer for UI consumption.
Create the Drain Bridge
Instantiate a Drain struct, which acts as a lightweight wrapper exposing a log method to forward records into tui-logger's storage without going through the global logger interface.
use tui_logger::Drain;
let tui_drain = Drain::new();
The Drain implementation (lines 37–50 in src/logger/api.rs) simply forwards calls to TUI_LOGGER.raw_log, injecting records directly into the hot buffer.
Configure env_logger with Custom Formatting
Build env_logger with a custom formatter that performs your desired formatting and then passes the record to the drain. Finally, call init() to install env_logger as the global logger.
use std::io::Write;
env_logger::Builder::default()
.format(move |buf, record| {
// Apply env_logger formatting (timestamps, levels, etc.)
let ts = buf.timestamp();
writeln!(buf, "[{}] {} - {}", ts, record.level(), record.args())?;
// Forward the record to tui-logger for UI capture
tui_drain.log(record);
Ok(())
})
.filter_level(LevelFilter::Trace)
.init(); // Installs env_logger as the global logger
After this setup, calls to log::info!, log::warn!, and other macros will route through env_logger, appear in your formatted console output, and simultaneously populate the tui-logger buffers for display in your ratatui interface.
Render the Widget
Create and configure the logger widget to display the captured logs. You can use either the standard widget or the smart widget with target selection.
use tui_logger::TuiLoggerWidget;
use ratatui::widgets::Block;
let logger_widget = TuiLoggerWidget::default()
.block(Block::default().title("Application Logs"))
.state(&tui_logger::TuiWidgetState::new());
Insert this widget into your terminal layout and call terminal.draw() each frame to render the latest logs from the main buffer.
Alternative: Using tui-logger as the Global Logger
If you do not require env_logger's formatting features, you can install tui-logger directly as the global logger. This approach simplifies configuration but requires you to set formatting options via the widget's LogFormatter if you want to customize output appearance.
use log::{info, LevelFilter};
use tui_logger::{init_logger, set_default_level, TuiLoggerWidget};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Register tui-logger as the global logger and start the background thread
init_logger(LevelFilter::Trace)?;
set_default_level(LevelFilter::Trace);
let widget = TuiLoggerWidget::default()
.block(ratatui::widgets::Block::default().title("Logs"));
info!("Application started with tui-logger as the global logger");
Ok(())
}
In this mode, init_logger calls log::set_logger internally (as implemented in src/logger/inner.rs), making tui-logger the sole recipient of all log records.
Key Components and Source Locations
Understanding the source layout helps diagnose issues or extend functionality:
src/logger/inner.rs– ContainsTuiLoggerInner,HotLog, andHotSelectstructures implementing the lock‑free circular buffers and thelog::Logtrait for the global logger implementation.src/logger/api.rs– Providesinit_logger,set_hot_buffer_depth, and theDrainstruct (lines 37–50) that wraps calls toraw_log.src/widget/standard.rs– ImplementsTuiLoggerWidgetfor basic log display.src/widget/smart.rs– ImplementsTuiLoggerSmartWidgetwith target filtering and selection capabilities.examples/demo.rs– Demonstrates both the env_logger integration pattern and standalone usage.src/lib.rs– Public API entry point re‑exporting all types includingDrain,TuiLoggerWidget, andLogFormatter.
Summary
- Use
tui_logger::init_loggerto spawn the background thread that moves records from the hot buffer to the main buffer; when usingenv_logger, this does not register the global logger. - Create a
Draininstance fromsrc/logger/api.rsto bridge records fromenv_loggerintotui-logger’s storage system. - Configure
env_logger::Builderwith a custom format closure that callsdrain.log(record)to ensure every formatted event appears in both the console and the TUI widget. - Maintain standard macro usage (
info!,warn!, etc.) which dispatch to the globally installedenv_loggerwhile the drain captures copies for the UI. - Reference
src/logger/inner.rsfor buffer implementations andsrc/widget/standard.rsfor rendering logic when customizing log display behavior.
Frequently Asked Questions
Can I use tui-logger without installing it as the global logger?
Yes. Call tui_logger::init_logger to start the background event mover thread, then create a Drain instance and call drain.log(record) inside another logger's formatter (such as env_logger). This allows env_logger to remain the global logger while tui-logger captures records for the UI via the drain.
Where does tui-logger store incoming log records?
Records are first written to a hot circular buffer (HotLog in src/logger/inner.rs) to minimize latency on the logging thread. A background thread (spawned in src/logger/api.rs) periodically moves these events to the main buffer, from which TuiLoggerWidget and TuiLoggerSmartWidget read for rendering.
Does using env_logger as the formatter affect the standard log macros?
No. The log!, info!, warn!, and other macros continue to function exactly as documented in the log crate. They dispatch to whichever logger is globally installed (env_logger in this setup), which formats the record before your closure forwards it to the tui-logger drain.
Can I customize how logs appear inside the TUI widget?
Yes. When using the env_logger approach, formatting is determined by the closure passed to env_logger::Builder::format. If you use tui-logger as the standalone global logger instead, you can provide a custom LogFormatter implementation to the TuiLoggerWidget to control timestamp display, color coding, and field ordering within the UI.
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 →