Krolyxon
Commit 9ff7ca4

rmptui9ff7ca4

add logging functionality

Author
krolyxon
Date
2026-10-15 23:23 +0530
Parent
ad32349
Commit
9ff7ca408006ed1ad14cf1ecd8513fefa24a1c1d

+111 −3

3 files changed

  1. M src/lib.rs +1 −0
  2. A src/logging.rs +86 −0
  3. M src/main.rs +24 −3
M src/lib.rs View
@@ -6,6 +6,7 @@ pub mod config;
66 pub mod connection;
77 pub mod event_handler;
88 pub mod list;
9+pub mod logging;
910 pub mod tui;
1011 pub mod ui;
1112 pub mod utils;
A src/logging.rs View
@@ -0,0 +1,86 @@
1+use std::{
2+ fs::{self, OpenOptions},
3+ io::{self, Write},
4+ path::{Path, PathBuf},
5+ sync::{Mutex, OnceLock},
6+ time::{SystemTime, UNIX_EPOCH},
7+};
8+
9+use crate::AppResult;
10+
11+static LOG_FILE: OnceLock<Mutex<fs::File>> = OnceLock::new();
12+
13+pub fn init(path: &Path) -> AppResult<PathBuf> {
14+ if let Some(parent) = path.parent() {
15+ fs::create_dir_all(parent)?;
16+ }
17+
18+ let mut options = OpenOptions::new();
19+ options.create(true).append(true);
20+ #[cfg(unix)]
21+ {
22+ use std::os::unix::fs::OpenOptionsExt;
23+ options.mode(0o600);
24+ }
25+ let file = options.open(path)?;
26+ let _ = LOG_FILE.set(Mutex::new(file));
27+
28+ let panic_hook = std::panic::take_hook();
29+ std::panic::set_hook(Box::new(move |panic| {
30+ let message = panic
31+ .payload()
32+ .downcast_ref::<&str>()
33+ .copied()
34+ .or_else(|| panic.payload().downcast_ref::<String>().map(String::as_str))
35+ .unwrap_or("non-string panic payload");
36+ let location = panic
37+ .location()
38+ .map(|location| {
39+ format!(
40+ " at {}:{}:{}",
41+ location.file(),
42+ location.line(),
43+ location.column()
44+ )
45+ })
46+ .unwrap_or_default();
47+ error(&format!("panic: {message}{location}"));
48+ panic_hook(panic);
49+ }));
50+
51+ info(&format!("logging initialized: {}", path.display()));
52+ Ok(path.to_path_buf())
53+}
54+
55+pub fn info(message: &str) {
56+ write("INFO", message);
57+}
58+
59+pub fn error(message: &str) {
60+ write("ERROR", message);
61+}
62+
63+fn write(level: &str, message: &str) {
64+ let Some(file) = LOG_FILE.get() else {
65+ return;
66+ };
67+
68+ let timestamp = SystemTime::now()
69+ .duration_since(UNIX_EPOCH)
70+ .unwrap_or_default();
71+ let line = format!(
72+ "[{}.{:03}] {level} {message}",
73+ timestamp.as_secs(),
74+ timestamp.subsec_millis()
75+ );
76+ let mut file = file
77+ .lock()
78+ .unwrap_or_else(std::sync::PoisonError::into_inner);
79+ if let Err(error) = writeln!(file, "{line}") {
80+ report_write_error(error);
81+ }
82+}
83+
84+fn report_write_error(error: io::Error) {
85+ eprintln!("failed to write rmptui log: {error}");
86+}
M src/main.rs View
@@ -8,12 +8,28 @@ use rmptui::{
88 config::Config,
99 connection::{Connection, MpdSnapshot},
1010 event_handler::handler,
11− tui, AppResult,
11+ logging, tui, AppResult,
1212 };
1313 use tokio::{sync::mpsc, time};
1414
1515 #[tokio::main]
1616 async fn main() -> AppResult<()> {
17+ let log_path = std::path::Path::new("/tmp/rmptui.log");
18+ if let Err(error) = logging::init(log_path) {
19+ eprintln!(
20+ "Could not initialize logging at {}: {error}",
21+ log_path.display()
22+ );
23+ }
24+
25+ let result = run().await;
26+ if let Err(error) = &result {
27+ logging::error(&format!("application exited with error: {error}"));
28+ }
29+ result
30+}
31+
32+async fn run() -> AppResult<()> {
1733 let (config, config_path) = Config::load_or_create()?;
1834 eprintln!("Using configuration: {}", config_path.display());
1935
@@ -58,8 +74,13 @@ async fn main() -> AppResult<()> {
5874 let (updates_tx, mut updates_rx) = mpsc::channel(16);
5975 let watcher_address = address.clone();
6076 let watcher_password = password.clone();
61− let watcher =
62− tokio::spawn(async move { watch_mpd(watcher_address, watcher_password, updates_tx).await });
77+ let watcher = tokio::spawn(async move {
78+ let result = watch_mpd(watcher_address, watcher_password, updates_tx).await;
79+ if let Err(error) = &result {
80+ logging::error(&format!("MPD change watcher failed: {error}"));
81+ }
82+ result
83+ });
6384 let mut terminal_events = EventStream::new();
6485 let mut redraw = time::interval(Duration::from_millis(50));
6586 redraw.set_missed_tick_behavior(time::MissedTickBehavior::Skip);