Skip to content

feat: Fix error logs breaking TUI layout by adding in-TUI log panel #104

@inureyes

Description

@inureyes

Problem / Background

When ERROR or WARN level logs occur during TUI mode execution, the log messages are printed directly to the screen, breaking the ratatui alternate screen layout.

Current State Analysis

  1. TUI Mode: Runs on ratatui's alternate screen in src/ui/tui/mod.rs
  2. Logging Implementation: Uses tracing_subscriber::fmt() in src/utils/logging.rs to output directly to stdout
  3. Log Usage: error!(), warn!() macros used in terminal_guard.rs and many other files
  4. Scope: 59 files use tracing throughout the codebase

Steps to Reproduce

  1. Execute command in TUI mode (bssh -c cluster "command")
  2. Trigger an error situation such as connection error or timeout
  3. Error logs are printed directly over the TUI screen, breaking the layout

Proposed Solution

Step 1: Create TUI-dedicated In-memory Log Buffer

// src/ui/tui/log_buffer.rs (new)
use std::collections::VecDeque;
use tracing::Level;

pub struct LogEntry {
    pub level: Level,
    pub target: String,
    pub message: String,
    pub timestamp: chrono::DateTime<chrono::Local>,
}

pub struct LogBuffer {
    entries: VecDeque<LogEntry>,
    max_entries: usize,
}

impl LogBuffer {
    pub fn new(max_entries: usize) -> Self {
        Self {
            entries: VecDeque::with_capacity(max_entries),
            max_entries,
        }
    }
    
    pub fn push(&mut self, entry: LogEntry) {
        if self.entries.len() >= self.max_entries {
            self.entries.pop_front();
        }
        self.entries.push_back(entry);
    }
    
    pub fn iter(&self) -> impl Iterator<Item = &LogEntry> {
        self.entries.iter()
    }
}

Step 2: Implement Custom tracing Layer

// src/ui/tui/log_layer.rs (new)
use std::sync::Arc;
use parking_lot::Mutex;
use tracing_subscriber::Layer;

pub struct TuiLogLayer {
    buffer: Arc<Mutex<LogBuffer>>,
}

impl<S> Layer<S> for TuiLogLayer 
where
    S: tracing::Subscriber,
{
    fn on_event(
        &self,
        event: &tracing::Event<'_>,
        _ctx: tracing_subscriber::layer::Context<'_, S>,
    ) {
        // Create LogEntry and add to buffer
    }
}

Step 3: Add Log Panel to TUI Layout

┌──────────────────────────────────────────────────────────┐
│ Cluster: production - command                            │
│ Total: 8 • ✓ 3 • ✗ 1 • 4 in progress                    │
├──────────────────────────────────────────────────────────┤
│                                                          │
│                    (Main content area)                   │
│                                                          │
├──────────────────────────────────────────────────────────┤
│ [ERROR] Connection timeout: node3 (10.0.0.3:22)         │ <- Log panel (toggleable)
│ [WARN] Slow response from node5                         │
└──────────────────────────────────────────────────────────┘
│ [l] Log  [1-9] Detail  [s] Split  [q] Quit  [?] Help    │

Step 4: Add Log-related Fields to TuiApp State

// src/ui/tui/app.rs modification
pub struct TuiApp {
    // Existing fields...
    
    // New log-related fields
    pub log_buffer: Arc<Mutex<LogBuffer>>,
    pub log_panel_visible: bool,
    pub log_panel_height: u16,  // Default: 3 lines
    pub log_scroll_offset: usize,
}

Acceptance Criteria

  • Implement LogBuffer struct (max 1000 entries, configurable)
  • Implement TuiLogLayer tracing layer
  • Auto-register log layer when entering TUI mode
  • Remove log layer and restore stdout output when exiting TUI mode
  • Implement log panel UI component (src/ui/tui/views/log_panel.rs)
  • Implement l key to toggle log panel visibility
  • Color-code by log level:
    • ERROR: Red (Color::Red)
    • WARN: Yellow (Color::Yellow)
    • INFO: White (Color::White)
    • DEBUG: Gray (Color::DarkGray)
  • Support log panel scrolling (j/k or arrow keys)
  • Adjustable log panel height (3-10 lines)
  • Add log-related keybindings to help overlay

Technical Considerations

Dependencies

  • Utilize tracing_subscriber::Layer trait (no additional dependencies needed)
  • Consider using parking_lot::Mutex (existing std::sync::Mutex also works)

Synchronization

  • LogBuffer is shared between tracing layer and TUI rendering thread
  • Use Arc<Mutex<LogBuffer>> pattern
  • Minimize lock acquisition time during rendering (copy only necessary data)

Memory Protection

  • Limit maximum log entries (default: 1000)
  • Auto-delete old logs in FIFO manner
  • Configurable via environment variable (BSSH_TUI_LOG_MAX_ENTRIES)

Compatibility

  • Maintain existing stdout logging when not in TUI mode
  • Preserve existing behavior with --no-tui or pipe connections

Additional Context

Future Improvements

  • Log filtering functionality (by level, keyword search)
  • Log copy/export functionality
  • Log timestamp display option
  • Log panel pin mode (disable auto-scroll)

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions