feat: diagnostic log (tracing)

The app now writes a diagnostic log to nexus.log in the data directory
(0600, rotated at 5 MiB): connection attempts and results, remote
closes, reconnect scheduling, host-key/certificate checks with the
policy applied, and credentials that couldn't be stored (kind only).
Credentials and terminal data are never logged. RUST_LOG sets the level
and also mirrors the log to stderr. The remaining eprintln! diagnostics
in storage and URL handling go through it too.
This commit is contained in:
Andy Dixon
2026-10-12 17:08:37 +01:00
parent 02bd237636
commit aacd44a275
9 changed files with 91 additions and 2 deletions
+2
View File
@@ -14,6 +14,8 @@ eframe = { version = "0.36", default-features = false, features = ["default_font
egui = "0.36"
parking_lot = "0.12"
zeroize = "1"
tracing = "0.1"
tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt", "env-filter", "std", "ansi"] }
egui-phosphor = "0.14"
rfd = "0.17"
image = { version = "0.25", default-features = false, features = ["png"] }
+22
View File
@@ -114,6 +114,8 @@ fn main() -> eframe::Result {
std::process::exit(1);
}
};
init_logging(&svc.config.data_dir);
tracing::info!(version = nexus_core::service::VERSION, "starting");
let headless = opts.screenshot.is_some();
let native = eframe::NativeOptions {
persist_window: !headless,
@@ -139,3 +141,23 @@ fn window_icon() -> egui::IconData {
let img = image::load_from_memory(chrome::LOGO_PNG).map(|i| i.to_rgba8()).unwrap_or_default();
egui::IconData { width: img.width(), height: img.height(), rgba: img.into_raw() }
}
/// Diagnostics go to `nexus.log` in the data directory (rotated at 5 MiB), and to stderr too when
/// RUST_LOG is set. RUST_LOG also sets the level (default: info for Nexus, warn for libraries).
/// Connection lifecycle, host-key checks and errors are logged; credentials and terminal data never.
fn init_logging(data_dir: &std::path::Path) {
use tracing_subscriber::prelude::*;
let path = data_dir.join("nexus.log");
if std::fs::metadata(&path).is_ok_and(|m| m.len() > 5 << 20) {
let _ = std::fs::rename(&path, data_dir.join("nexus.log.1"));
}
let mut opts = std::fs::OpenOptions::new();
opts.create(true).append(true);
#[cfg(unix)]
std::os::unix::fs::OpenOptionsExt::mode(&mut opts, 0o600);
let file = opts.open(&path).ok().map(|f| tracing_subscriber::fmt::layer().with_ansi(false).with_writer(std::sync::Mutex::new(f)));
let stderr = std::env::var_os("RUST_LOG").map(|_| tracing_subscriber::fmt::layer().with_writer(std::io::stderr));
let filter = tracing_subscriber::EnvFilter::try_from_default_env()
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("warn,nexus_terminal=info,nexus_core=info"));
let _ = tracing_subscriber::registry().with(filter).with(file).with(stderr).try_init();
}
+1 -1
View File
@@ -136,7 +136,7 @@ pub fn is_web_url(url: &str) -> bool {
/// Opens `url` in the browser if it is a web link; returns false (and opens nothing) otherwise.
pub fn open_web_url(ctx: &egui::Context, url: &str) -> bool {
if !is_web_url(url) {
eprintln!("refusing to open non-web URL {url:?}");
tracing::warn!(url, "refusing to open a non-web URL");
return false;
}
ctx.open_url(egui::OpenUrl::new_tab(url.trim()));