feat: comprehensive debug logging across all controller modules

Add detailed [DEBUG] logging to every controller module when
logging.level is set to "debug". Each module with stateful debug
uses SetDebug(bool) wired from main.go. Covers stacks, backup,
cloudflare, integrations, system, monitor, settings, scheduler,
web handlers, storage, metrics, API, selfupdate, and assets.

Also includes the app export/import (.fab bundles) feature from
v0.32.0 and its debug page integration.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-26 18:14:43 +01:00
parent f6caea8067
commit 95c821deb2
54 changed files with 5015 additions and 82 deletions
+25 -4
View File
@@ -65,6 +65,7 @@ type DiskUsageInfo struct {
func GetDiskUsage(path string) *DiskUsageInfo {
var stat syscall.Statfs_t
if err := syscall.Statfs(path, &stat); err != nil {
debugf("[DEBUG] [system] GetDiskUsage: statfs(%q) failed: %v", path, err)
return nil
}
@@ -84,6 +85,8 @@ func GetDiskUsage(path string) *DiskUsageInfo {
}
info.TotalHuman = formatGB(info.TotalGB)
info.UsedHuman = formatGB(info.UsedGB)
debugf("[DEBUG] [system] GetDiskUsage: path=%q total=%s used=%s avail=%.1fGB (%.1f%%)",
path, info.TotalHuman, info.UsedHuman, info.AvailGB, info.UsedPercent)
return info
}
@@ -105,10 +108,12 @@ type FSInfo struct {
func GetFSInfo(path string) *FSInfo {
out, err := exec.Command("findmnt", "-n", "-o", "SOURCE,FSTYPE", "--target", path).Output()
if err != nil {
debugf("[DEBUG] [system] GetFSInfo: findmnt(%q) failed: %v", path, err)
return nil
}
fields := strings.Fields(strings.TrimSpace(string(out)))
if len(fields) < 2 {
debugf("[DEBUG] [system] GetFSInfo: findmnt(%q) returned unexpected output: %q", path, strings.TrimSpace(string(out)))
return nil
}
info := &FSInfo{
@@ -117,6 +122,7 @@ func GetFSInfo(path string) *FSInfo {
}
// Try to get disk model from sysfs
info.Model = diskModel(info.Device)
debugf("[DEBUG] [system] GetFSInfo: path=%q device=%s fstype=%s model=%q", path, info.Device, info.FSType, info.Model)
return info
}
@@ -136,6 +142,7 @@ type DestinationHealth struct {
// CheckBackupDestination performs tiered validation of a cross-drive backup destination.
// Returns a DestinationHealth describing any issues found.
func CheckBackupDestination(path string) DestinationHealth {
debugf("[DEBUG] [system] CheckBackupDestination: path=%q", path)
h := DestinationHealth{Severity: "ok"}
// Tier 1: path must exist
@@ -143,6 +150,7 @@ func CheckBackupDestination(path string) DestinationHealth {
h.Warning = "A cél tárhely (" + path + ") nem létezik!"
h.Blocked = true
h.Severity = "critical"
debugf("[DEBUG] [system] CheckBackupDestination: path=%q — tier1 FAIL (not exists)", path)
return h
}
h.Exists = true
@@ -152,6 +160,7 @@ func CheckBackupDestination(path string) DestinationHealth {
h.Warning = "A cél tárhely (" + path + ") nem írható! Ellenőrizd a jogosultságokat."
h.Blocked = true
h.Severity = "critical"
debugf("[DEBUG] [system] CheckBackupDestination: path=%q — tier2 FAIL (not writable)", path)
return h
}
h.Writable = true
@@ -165,9 +174,11 @@ func CheckBackupDestination(path string) DestinationHealth {
"Meghajtóhiba esetén az eredeti adat és a mentés is elveszhet. " +
"Külső meghajtó használata javasolt."
h.Severity = "warning"
debugf("[DEBUG] [system] CheckBackupDestination: path=%q — tier3 WARN (same block device as /)", path)
// Don't return early — also check disk usage
} else {
h.MountPoint = true
debugf("[DEBUG] [system] CheckBackupDestination: path=%q — tier3 OK (different block device)", path)
}
// Tier 4: disk usage checks
@@ -199,6 +210,8 @@ func CheckBackupDestination(path string) DestinationHealth {
}
}
debugf("[DEBUG] [system] CheckBackupDestination: path=%q — result: severity=%s blocked=%v freeGB=%.1f usedPct=%.1f%%",
path, h.Severity, h.Blocked, h.FreeGB, h.UsedPercent)
return h
}
@@ -256,8 +269,11 @@ type ProbeResult struct {
// ProbeStoragePath checks if a storage path is responsive.
// Uses a goroutine with a 3-second timeout to avoid blocking on dead mounts.
func ProbeStoragePath(path string) ProbeResult {
start := time.Now()
// Quick check: does the path exist at all?
if _, err := os.Lstat(path); os.IsNotExist(err) {
debugf("[DEBUG] [system] ProbeStoragePath: path=%q — not exists (%s)", path, time.Since(start).Round(time.Millisecond))
return ProbeResult{Status: ProbeDisconnected, Err: err}
}
@@ -273,17 +289,22 @@ func ProbeStoragePath(path string) ProbeResult {
select {
case res := <-ch:
elapsed := time.Since(start).Round(time.Millisecond)
if res.err == nil {
debugf("[DEBUG] [system] ProbeStoragePath: path=%q — connected (%s)", path, elapsed)
return ProbeResult{Status: ProbeConnected}
}
errStr := res.err.Error()
if strings.Contains(errStr, "transport endpoint") ||
strings.Contains(errStr, "input/output error") ||
strings.Contains(errStr, "no such device") {
debugf("[DEBUG] [system] ProbeStoragePath: path=%q — disconnected: %v (%s)", path, res.err, elapsed)
return ProbeResult{Status: ProbeDisconnected, Err: res.err}
}
debugf("[DEBUG] [system] ProbeStoragePath: path=%q — disconnected (other error): %v (%s)", path, res.err, elapsed)
return ProbeResult{Status: ProbeDisconnected, Err: res.err}
case <-time.After(3 * time.Second):
debugf("[DEBUG] [system] ProbeStoragePath: path=%q — TIMEOUT (3s)", path)
return ProbeResult{Status: ProbeTimeout, Err: fmt.Errorf("stat timed out after 3s")}
}
}
@@ -302,11 +323,11 @@ func IsUSBDevice(devicePath string) bool {
if err != nil {
continue
}
if strings.Contains(link, "/usb") {
return true
}
return false // found the sysfs entry, but not USB
isUSB := strings.Contains(link, "/usb")
debugf("[DEBUG] [system] IsUSBDevice: device=%q disk=%q sysfs=%s → usb=%v", devicePath, disk, link, isUSB)
return isUSB
}
debugf("[DEBUG] [system] IsUSBDevice: device=%q disk=%q — no sysfs entry found", devicePath, disk)
return false
}