# How to Implement File Logging with TUI Display Using TuiLoggerFile

> Learn how to implement file logging with TUI display using TuiLoggerFile. Configure file dumps and register logs for seamless disk writes and TUI display.

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

---

**Use `TuiLoggerFile` to configure a file dump, then register it with `set_log_file()` so the background thread writes formatted log lines to disk while simultaneously displaying them in the TUI widget.**

The `tui-logger` crate allows Rust applications to capture log events in a circular buffer for display inside a terminal user interface (TUI) widget. By implementing `TuiLoggerFile`, you can persist those same log records to a file on disk without blocking the main thread. This guide explains the architecture and implementation based on the `gin66/tui-logger` source code.

## What is TuiLoggerFile?

`TuiLoggerFile` is a configuration struct defined in [`src/file.rs`](https://github.com/gin66/tui-logger/blob/main/src/file.rs) that manages the on-disk persistence of log records. It holds the `File` handle and formatting toggles that control how each log line appears on disk, including timestamp format, field separators, and whether to include the target name, source file, or line number.

When you register a `TuiLoggerFile` instance via `set_log_file()` (located in [`src/logger/api.rs`](https://github.com/gin66/tui-logger/blob/main/src/logger/api.rs)), the global logger stores it in `TUI_LOGGER.inner.dump`. From that point forward, every log entry that is moved from the hot buffer to the main buffer by the background thread is also formatted and written to the configured file.

## How File Logging Works Under the Hood

The crate uses a dual-buffer architecture with a background mover thread to ensure that file I/O never blocks the UI rendering or application logic.

1. **Hot Buffer**: Log events emitted via `info!`, `warn!`, or other macros first land in a fast, lock-free hot buffer.
2. **Background Thread**: Started automatically by `init_logger()`, this thread calls `move_events()` every 10 milliseconds or whenever the hot buffer reaches 50% capacity.
3. **File Dump**: Inside [`src/logger/inner.rs`](https://github.com/gin66/tui-logger/blob/main/src/logger/inner.rs), the `move_events()` function pulls events from the hot buffer, formats them according to the `TuiLoggerFile` options, and writes them to disk using `writeln!`.
4. **TUI Display**: The same events are simultaneously stored in the main buffer for the widget to render.

This design ensures that expensive filesystem operations happen on a separate thread while the TUI remains responsive.

## Step-by-Step Implementation

Below is a complete, runnable example that initializes the logger, configures a `TuiLoggerFile`, and emits logs that appear both in the TUI widget and in `app.log`.

```rust
use log::{info, warn, LevelFilter};
use tui_logger::{init_logger, set_default_level, set_log_file, TuiLoggerFile};

fn main() {
    // 1. Initialize the logger with the maximum level you want to capture.
    init_logger(LevelFilter::Trace).expect("Failed to initialise logger");

    // 2. Configure the file logger with custom formatting.
    let file_logger = TuiLoggerFile::new("app.log")
        .output_timestamp(Some("[%Y:%m:%d %H:%M:%S]".to_string()))
        .output_separator('|')
        .output_level(Some(tui_logger::TuiLoggerLevelOutput::Long))
        .output_target(true)
        .output_file(true)
        .output_line(true);

    // 3. Register the file logger with the global instance.
    set_log_file(file_logger);

    // 4. (Optional) Set a default level for targets without explicit filters.
    set_default_level(LevelFilter::Trace);

    // 5. Emit logs that appear in both the TUI and the file.
    info!("Application started");
    warn!(target: "network", "Connection latency high");
}

```

The `init_logger()` function spawns the background thread that handles the actual writing. You do not need to manage file flushing manually; the thread ensures data is written periodically or when the buffer threshold is met.

## Customizing the File Output

`TuiLoggerFile` provides a fluent builder API defined in [`src/file.rs`](https://github.com/gin66/tui-logger/blob/main/src/file.rs) to control the output format. Each method returns `Self`, allowing chained configuration:

- **`output_timestamp(Some(fmt))`**: Sets the timestamp format using `chrono` syntax. Pass `None` to disable timestamps entirely.
- **`output_separator(char)`**: Defines the character inserted between fields (default is `:`).
- **`output_level(Some(style))`**: Choose `TuiLoggerLevelOutput::Long` for full level names like `ERROR`, or `Abbreviated` for single-letter codes. Use `None` to omit levels.
- **`output_target(bool)`**: Include or exclude the log target (module path).
- **`output_file(bool)`**: Include or exclude the source filename.
- **`output_line(bool)`**: Include or exclude the source line number.

For example, to create a compact log format without timestamps or file information:

```rust
let compact = TuiLoggerFile::new("compact.log")
    .output_timestamp(None)
    .output_file(false)
    .output_line(false)
    .output_separator(' ');

```

## Running the Demo

The repository includes a working demonstration in [`examples/demo.rs`](https://github.com/gin66/tui-logger/blob/main/examples/demo.rs) that shows file logging alongside the TUI widget. Run it with your preferred backend:

```bash
cargo run --example demo --features crossterm

# or

cargo run --example demo --features termion

```

This demo illustrates how the same log events are processed through `move_events()` and written to disk while being rendered in the terminal interface.

## Summary

- **TuiLoggerFile** in [`src/file.rs`](https://github.com/gin66/tui-logger/blob/main/src/file.rs) configures file output via a builder pattern with options for timestamps, separators, and metadata fields.
- **Registration** happens through `set_log_file()` in [`src/logger/api.rs`](https://github.com/gin66/tui-logger/blob/main/src/logger/api.rs), which stores the configuration in the global logger.
- **Processing** occurs in `move_events()` inside [`src/logger/inner.rs`](https://github.com/gin66/tui-logger/blob/main/src/logger/inner.rs), where a background thread formats and writes events to the file handle every 10ms or when the buffer is half-full.
- The same log entries are simultaneously available to the TUI widget and the file, ensuring no data loss when the application crashes.

## Frequently Asked Questions

### How do I disable file logging after it has been enabled?

Once `set_log_file()` has been called, the global logger holds an `Option<TuiLoggerFile>`. To disable file logging at runtime, call `set_log_file()` again with a `TuiLoggerFile` that has been configured to use a different path, or reinitialize the logger if the application architecture permits. There is no explicit "unset" API, but replacing the configuration effectively stops writes to the previous file (the file handle is dropped).

### Does file logging impact TUI rendering performance?

No. All file I/O is performed by the background mover thread in `move_events()`, not the main thread. The hot buffer acts as a lock-free channel between the application and the background thread, ensuring that `log` macro calls return immediately. The 10ms interval and 50% buffer threshold provide a balance between latency and throughput.

### What file permissions are required for TuiLoggerFile?

`TuiLoggerFile::new()` attempts to create or open the file with append permissions. The application must have write access to the directory and the file path provided. If the file cannot be opened, the logger will panic during the `set_log_file()` call or when the background thread first attempts to write, depending on when the file system check occurs.

### Can I log to multiple files simultaneously based on log level or target?

The current implementation in `gin66/tui-logger` supports only a single file dump via `TuiLoggerFile`. To split logs across multiple files (e.g., separate files for `ERROR` vs `INFO`), you would need to implement a custom filtering proxy or use a more complex logging framework like `tracing` with `tracing-appender`, as `tui-logger` is designed for a single shared buffer with optional single-file persistence.