feat: comprehensive INFO/WARN/ERROR logging across all controller modules

Add structured operational logging at INFO, WARN, and ERROR levels to
every controller module. Standardize custom prefixes ([GEO], [SCHED],
[SYNC]) to use [INFO/WARN/ERROR] [module] format. Fix misleveled logs
(WARN->ERROR for data loss scenarios, WARN->INFO for routine operations).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-26 19:58:27 +01:00
parent 95c821deb2
commit 8e61cd7ec4
44 changed files with 326 additions and 44 deletions
+2
View File
@@ -4,6 +4,7 @@ import (
"bufio"
"context"
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
@@ -557,6 +558,7 @@ func ParseComposeHDDMounts(composePath, hddPath string) []string {
}
}
log.Printf("[INFO] [stacks] ParseComposeHDDMounts: found %d HDD mounts for %s", len(mounts), composePath)
return mounts
}
+10
View File
@@ -375,6 +375,7 @@ func (m *Manager) runComposeDeploy(name, stackDir string, env map[string]string,
// UpdateStackConfig updates non-locked fields for a deployed stack.
func (m *Manager) UpdateStackConfig(name string, values map[string]string) error {
m.logger.Printf("[INFO] [stacks] Updating config for stack %s", name)
if m.isDebug() {
m.logger.Printf("[DEBUG] [stacks] UpdateStackConfig called: name=%q, %d values to update", name, len(values))
}
@@ -457,6 +458,7 @@ func (m *Manager) GetDeployFields(name string) (*Metadata, *AppConfig, error) {
// UpdateOptionalConfig updates optional env vars in app.yaml and restarts the stack if deployed.
// Only updates env vars that are listed in the metadata's optional_config sections.
func (m *Manager) UpdateOptionalConfig(stackName string, values map[string]string) error {
m.logger.Printf("[INFO] [stacks] Updating optional config for stack %s", stackName)
if m.isDebug() {
m.logger.Printf("[DEBUG] [stacks] UpdateOptionalConfig called: stack=%q, %d values provided", stackName, len(values))
}
@@ -588,6 +590,7 @@ func LoadAppConfig(stackDir string) *AppConfig {
}
cfg := &AppConfig{}
if err := yaml.Unmarshal(data, cfg); err != nil {
log.Printf("[WARN] [stacks] LoadAppConfig: %v", err)
log.Printf("[DEBUG] [stacks] LoadAppConfig: failed to parse %s: %v", path, err)
return nil
}
@@ -626,6 +629,7 @@ func SaveAppConfig(stackDir string, cfg *AppConfig, encKey []byte, sensitiveVars
data, err := yaml.Marshal(saveCfg)
if err != nil {
log.Printf("[ERROR] [stacks] SaveAppConfig: failed to marshal config for %s: %v", stackDir, err)
return fmt.Errorf("marshaling app config: %w", err)
}
path := filepath.Join(stackDir, "app.yaml")
@@ -635,12 +639,15 @@ func SaveAppConfig(stackDir string, cfg *AppConfig, encKey []byte, sensitiveVars
// Atomic write: write to .tmp then rename (H04 fix)
tmpPath := path + ".tmp"
if err := os.WriteFile(tmpPath, []byte(content), 0600); err != nil {
log.Printf("[ERROR] [stacks] SaveAppConfig: failed to save %s: %v", path, err)
return fmt.Errorf("writing %s: %w", tmpPath, err)
}
if err := os.Rename(tmpPath, path); err != nil {
_ = os.Remove(tmpPath)
log.Printf("[ERROR] [stacks] SaveAppConfig: failed to save %s: %v", path, err)
return fmt.Errorf("renaming %s to %s: %w", tmpPath, path, err)
}
log.Printf("[INFO] [stacks] SaveAppConfig: saved config for %s", filepath.Base(stackDir))
return nil
}
@@ -724,11 +731,13 @@ func (m *Manager) InjectMissingFields(stackNames []string) {
m.logger.Printf("[DEBUG] [stacks] InjectMissingFields: checking %d stacks", len(stackNames))
}
count := 0
for _, name := range stackNames {
stack, ok := m.GetStack(name)
if !ok {
continue
}
count++
stackDir := filepath.Dir(stack.ComposePath)
meta := LoadMetadata(stackDir)
@@ -804,6 +813,7 @@ func (m *Manager) InjectMissingFields(stackNames []string) {
m.logger.Printf("[SYNC] Stack %s: injected missing fields: %s", name, strings.Join(injected, ", "))
}
}
m.logger.Printf("[INFO] [stacks] InjectMissingFields: processed %d stacks", count)
}
func containsStr(slice []string, s string) bool {
+3 -3
View File
@@ -126,9 +126,9 @@ func (m *Manager) RunHealthProbes() error {
// Summary log
if failCount > 0 {
m.logger.Printf("[INFO] Health probes: %d ok, %d unhealthy (of %d probed)", okCount, failCount, len(targets))
} else if m.isDebug() {
m.logger.Printf("[DEBUG] Health probes: %d ok (of %d probed)", okCount, len(targets))
m.logger.Printf("[WARN] Health probes: %d ok, %d unhealthy (of %d probed)", okCount, failCount, len(targets))
} else {
m.logger.Printf("[INFO] Health probes: %d ok (of %d probed)", okCount, len(targets))
}
return nil
+6
View File
@@ -184,6 +184,8 @@ func (m *Manager) MigrateEncryption() {
}
if migrated > 0 {
m.logger.Printf("[INFO] Encrypted sensitive values in %d app.yaml file(s)", migrated)
} else {
m.logger.Printf("[INFO] [stacks] Encryption migration: no stacks needed migration")
}
}
@@ -326,6 +328,7 @@ func (m *Manager) ScanStacks() error {
}
m.logger.Printf("[INFO] Scanned stacks: %d found (%d deployed, %d available)",
len(m.stacks), deployedCount, len(m.stacks)-deployedCount)
m.logger.Printf("[INFO] [stacks] ScanStacks complete: %d stacks found", len(m.stacks))
return m.refreshStatusLocked()
}
@@ -370,6 +373,8 @@ func (m *Manager) refreshStatusLocked() error {
m.logger.Printf("[DEBUG] [stacks] refreshStatusLocked: docker ps returned %d containers across %d projects", totalContainers, len(projectContainers))
}
m.logger.Printf("[INFO] [stacks] Status refresh: %d containers across %d stacks", totalContainers, len(m.stacks))
for name, stack := range m.stacks {
containers, exists := projectContainers[name]
if !exists {
@@ -759,6 +764,7 @@ func (m *Manager) GetLogs(name string, lines int) (string, error) {
lines = 1000
}
m.logger.Printf("[INFO] [stacks] Fetching logs for stack %s (tail=%d)", name, lines)
m.logger.Printf("[DEBUG] Fetching logs for %s (tail %d)", name, lines)
dir := filepath.Dir(stack.ComposePath)
+2 -2
View File
@@ -1,7 +1,7 @@
package stacks
import (
"fmt"
"log"
"os"
"path/filepath"
"strings"
@@ -126,7 +126,7 @@ func LoadMetadata(stackDir string) Metadata {
}
if err := yaml.Unmarshal(data, &meta); err != nil {
fmt.Fprintf(os.Stderr, "[ERROR] Failed to parse .felhom.yml in %s: %v\n", stackDir, err)
log.Printf("[ERROR] [stacks] Failed to parse .felhom.yml in %s: %v", stackDir, err)
dirName := filepath.Base(stackDir)
meta.DisplayName = toTitleCase(strings.ReplaceAll(dirName, "-", " "))
meta.Slug = dirName