v0.83.0: observability pass — always-DEBUG capture ring + GET /debug/logs + heartbeat log-pull + gap-fill sweep

Capture layer: applog.New returns (logger, Ring) — slog fan-out, stderr at the
configured level, ~1000-entry ring fixed at LevelDebug (remote diagnostics
without a config flip). GET /debug/logs (token-authed, ?raw=1) + request-level
DEBUG middleware. Heartbeat log-pull mirrors the report logtail pattern:
envelope log_tail_requested -> next heartbeat carries log_tail (128KB cap,
consume-once, failed-push retry proven). Gap-fill sweep over netverify/
netstorage/netmount/signedjobs/selfupdate/disks/controller-swap/desired/loop.
Red-proofs: ring-at-emit-level FAILs capture test; drain removed FAILs
consume-once; dropped phase line FAILs the S7 log-sequence smoke.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
This commit is contained in:
2026-07-11 16:24:07 +02:00
parent 461eaf42c1
commit cb692f8788
20 changed files with 902 additions and 29 deletions
+8 -2
View File
@@ -127,12 +127,15 @@ func (s *Server) handleNetStorageAdd(w http.ResponseWriter, r *http.Request, vmi
if !s.netReachable(spec.Protocol, spec.Server) {
s.releaseNetVerify(job)
s.logger.Warn("local-api: network mount refused — endpoint not reachable",
"name", spec.Name, "server", spec.Server, "proto", spec.Protocol)
"name", spec.Name, "server", spec.Server, "proto", spec.Protocol,
"code", storage.NetVerifyUnreachable)
writeStatus(w, http.StatusBadGateway, false,
map[string]any{"code": storage.NetVerifyUnreachable},
"NAS endpoint not reachable")
return
}
s.logger.Debug("local-api: NAS endpoint pre-probe passed",
"name", spec.Name, "server", spec.Server, "proto", spec.Protocol)
// SMB: stage the credentials out-of-band (0600). NFS needs none (server squash).
if spec.Protocol == storage.ProtocolSMB {
@@ -144,6 +147,7 @@ func (s *Server) handleNetStorageAdd(w http.ResponseWriter, r *http.Request, vmi
return
}
spec.CredsRef = credsPath
s.logger.Debug("local-api: SMB credentials staged", "name", spec.Name, "path", credsPath) // path only, never content
}
if err := s.netStorage.EnsureNetworkMount(r.Context(), spec); err != nil {
@@ -251,7 +255,9 @@ func (s *Server) writeSMBCreds(name, username, password string) (string, error)
// removeSMBCreds deletes a share's creds file (best-effort; absent is fine).
func (s *Server) removeSMBCreds(name string) {
_ = os.Remove(s.smbCredsPath(name))
if err := os.Remove(s.smbCredsPath(name)); err == nil {
s.logger.Debug("local-api: SMB credentials file removed", "name", name, "path", s.smbCredsPath(name))
}
}
// smbCredsPath computes a share's creds file path (pure — used by validation BEFORE the file exists).