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:
@@ -149,6 +149,7 @@ impl Manager {
|
||||
Err(_) => Err("host key prompt failed: host key prompt timed out waiting for user response".into()),
|
||||
}
|
||||
};
|
||||
tracing::info!(host = %k.host, port = k.port, key_type = %k.key_type, fingerprint = %k.fingerprint, ?policy, known = stored.is_some(), "checking host key");
|
||||
match (policy, stored) {
|
||||
(HostKeyPolicy::Insecure, None) | (HostKeyPolicy::AcceptNew, None) => save(),
|
||||
(HostKeyPolicy::Insecure, Some(s)) => touch(s),
|
||||
|
||||
@@ -346,6 +346,7 @@ impl Manager {
|
||||
}
|
||||
(Err(_), None) => {}
|
||||
(Err(e), Some(s)) => {
|
||||
tracing::warn!(session = id, error = %e, "connection failed");
|
||||
s.info.state = SessionState::Error;
|
||||
s.info.error = e.to_string();
|
||||
attempt_failed(&s.session_secrets, &e.to_string());
|
||||
@@ -353,6 +354,7 @@ impl Manager {
|
||||
self.emit_state(id, SessionState::Error, &e.to_string());
|
||||
}
|
||||
(Ok(client), Some(s)) => {
|
||||
tracing::info!(session = id, "connected");
|
||||
let typed = std::mem::take(&mut s.session_secrets.lock().pending);
|
||||
s.client = client.clone();
|
||||
s.info.state = SessionState::Connected;
|
||||
@@ -365,6 +367,7 @@ impl Manager {
|
||||
}
|
||||
for (key, value) in typed {
|
||||
if let Err(e) = self.secrets.set_checked(&key, &value) {
|
||||
tracing::warn!(session = id, credential = key.rsplit(':').next().unwrap_or(""), error = %e, "credential not saved");
|
||||
self.emit(Event::Warning { session: id.into(), message: format!("Credential not saved: {e}") });
|
||||
}
|
||||
}
|
||||
@@ -375,6 +378,7 @@ impl Manager {
|
||||
}
|
||||
|
||||
fn connect_protocol(self: &Arc<Self>, id: &str, epoch: u64, p: &Profile, prompt: Prompt) -> Result<Client> {
|
||||
tracing::info!(session = id, protocol = p.protocol.id(), host = %p.host, port = p.port, "connecting");
|
||||
let _ = self.with(id, |s| {
|
||||
let mut st = s.session_secrets.lock();
|
||||
st.pending.clear();
|
||||
@@ -510,6 +514,7 @@ impl Manager {
|
||||
s.profile.clone()
|
||||
};
|
||||
self.store.add_audit("disconnect", "", id, "Session disconnected");
|
||||
tracing::info!(session = id, "remote closed the session");
|
||||
if let Some(p) = profile.filter(|p| p.auto_reconnect()) {
|
||||
self.schedule_reconnect(id, epoch, p);
|
||||
return;
|
||||
@@ -525,9 +530,11 @@ impl Manager {
|
||||
let attempt = self.with(id, |s| {
|
||||
s.reconnect_count += 1;
|
||||
if s.reconnect_count > max {
|
||||
tracing::warn!(session = id, tries = max, "giving up reconnecting");
|
||||
s.info.state = SessionState::Disconnected;
|
||||
return None;
|
||||
}
|
||||
tracing::info!(session = id, attempt = s.reconnect_count, of = max, "scheduling reconnect");
|
||||
s.info.state = SessionState::Reconnecting;
|
||||
s.info.error.clear();
|
||||
if let Some(c) = s.cancel_reconnect.replace(cancel.clone()) {
|
||||
|
||||
@@ -48,7 +48,7 @@ fn json_col<T: serde::Serialize>(v: &Option<T>) -> Option<String> {
|
||||
|
||||
fn parse_col<T: serde::de::DeserializeOwned>(s: Option<String>) -> Option<T> {
|
||||
let s = s.filter(|s| !s.is_empty() && s != "null")?;
|
||||
serde_json::from_str(&s).inspect_err(|e| eprintln!("storage: ignoring unreadable profile config: {e}")).ok()
|
||||
serde_json::from_str(&s).inspect_err(|e| tracing::warn!(error = %e, "ignoring unreadable profile config")).ok()
|
||||
}
|
||||
|
||||
fn scan_profile(r: &Row) -> rusqlite::Result<Profile> {
|
||||
|
||||
Reference in New Issue
Block a user