Add TUI module for logging and enhance logging functionality
This commit is contained in:
parent
5a8792b9fd
commit
6ebdef9a64
4 changed files with 712 additions and 8 deletions
51
src/main.rs
51
src/main.rs
|
@ -1,11 +1,14 @@
|
|||
mod client;
|
||||
use client::handlers::handle_client;
|
||||
mod tui; // Add this new module
|
||||
|
||||
use colog;
|
||||
use log::{error, info};
|
||||
use client::handlers::handle_client;
|
||||
use log::{error, info, Level, Log, Metadata, Record};
|
||||
use serde::Deserialize;
|
||||
use std::sync::mpsc;
|
||||
use std::thread;
|
||||
use tokio::net::TcpListener;
|
||||
use tokio::sync::broadcast;
|
||||
use tui::{run_app, LogEntry};
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct Config {
|
||||
|
@ -13,10 +16,47 @@ struct Config {
|
|||
port: String,
|
||||
}
|
||||
|
||||
struct CustomLogger {
|
||||
tx: mpsc::Sender<LogEntry>,
|
||||
}
|
||||
|
||||
impl Log for CustomLogger {
|
||||
fn enabled(&self, metadata: &Metadata) -> bool {
|
||||
metadata.level() <= Level::Info
|
||||
}
|
||||
|
||||
fn log(&self, record: &Record) {
|
||||
if self.enabled(record.metadata()) {
|
||||
let now = chrono::Local::now();
|
||||
let log_entry = LogEntry {
|
||||
timestamp: now.format("%H:%M:%S").to_string(),
|
||||
level: record.level().to_string(),
|
||||
message: record.args().to_string(),
|
||||
};
|
||||
|
||||
let _ = self.tx.send(log_entry);
|
||||
}
|
||||
}
|
||||
|
||||
fn flush(&self) {}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
// Initialize the logger
|
||||
colog::init();
|
||||
// Create a channel for logging
|
||||
let (tx, rx) = mpsc::channel();
|
||||
|
||||
// Create and set the custom logger
|
||||
let logger = Box::new(CustomLogger { tx });
|
||||
log::set_boxed_logger(logger).unwrap();
|
||||
log::set_max_level(log::LevelFilter::Info);
|
||||
|
||||
// Start the TUI in a separate thread
|
||||
let _tui_handle = thread::spawn(move || {
|
||||
if let Err(e) = run_app(rx) {
|
||||
eprintln!("Error running TUI: {:?}", e);
|
||||
}
|
||||
});
|
||||
|
||||
// Load the configuration from config file
|
||||
let config = match std::fs::read_to_string("config.toml") {
|
||||
|
@ -34,6 +74,7 @@ async fn main() {
|
|||
};
|
||||
|
||||
info!("Configuration loaded: {:?}", config);
|
||||
|
||||
// Bind a TCP listener to accept incoming connections
|
||||
let listener = TcpListener::bind(config.address + ":" + config.port.as_str())
|
||||
.await
|
||||
|
|
138
src/tui/mod.rs
Normal file
138
src/tui/mod.rs
Normal file
|
@ -0,0 +1,138 @@
|
|||
use crossterm::{
|
||||
event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode},
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
};
|
||||
use ratatui::{
|
||||
backend::CrosstermBackend,
|
||||
layout::{Constraint, Direction, Layout},
|
||||
style::{Color, Modifier, Style},
|
||||
text::{Line, Span},
|
||||
widgets::{Block, Borders, List, ListItem},
|
||||
Terminal,
|
||||
};
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
sync::mpsc,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
pub struct LogEntry {
|
||||
pub timestamp: String,
|
||||
pub level: String,
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
pub struct App {
|
||||
logs: VecDeque<LogEntry>,
|
||||
should_quit: bool,
|
||||
}
|
||||
|
||||
impl App {
|
||||
pub fn new() -> App {
|
||||
App {
|
||||
logs: VecDeque::with_capacity(100),
|
||||
should_quit: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_log(&mut self, entry: LogEntry) {
|
||||
if self.logs.len() >= 100 {
|
||||
self.logs.pop_front();
|
||||
}
|
||||
self.logs.push_back(entry);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_app(rx: mpsc::Receiver<LogEntry>) -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Terminal initialization
|
||||
enable_raw_mode()?;
|
||||
let mut stdout = std::io::stdout();
|
||||
execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
|
||||
let backend = CrosstermBackend::new(stdout);
|
||||
let mut terminal = Terminal::new(backend)?;
|
||||
|
||||
let mut app = App::new();
|
||||
let mut last_tick = Instant::now();
|
||||
let tick_rate = Duration::from_millis(250);
|
||||
|
||||
loop {
|
||||
terminal.draw(|f| {
|
||||
let chunks = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.margin(1)
|
||||
.constraints(
|
||||
[
|
||||
Constraint::Min(0), // Logs
|
||||
]
|
||||
.as_ref(),
|
||||
)
|
||||
.split(f.area());
|
||||
|
||||
// Logs
|
||||
let logs: Vec<ListItem> = app
|
||||
.logs
|
||||
.iter()
|
||||
.map(|log| {
|
||||
let color = match log.level.as_str() {
|
||||
"ERROR" => Color::Red,
|
||||
"WARN" => Color::Yellow,
|
||||
"INFO" => Color::Blue,
|
||||
"DEBUG" => Color::Green,
|
||||
_ => Color::White,
|
||||
};
|
||||
|
||||
ListItem::new(Line::from(vec![
|
||||
Span::styled(&log.timestamp, Style::default().fg(Color::DarkGray)),
|
||||
Span::raw(" "),
|
||||
Span::styled(
|
||||
format!("[{}]", log.level),
|
||||
Style::default().fg(color).add_modifier(Modifier::BOLD),
|
||||
),
|
||||
Span::raw(" "),
|
||||
Span::raw(&log.message),
|
||||
]))
|
||||
})
|
||||
.collect();
|
||||
|
||||
let logs =
|
||||
List::new(logs).block(Block::default().borders(Borders::ALL).title("Server Logs"));
|
||||
f.render_widget(logs, chunks[0]);
|
||||
})?;
|
||||
|
||||
let timeout = tick_rate
|
||||
.checked_sub(last_tick.elapsed())
|
||||
.unwrap_or_else(|| Duration::from_secs(0));
|
||||
|
||||
if crossterm::event::poll(timeout)? {
|
||||
if let Event::Key(key) = event::read()? {
|
||||
if let KeyCode::Char('q') = key.code {
|
||||
app.should_quit = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if last_tick.elapsed() >= tick_rate {
|
||||
// Check for new log entries
|
||||
if let Ok(log_entry) = rx.try_recv() {
|
||||
app.add_log(log_entry);
|
||||
}
|
||||
last_tick = Instant::now();
|
||||
}
|
||||
|
||||
if app.should_quit {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Restore terminal
|
||||
disable_raw_mode()?;
|
||||
execute!(
|
||||
terminal.backend_mut(),
|
||||
LeaveAlternateScreen,
|
||||
DisableMouseCapture
|
||||
)?;
|
||||
terminal.show_cursor()?;
|
||||
|
||||
Ok(())
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue