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
+69
View File
@@ -117,15 +117,33 @@ func (m *Manager) SetEncryptionKey(key []byte) {
m.encKey = key
}
// GetStacksBaseDir returns the base directory where stacks live.
func (m *Manager) GetStacksBaseDir() string {
return m.cfg.Paths.StacksDir
}
// MigrateEncryption re-saves app.yaml for deployed stacks that still have
// plaintext values in sensitive fields. Called once on startup.
func (m *Manager) MigrateEncryption() {
m.mu.Lock()
defer m.mu.Unlock()
if m.encKey == nil {
if m.isDebug() {
m.logger.Printf("[DEBUG] [stacks] MigrateEncryption: no encryption key set, skipping")
}
return
}
if m.isDebug() {
deployedCount := 0
for _, s := range m.stacks {
if s.Deployed {
deployedCount++
}
}
m.logger.Printf("[DEBUG] [stacks] MigrateEncryption: checking %d deployed stacks for plaintext sensitive values", deployedCount)
}
migrated := 0
for _, s := range m.stacks {
if !s.Deployed {
@@ -141,6 +159,11 @@ func (m *Manager) MigrateEncryption() {
if len(sensitive) == 0 {
continue
}
if m.isDebug() {
m.logger.Printf("[DEBUG] [stacks] MigrateEncryption: checking stack %q (%d sensitive fields)", s.Name, len(sensitive))
}
needsMigration := false
for _, envVar := range sensitive {
if v, ok := appCfg.Env[envVar]; ok && v != "" && !crypto.IsEncrypted(v) {
@@ -149,6 +172,9 @@ func (m *Manager) MigrateEncryption() {
}
}
if needsMigration {
if m.isDebug() {
m.logger.Printf("[DEBUG] [stacks] MigrateEncryption: stack %q needs migration — re-saving with encryption", s.Name)
}
if err := SaveAppConfig(stackDir, appCfg, m.encKey, sensitive); err != nil {
m.logger.Printf("[WARN] Encryption migration failed for %s: %v", s.Name, err)
} else {
@@ -229,6 +255,10 @@ func (m *Manager) ScanStacks() error {
appCfg := LoadAppConfig(stackDir)
deployed := appCfg != nil && appCfg.Deployed
if m.isDebug() {
m.logger.Printf("[DEBUG] [stacks] ScanStacks: found stack %q deployed=%v composePath=%s", name, deployed, composePath)
}
if existing, ok := m.stacks[name]; ok {
existing.ComposePath = composePath
existing.Meta = meta
@@ -261,6 +291,13 @@ func (m *Manager) ScanStacks() error {
// Detect orphaned stacks (deployed but no longer in catalog)
catalogTemplates := m.getCatalogTemplateSlugs()
if m.isDebug() {
if catalogTemplates != nil {
m.logger.Printf("[DEBUG] [stacks] ScanStacks: catalog has %d template slugs for orphan detection", len(catalogTemplates))
} else {
m.logger.Printf("[DEBUG] [stacks] ScanStacks: catalog templates unavailable, skipping orphan detection")
}
}
if catalogTemplates != nil {
orphanCount := 0
for _, stack := range m.stacks {
@@ -271,6 +308,9 @@ func (m *Manager) ScanStacks() error {
stack.Orphaned = !catalogTemplates[stack.Name]
if stack.Orphaned {
orphanCount++
if m.isDebug() {
m.logger.Printf("[DEBUG] [stacks] ScanStacks: stack %q is orphaned (deployed but not in catalog)", stack.Name)
}
}
}
if orphanCount > 0 {
@@ -306,6 +346,7 @@ func (m *Manager) refreshStatusLocked() error {
projectContainers := make(map[string][]ContainerInfo)
totalContainers := 0
for _, line := range strings.Split(strings.TrimSpace(output), "\n") {
if line == "" {
continue
@@ -322,6 +363,11 @@ func (m *Manager) refreshStatusLocked() error {
Status: parts[3],
}
projectContainers[parts[4]] = append(projectContainers[parts[4]], ci)
totalContainers++
}
if m.isDebug() {
m.logger.Printf("[DEBUG] [stacks] refreshStatusLocked: docker ps returned %d containers across %d projects", totalContainers, len(projectContainers))
}
for name, stack := range m.stacks {
@@ -346,6 +392,10 @@ func (m *Manager) refreshStatusLocked() error {
stack.State = StateUnhealthy
}
if m.isDebug() {
m.logger.Printf("[DEBUG] [stacks] refreshStatusLocked: stack %q → state=%s containers=%d", name, stack.State, len(stack.Containers))
}
stack.LastUpdated = time.Now()
}
@@ -569,12 +619,20 @@ func (m *Manager) StartStack(name string) error {
return fmt.Errorf("stack %q not found", name)
}
if m.isDebug() {
m.logger.Printf("[DEBUG] [stacks] StartStack %s: current state=%s deployed=%v", name, stack.State, stack.Deployed)
}
m.logger.Printf("[INFO] Starting stack: %s", name)
start := time.Now()
dir := filepath.Dir(stack.ComposePath)
env := m.stackEnv(dir)
if m.isDebug() {
m.logger.Printf("[DEBUG] [stacks] StartStack %s: prepared %d env vars for compose", name, len(env))
}
if _, err := m.composeExecCustomEnv(dir, env, "up", "-d"); err != nil {
m.logger.Printf("[ERROR] Stack %s start failed after %.1fs: %v", name, time.Since(start).Seconds(), err)
return fmt.Errorf("starting stack %s: %w", name, err)
@@ -604,6 +662,10 @@ func (m *Manager) StopStack(name string) error {
return fmt.Errorf("stack %q not found", name)
}
if m.isDebug() {
m.logger.Printf("[DEBUG] [stacks] StopStack %s: current state=%s deployed=%v containers=%d", name, stack.State, stack.Deployed, len(stack.Containers))
}
m.logger.Printf("[INFO] Stopping stack: %s", name)
start := time.Now()
dir := filepath.Dir(stack.ComposePath)
@@ -623,6 +685,10 @@ func (m *Manager) RestartStack(name string) error {
return fmt.Errorf("stack %q not found", name)
}
if m.isDebug() {
m.logger.Printf("[DEBUG] [stacks] RestartStack %s: current state=%s deployed=%v containers=%d", name, stack.State, stack.Deployed, len(stack.Containers))
}
m.logger.Printf("[INFO] Restarting stack: %s", name)
start := time.Now()
dir := filepath.Dir(stack.ComposePath)
@@ -997,5 +1063,8 @@ func (m *Manager) getCatalogTemplateSlugs() map[string]bool {
}
}
}
if m.isDebug() {
m.logger.Printf("[DEBUG] [stacks] getCatalogTemplateSlugs: found %d template slugs in %s", len(slugs), cacheDir)
}
return slugs
}