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
+48 -4
View File
@@ -14,9 +14,10 @@ import (
// Settings holds customer-modifiable overrides and cached state.
// Persisted as a single JSON file (settings.json) in the data directory.
type Settings struct {
mu sync.RWMutex `json:"-"`
path string `json:"-"`
log *log.Logger `json:"-"`
mu sync.RWMutex `json:"-"`
path string `json:"-"`
log *log.Logger `json:"-"`
debug bool `json:"-"`
// Auth
PasswordHash string `json:"password_hash,omitempty"` // bcrypt hash, overrides controller.yaml
@@ -156,6 +157,11 @@ type DBValidationCache struct {
Error string `json:"error,omitempty"`
}
// SetDebug enables or disables debug logging for settings operations.
func (s *Settings) SetDebug(debug bool) {
s.debug = debug
}
// Load reads settings from the given file path.
// Returns empty Settings if the file doesn't exist (not an error).
func Load(path string, logger *log.Logger) (*Settings, error) {
@@ -178,6 +184,10 @@ func Load(path string, logger *log.Logger) (*Settings, error) {
}
logger.Printf("[DEBUG] Settings loaded from %s", path)
if s.debug {
s.log.Printf("[DEBUG] [settings] loaded: storage_paths=%d integrations=%d pending_events=%d",
len(s.StoragePaths), len(s.Integrations), len(s.PendingEvents))
}
s.migrateResticToRsync()
return s, nil
}
@@ -226,7 +236,9 @@ func (s *Settings) save() error {
return fmt.Errorf("renaming settings file: %w", err)
}
s.log.Printf("[DEBUG] Settings saved to %s", s.path)
if s.debug {
s.log.Printf("[DEBUG] [settings] saved to %s (%d bytes)", s.path, len(data))
}
return nil
}
@@ -435,6 +447,9 @@ func (s *Settings) GetSchedulableStoragePaths() []StoragePath {
func (s *Settings) AddStoragePath(sp StoragePath) error {
s.mu.Lock()
defer s.mu.Unlock()
if s.debug {
s.log.Printf("[DEBUG] [settings] AddStoragePath path=%q label=%q default=%v", sp.Path, sp.Label, sp.IsDefault)
}
for _, existing := range s.StoragePaths {
if existing.Path == sp.Path {
return fmt.Errorf("storage path %q already registered", sp.Path)
@@ -453,6 +468,9 @@ func (s *Settings) AddStoragePath(sp StoragePath) error {
func (s *Settings) RemoveStoragePath(path string) error {
s.mu.Lock()
defer s.mu.Unlock()
if s.debug {
s.log.Printf("[DEBUG] [settings] RemoveStoragePath path=%q", path)
}
var kept []StoragePath
for _, sp := range s.StoragePaths {
if sp.Path != path {
@@ -515,6 +533,10 @@ func (s *Settings) AutoDiscoverStoragePaths(discoveredPaths []string, fallbackHD
s.mu.Lock()
defer s.mu.Unlock()
if s.debug {
s.log.Printf("[DEBUG] [settings] AutoDiscoverStoragePaths discovered=%v fallback=%q existing=%d", discoveredPaths, fallbackHDDPath, len(s.StoragePaths))
}
if len(s.StoragePaths) > 0 {
return // already configured
}
@@ -572,6 +594,9 @@ func InferStorageLabel(path string) string {
func (s *Settings) SetDisconnected(path string, disconnected bool, stoppedStacks []string) error {
s.mu.Lock()
defer s.mu.Unlock()
if s.debug {
s.log.Printf("[DEBUG] [settings] SetDisconnected path=%q disconnected=%v stopped_stacks=%d", path, disconnected, len(stoppedStacks))
}
for i := range s.StoragePaths {
if s.StoragePaths[i].Path == path {
s.StoragePaths[i].Disconnected = disconnected
@@ -679,6 +704,9 @@ func (s *Settings) ClearStoppedStacks(path string) error {
func (s *Settings) SetDecommissioned(path, migratedTo string) error {
s.mu.Lock()
defer s.mu.Unlock()
if s.debug {
s.log.Printf("[DEBUG] [settings] SetDecommissioned path=%q migrated_to=%q", path, migratedTo)
}
for i := range s.StoragePaths {
if s.StoragePaths[i].Path == path {
s.StoragePaths[i].Decommissioned = true
@@ -811,6 +839,9 @@ func (s *Settings) SetRetrievalPassword(password string) error {
func (s *Settings) AddPendingEvent(event PendingEvent) error {
s.mu.Lock()
defer s.mu.Unlock()
if s.debug {
s.log.Printf("[DEBUG] [settings] AddPendingEvent type=%q severity=%q", event.EventType, event.Severity)
}
s.PendingEvents = append(s.PendingEvents, event)
return s.save()
}
@@ -822,6 +853,9 @@ func (s *Settings) DrainPendingEvents() []PendingEvent {
if len(s.PendingEvents) == 0 {
return nil
}
if s.debug {
s.log.Printf("[DEBUG] [settings] DrainPendingEvents count=%d", len(s.PendingEvents))
}
events := make([]PendingEvent, len(s.PendingEvents))
copy(events, s.PendingEvents)
s.PendingEvents = nil
@@ -862,6 +896,13 @@ func (s *Settings) GetGeoRestriction() *GeoRestriction {
func (s *Settings) SetGeoRestriction(geo *GeoRestriction) error {
s.mu.Lock()
defer s.mu.Unlock()
if s.debug {
if geo == nil {
s.log.Printf("[DEBUG] [settings] SetGeoRestriction geo=nil (clearing)")
} else {
s.log.Printf("[DEBUG] [settings] SetGeoRestriction enabled=%v countries=%d", geo.Enabled, len(geo.AllowedCountries))
}
}
if geo == nil {
s.GeoRestriction = nil
return s.save()
@@ -953,6 +994,9 @@ func (s *Settings) GetIntegrationState(key string) (IntegrationState, bool) {
func (s *Settings) SetIntegrationState(key string, state IntegrationState) error {
s.mu.Lock()
defer s.mu.Unlock()
if s.debug {
s.log.Printf("[DEBUG] [settings] SetIntegrationState key=%q status=%q enabled=%v", key, state.Status, state.Enabled)
}
if s.Integrations == nil {
s.Integrations = make(map[string]IntegrationState)
}