diff --git a/CHANGELOG.md b/CHANGELOG.md index 22f7b91..0fa2f94 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,51 @@ ## Changelog +### v0.32.2 — Comprehensive INFO/WARN/ERROR logging across all modules (2026-02-26) + +#### Added +- **stacks/manager.go**: INFO logs for status refresh container/stack counts, log fetching, encryption migration, ScanStacks completion +- **stacks/deploy.go**: INFO logs for config updates, InjectMissingFields summary; ERROR logs for SaveAppConfig failures; WARN for LoadAppConfig errors +- **stacks/delete.go**: INFO log for ParseComposeHDDMounts result count +- **stacks/metadata.go**: Fixed LoadMetadata error to use `log.Printf` instead of `fmt.Fprintf(os.Stderr)` +- **backup/backup.go**: WARN for perDriveRepoStats failures; INFO for drive stats, aggregate stats, dump file count, snapshot history save +- **backup/crossdrive.go**: INFO for cross-drive backup start/completion with success/fail counts; ERROR for rsync failures; INFO for DB dump copy counts +- **backup/restic.go**: INFO for Snapshot and Check success +- **backup/dbdump.go**: INFO for DiscoverDatabases count; INFO for DumpAll start/completion +- **backup/restore_drives_linux.go**: INFO for fstab entry additions +- **backup/local_infra.go**: INFO for backup version pruning with kept/removed counts +- **cloudflare/geosync.go**: Standardized all `[GEO]` prefixed logs to `[INFO]/[WARN]/[ERROR] [cloudflare]` format +- **scheduler/scheduler.go**: Standardized all `[SCHED]` prefixed logs to `[INFO]/[WARN]/[ERROR] [scheduler]` format +- **sync/sync.go**: INFO for catalog sync start/completion; ERROR for git/network failures; WARN for file copy errors (replaced `[SYNC]` prefix) +- **report/pusher.go**: WARN for Push and InfraBackup push failures +- **report/builder.go**: INFO for BuildReport start +- **monitor/healthcheck.go**: WARN for CPU/memory/disk/temperature threshold breaches; INFO for health check result status +- **system/mounts_linux.go**: WARN for unsafe backup destinations and storage path probe failures +- **settings/settings.go**: INFO for settings load/save, storage path add/remove, disconnect/decommission, pending events; ERROR for save failures +- **storage/attach_linux.go**: INFO for disk attach start/success; ERROR for attach failures +- **storage/scan_linux.go**: INFO for disk scan start/completion with count +- **storage/format_linux.go**: INFO for format start/success; ERROR for format failures +- **storage/migrate.go**: INFO for migration start/completion; ERROR for migration failures +- **integrations/manager.go**: ERROR for integration apply failures; WARN for context build and env load failures +- **integrations/lifecycle.go**: Added `[integrations]` module tag to all logs; upgraded re-apply failure from WARN to ERROR +- **integrations/onlyoffice_filebrowser.go**: ERROR for all Apply/Revoke error paths +- **integrations/onlyoffice_nextcloud.go**: ERROR for all Apply/Revoke error paths +- **selfupdate/updater.go**: INFO for up-to-date and update-available results; INFO/ERROR for compose file updates +- **selfupdate/state.go**: INFO for state cleared +- **assets/syncer.go**: ERROR for manifest save failures (previously silent); changed sync failure log from WARN to ERROR +- **appexport/restore.go**: INFO for import start +- **web/auth.go**: INFO for logout/session invalidation/session cleanup; WARN for unauthorized API requests +- **web/server.go**: WARN for 404 Not Found on unknown routes +- **web/handlers.go**: INFO for default storage path and schedulable state changes +- **web/handler_restore.go**: INFO for restore-all initiation +- **web/handler_export.go**: ERROR for export/import start failures +- **web/storage_handlers.go**: INFO for disk disconnect/reconnect/restart-apps completion +- **api/router.go**: ERROR for stack action failures, backup snapshot listing failures, metrics query failures + +#### Changed +- **stacks/healthprobe.go**: Summary log now always prints — WARN when unhealthy, INFO when all ok (was debug-only for all-ok) +- **backup/restore.go**: Changed RestoreApp start log from `[WARN]` to `[INFO] [backup]` +- **backup/restore_app_linux.go**: Changed restoreUserData/restoreDBDumps failure logs from `[WARN]` to `[ERROR]` where data loss could occur + ### v0.32.1 — Comprehensive debug logging across all modules (2026-02-26) #### Added diff --git a/controller/README.md b/controller/README.md index f59644d..ec49603 100644 --- a/controller/README.md +++ b/controller/README.md @@ -1145,9 +1145,14 @@ When `logging.level: "debug"` is set in `controller.yaml`, the controller expose - **DebugCallbacks**: 7 closures wired from main.go for operations needing modules not on Server struct (hub push, infra backup, connectivity tests, telemetry preview). - **Telemetry debug**: `GetTelemetryPreview` callback calls `report.BuildAppTelemetryForDebug()` (exported wrapper around the private `buildAppTelemetrySection()`). Result renders as a table with collapsible raw JSON. Available regardless of hub configuration. -#### Per-Module Debug Logging +#### Per-Module Logging -When `logging.level: "debug"`, every module emits detailed `[DEBUG] [module]` prefixed log lines. Each module with stateful debug (struct-based) exposes a `SetDebug(bool)` method, wired from `main.go`. Modules without a struct use package-level `DebugLogger` variables (e.g., `system.DebugLogger`). +All modules emit structured log lines at `[INFO]`, `[WARN]`, and `[ERROR]` levels for operational events (state changes, completions, failures). When `logging.level: "debug"`, additional detailed `[DEBUG] [module]` prefixed log lines are emitted. Each module with stateful debug (struct-based) exposes a `SetDebug(bool)` method, wired from `main.go`. Modules without a struct use package-level `DebugLogger` variables (e.g., `system.DebugLogger`). + +**Standard-level logging** (always active): +- `[INFO]` — Operational events: stack deploy/start/stop, backup completion, config changes, disk operations, sync results +- `[WARN]` — Degraded states: health threshold breaches, unsafe backup destinations, retryable failures, best-effort operation failures +- `[ERROR]` — Hard failures: data restore errors, integration apply failures, compose file update errors, disk format failures | Module | Debug Field | Prefix | Key Areas | |--------|------------|--------|-----------| diff --git a/controller/internal/api/router.go b/controller/internal/api/router.go index 6088fb7..906e187 100644 --- a/controller/internal/api/router.go +++ b/controller/internal/api/router.go @@ -456,6 +456,7 @@ func (r *Router) actionStack(w http.ResponseWriter, action, name string) { } if err != nil { + r.logger.Printf("[ERROR] [api] %s failed for %s: %v", action, name, err) status := http.StatusInternalServerError if strings.Contains(err.Error(), "protected") { status = http.StatusForbidden @@ -796,6 +797,7 @@ func (r *Router) backupSnapshots(w http.ResponseWriter, req *http.Request) { snapshots, err := r.backupMgr.ListAllSnapshots(50) if err != nil { + r.logger.Printf("[ERROR] [api] Failed to list backup snapshots: %v", err) writeJSON(w, http.StatusInternalServerError, apiResponse{OK: false, Error: err.Error()}) return } @@ -833,6 +835,7 @@ func (r *Router) metricsSystem(w http.ResponseWriter, req *http.Request) { samples, err := r.metricsStore.QuerySystemMetrics(from, to, resolution) if err != nil { + r.logger.Printf("[ERROR] [api] Failed to query system metrics: %v", err) writeJSON(w, http.StatusInternalServerError, apiResponse{OK: false, Error: err.Error()}) return } @@ -869,6 +872,7 @@ func (r *Router) metricsContainerSummary(w http.ResponseWriter, _ *http.Request) summary, err := r.metricsStore.QueryContainerSummary() if err != nil { + r.logger.Printf("[ERROR] [api] Failed to query container summary: %v", err) writeJSON(w, http.StatusInternalServerError, apiResponse{OK: false, Error: err.Error()}) return } @@ -887,6 +891,7 @@ func (r *Router) metricsContainer(w http.ResponseWriter, req *http.Request, name samples, err := r.metricsStore.QueryContainerMetrics(name, from, to, resolution) if err != nil { + r.logger.Printf("[ERROR] [api] Failed to query container metrics for %s: %v", name, err) writeJSON(w, http.StatusInternalServerError, apiResponse{OK: false, Error: err.Error()}) return } diff --git a/controller/internal/appexport/restore.go b/controller/internal/appexport/restore.go index eb23853..4a5847d 100644 --- a/controller/internal/appexport/restore.go +++ b/controller/internal/appexport/restore.go @@ -218,6 +218,7 @@ func (e *Exporter) StartImport(req ImportRequest) error { func (e *Exporter) executeImport(req ImportRequest, job *Job) { importStart := time.Now() + e.logger.Printf("[INFO] [appexport] Import started for %s", filepath.Base(req.FABPath)) e.debugf("=== IMPORT START: path=%s encrypted=%v ===", req.FABPath, req.Password != "") defer func() { diff --git a/controller/internal/assets/syncer.go b/controller/internal/assets/syncer.go index 2d65f9b..03f92ed 100644 --- a/controller/internal/assets/syncer.go +++ b/controller/internal/assets/syncer.go @@ -218,7 +218,7 @@ func (s *Syncer) setError(err error) { TotalBytes: s.status.TotalBytes, } s.mu.Unlock() - s.logger.Printf("[WARN] Asset sync failed: %v", err) + s.logger.Printf("[ERROR] [assets] Asset sync failed: %v", err) } func (s *Syncer) fetchManifest(ctx context.Context) (*HubManifest, error) { @@ -316,14 +316,18 @@ func (s *Syncer) downloadFile(ctx context.Context, filename string) error { func (s *Syncer) saveLocalManifest(manifest *HubManifest) { data, err := json.MarshalIndent(manifest, "", " ") if err != nil { + s.logger.Printf("[ERROR] [assets] Failed to save local manifest: %v", err) return } path := filepath.Join(s.assetsDir, "manifest.json") tmp := path + ".tmp" if err := os.WriteFile(tmp, data, 0644); err != nil { + s.logger.Printf("[ERROR] [assets] Failed to save local manifest: %v", err) return } - os.Rename(tmp, path) + if err := os.Rename(tmp, path); err != nil { + s.logger.Printf("[ERROR] [assets] Failed to save local manifest: %v", err) + } } func fileSHA256(path string) (string, error) { diff --git a/controller/internal/backup/backup.go b/controller/internal/backup/backup.go index c1ce1f6..c226b11 100644 --- a/controller/internal/backup/backup.go +++ b/controller/internal/backup/backup.go @@ -856,6 +856,7 @@ func (m *Manager) perDriveRepoStats() []DriveRepoInfo { } stats, err := m.restic.Stats(repoPath) if err != nil { + m.logger.Printf("[WARN] [backup] perDriveRepoStats: failed to get stats for %s: %v", drive, err) continue } infos = append(infos, DriveRepoInfo{ @@ -866,6 +867,7 @@ func (m *Manager) perDriveRepoStats() []DriveRepoInfo { LatestSnapshot: stats.LatestSnapshot, }) } + m.logger.Printf("[INFO] [backup] perDriveRepoStats: collected stats for %d drives", len(infos)) return infos } @@ -918,6 +920,7 @@ func (m *Manager) aggregateRepoStats() *RepoStats { if totalBytes > 0 { agg.TotalSize = humanizeBytes(totalBytes) } + m.logger.Printf("[INFO] [backup] Aggregated repo stats: %d snapshots, total size %s", agg.SnapshotCount, agg.TotalSize) return agg } @@ -932,6 +935,7 @@ func (m *Manager) listAllDumpFiles() []DumpFileInfo { } } } + m.logger.Printf("[INFO] [backup] Found %d DB dump files across drives", len(allFiles)) return allFiles } @@ -979,7 +983,9 @@ func (m *Manager) saveSnapshotHistory() { } if err := os.Rename(tmp, m.snapshotHistoryFile); err != nil { m.logger.Printf("[WARN] Could not rename snapshot history file: %v", err) + return } + m.logger.Printf("[INFO] [backup] Saved snapshot history (%d entries)", len(m.snapshotHistory)) } // loadSnapshotHistoryFromFile reads the persisted snapshot history from disk. diff --git a/controller/internal/backup/crossdrive.go b/controller/internal/backup/crossdrive.go index 7036c22..34b5715 100644 --- a/controller/internal/backup/crossdrive.go +++ b/controller/internal/backup/crossdrive.go @@ -203,6 +203,7 @@ func (r *CrossDriveRunner) RunAllScheduled(ctx context.Context, schedule string) var errs []string var scheduled, skippedDisabled, skippedWrongSchedule int + r.logger.Printf("[INFO] [backup] Cross-drive backup: starting scheduled run for %d configured app(s), schedule=%s", len(configs), schedule) for stackName, cfg := range configs { if !cfg.Enabled { if r.debug { @@ -240,6 +241,8 @@ func (r *CrossDriveRunner) RunAllScheduled(ctx context.Context, schedule string) scheduled, skippedDisabled, skippedWrongSchedule, len(errs)) } + r.logger.Printf("[INFO] [backup] Cross-drive backup complete: %d succeeded, %d failed", scheduled-len(errs), len(errs)) + if len(errs) > 0 { return fmt.Errorf("cross-drive backup errors: %s", strings.Join(errs, "; ")) } @@ -263,6 +266,7 @@ func (r *CrossDriveRunner) RunAllConfigured(ctx context.Context) error { var errs []string var ran int + r.logger.Printf("[INFO] [backup] Cross-drive backup: starting all configured app(s), %d total", len(configs)) for stackName, cfg := range configs { if !cfg.Enabled { continue @@ -281,6 +285,7 @@ func (r *CrossDriveRunner) RunAllConfigured(ctx context.Context) error { if r.debug { r.logger.Printf("[DEBUG] RunAllConfigured: done — %d ran, %d errors", ran, len(errs)) } + r.logger.Printf("[INFO] [backup] Cross-drive backup complete: %d succeeded, %d failed", ran-len(errs), len(errs)) if len(errs) > 0 { return fmt.Errorf("cross-drive errors: %s", strings.Join(errs, "; ")) } @@ -418,6 +423,7 @@ func (r *CrossDriveRunner) runRsyncBackup(ctx context.Context, stackName, destBa if r.debug { r.logger.Printf("[DEBUG] runRsyncBackup: rsync failed for %s: %s", srcMount, util.TruncateStr(strings.TrimSpace(string(out)), 500)) } + r.logger.Printf("[ERROR] [backup] Rsync backup for %s failed: %v", stackName, err) return fmt.Errorf("rsync failed for %s: %v (%s)", srcMount, err, strings.TrimSpace(string(out))) } if r.debug { @@ -452,6 +458,7 @@ func (r *CrossDriveRunner) runRsyncBackup(ctx context.Context, stackName, destBa } } + r.logger.Printf("[INFO] [backup] Rsync backup for %s to %s complete", stackName, destDir) return nil } @@ -484,6 +491,7 @@ func (r *CrossDriveRunner) copyStackDBDumps(stackName, destDir string) error { if copied > 0 { r.logger.Printf("[DEBUG] Copied %d DB dump file(s) to %s", copied, destDir) } + r.logger.Printf("[INFO] [backup] Copied %d DB dumps for %s", copied, stackName) return nil } diff --git a/controller/internal/backup/dbdump.go b/controller/internal/backup/dbdump.go index 40a038f..7bfa0ae 100644 --- a/controller/internal/backup/dbdump.go +++ b/controller/internal/backup/dbdump.go @@ -135,6 +135,7 @@ func DiscoverDatabases(ctx context.Context, logger *log.Logger, debug bool) ([]D logger.Printf("[DEBUG] DiscoverDatabases: found %d database(s), skipped %d non-DB container(s)", len(dbs), skipped) } + logger.Printf("[INFO] [backup] Discovered %d databases", len(dbs)) return dbs, nil } @@ -143,11 +144,17 @@ func DumpAll(ctx context.Context, dbs []DiscoveredDB, dumpDir string, logger *lo // Clean up old .tmp files (older than 1 hour) cleanupTmpFiles(dumpDir, logger) + logger.Printf("[INFO] [backup] Starting DB dump for %d databases", len(dbs)) var results []DumpResult + var failed int for _, db := range dbs { result := DumpOne(ctx, db, dumpDir, logger, debug) results = append(results, result) + if result.Error != nil { + failed++ + } } + logger.Printf("[INFO] [backup] DB dump complete: %d succeeded, %d failed", len(results)-failed, failed) return results } diff --git a/controller/internal/backup/local_infra.go b/controller/internal/backup/local_infra.go index 04f6a46..ecf15d7 100644 --- a/controller/internal/backup/local_infra.go +++ b/controller/internal/backup/local_infra.go @@ -187,6 +187,9 @@ func pruneLocalHistory(histDir string, maxKeep int, logger *log.Logger) { logger.Printf("[DEBUG] Local infra history: pruned old version %s", ts) } } + if logger != nil && toDelete > 0 { + logger.Printf("[INFO] [backup] Pruning old backup versions: kept %d, removed %d", maxKeep, toDelete) + } } // sanitizeTimestamp converts an RFC3339 timestamp to a filename-safe format. diff --git a/controller/internal/backup/restic.go b/controller/internal/backup/restic.go index f82af96..b71d75f 100644 --- a/controller/internal/backup/restic.go +++ b/controller/internal/backup/restic.go @@ -207,6 +207,7 @@ func (r *ResticManager) Snapshot(repoPath string, paths []string, tags []string) result.Duration, result.SnapshotID, result.FilesNew, result.FilesChanged, result.DataAdded) } + r.logger.Printf("[INFO] [backup] Restic snapshot complete for %s", repoPath) return result, nil } @@ -264,6 +265,7 @@ func (r *ResticManager) Check(repoPath string) error { if r.debug { r.logger.Printf("[DEBUG] [restic] Check: repo=%s OK, completed in %s", repoPath, time.Since(start)) } + r.logger.Printf("[INFO] [backup] Restic check passed for repo %s", repoPath) return nil } diff --git a/controller/internal/backup/restore.go b/controller/internal/backup/restore.go index 257c827..d15d3ad 100644 --- a/controller/internal/backup/restore.go +++ b/controller/internal/backup/restore.go @@ -87,7 +87,7 @@ func (m *Manager) RestoreApp(stackName, snapshotID string) error { m.logger.Printf("[DEBUG] RestoreApp: using repo=%s, %d restore path(s)", repoPath, len(restorePaths)) } - m.logger.Printf("[WARN] RESTORE starting: stack=%s, snapshot=%s, repo=%s, paths=%v, hasHDD=%v", + m.logger.Printf("[INFO] [backup] Starting restore for %s (snapshot=%s, repo=%s, paths=%v, hasHDD=%v)", stackName, snapshotID, repoPath, restorePaths, hasHDD) // Stop the app before restore diff --git a/controller/internal/backup/restore_app_linux.go b/controller/internal/backup/restore_app_linux.go index 9432a0f..6e665cd 100644 --- a/controller/internal/backup/restore_app_linux.go +++ b/controller/internal/backup/restore_app_linux.go @@ -156,14 +156,14 @@ func restoreUserData(ctx context.Context, app *RestorableApp, logger *log.Logger if e.IsDir() { src = strings.TrimRight(src, "/") + "/" if err := os.MkdirAll(dst, 0755); err != nil { - logger.Printf("[WARN] Cannot create %s: %v", dst, err) + logger.Printf("[ERROR] [backup] Cannot create %s: %v", dst, err) continue } dst = strings.TrimRight(dst, "/") + "/" logger.Printf("[DEBUG] [backup] restoreUserData: %s — rsync dir %s → %s", app.Name, src, dst) cmd := exec.CommandContext(ctx, "rsync", "-a", src, dst) if out, err := cmd.CombinedOutput(); err != nil { - logger.Printf("[WARN] rsync data %s: %v (%s)", name, err, strings.TrimSpace(string(out))) + logger.Printf("[ERROR] [backup] rsync data %s: %v (%s)", name, err, strings.TrimSpace(string(out))) } else { restored++ } @@ -171,12 +171,12 @@ func restoreUserData(ctx context.Context, app *RestorableApp, logger *log.Logger // Single file — copy directly data, err := os.ReadFile(src) if err != nil { - logger.Printf("[WARN] Cannot read %s: %v", src, err) + logger.Printf("[ERROR] [backup] Cannot read %s: %v", src, err) continue } logger.Printf("[DEBUG] [backup] restoreUserData: %s — copying file %s (%d bytes)", app.Name, name, len(data)) if err := os.WriteFile(dst, data, 0644); err != nil { - logger.Printf("[WARN] Cannot write %s: %v", dst, err) + logger.Printf("[ERROR] [backup] Cannot write %s: %v", dst, err) } else { restored++ } @@ -224,12 +224,12 @@ func restoreDBDumps(app *RestorableApp, logger *log.Logger) error { dst := filepath.Join(destDir, e.Name()) data, err := os.ReadFile(src) if err != nil { - logger.Printf("[WARN] Cannot read dump %s: %v", e.Name(), err) + logger.Printf("[ERROR] [backup] Cannot read dump %s: %v", e.Name(), err) continue } logger.Printf("[DEBUG] [backup] restoreDBDumps: %s — copying %s (%d bytes)", app.Name, e.Name(), len(data)) if err := os.WriteFile(dst, data, 0644); err != nil { - logger.Printf("[WARN] Cannot write dump %s: %v", e.Name(), err) + logger.Printf("[ERROR] [backup] Cannot write dump %s: %v", e.Name(), err) } else { copied++ } diff --git a/controller/internal/backup/restore_drives_linux.go b/controller/internal/backup/restore_drives_linux.go index 68743e7..5249f33 100644 --- a/controller/internal/backup/restore_drives_linux.go +++ b/controller/internal/backup/restore_drives_linux.go @@ -239,6 +239,7 @@ func mountRawAndBind(ctx context.Context, device string, dm DiskMount, logger *l func addDRFstabEntries(dm DiskMount, logger *log.Logger) error { const fstabPath = "/host-fstab" + logger.Printf("[INFO] [backup] Adding fstab entries for disaster recovery (%s, UUID=%s)", dm.Label, dm.UUID) logger.Printf("[DEBUG] [backup] addDRFstabEntries: checking fstab for %s (UUID=%s)", dm.Label, dm.UUID) data, err := os.ReadFile(fstabPath) @@ -257,20 +258,24 @@ func addDRFstabEntries(dm DiskMount, logger *log.Logger) error { var additions strings.Builder additions.WriteString("\n# Restored by felhom-controller DR\n") + entryCount := 0 if dm.RawMount != "" { // Raw mount entry additions.WriteString(fmt.Sprintf("UUID=%s\t%s\t%s\t%s\t0 2\n", dm.UUID, dm.RawMount, dm.FSType, dm.FstabOptions)) + entryCount++ } if dm.BindSubdir != "" && dm.RawMount != "" { // Bind mount entry additions.WriteString(fmt.Sprintf("%s/%s\t%s\tnone\tbind,nofail\t0 0\n", dm.RawMount, dm.BindSubdir, dm.MountPoint)) + entryCount++ } else if dm.RawMount == "" { // Direct mount entry (no bind) additions.WriteString(fmt.Sprintf("UUID=%s\t%s\t%s\t%s\t0 2\n", dm.UUID, dm.MountPoint, dm.FSType, dm.FstabOptions)) + entryCount++ } newContent := content + additions.String() @@ -288,6 +293,7 @@ func addDRFstabEntries(dm DiskMount, logger *log.Logger) error { } } + logger.Printf("[INFO] [backup] Added %d fstab entries for %s", entryCount, dm.Label) return nil } diff --git a/controller/internal/cloudflare/geosync.go b/controller/internal/cloudflare/geosync.go index 91819cf..3799d3e 100644 --- a/controller/internal/cloudflare/geosync.go +++ b/controller/internal/cloudflare/geosync.go @@ -68,6 +68,8 @@ func (g *GeoSyncManager) Sync(ctx context.Context) error { g.mu.Unlock() }() + g.logger.Printf("[INFO] [cloudflare] Geo-restriction sync starting") + geo := g.settings.GetGeoRestriction() // If geo is nil or disabled, delete all felhom rules and return. @@ -75,7 +77,7 @@ func (g *GeoSyncManager) Sync(ctx context.Context) error { return g.deleteAllRules(ctx, geo) } - g.logger.Printf("[GEO] Starting sync for domain %s (%d allowed countries, %d app overrides)", + g.logger.Printf("[INFO] [cloudflare] Starting sync for domain %s (%d allowed countries, %d app overrides)", g.domain, len(geo.AllowedCountries), len(geo.AppOverrides)) // 1. Resolve zone ID (use cached value if available) @@ -156,7 +158,9 @@ func (g *GeoSyncManager) Sync(ctx context.Context) error { // 6. Save success state g.saveError(zoneID, rulesetID, "") - g.logger.Printf("[GEO] Sync completed successfully") + // Count rules for summary + finalRules, _ := g.client.GetFelhomRules(ctx, zoneID, rulesetID) + g.logger.Printf("[INFO] [cloudflare] Geo-restriction sync complete (%d active rules)", len(finalRules)) return nil } @@ -184,7 +188,7 @@ func (g *GeoSyncManager) deleteAllRules(ctx context.Context, geo *settings.GeoRe existing, err := g.client.GetFelhomRules(ctx, zoneID, rulesetID) if err != nil { - g.logger.Printf("[GEO] Warning: could not list rules for cleanup: %v", err) + g.logger.Printf("[WARN] [cloudflare] Could not list rules for cleanup: %v", err) return nil } @@ -195,14 +199,14 @@ func (g *GeoSyncManager) deleteAllRules(ctx context.Context, geo *settings.GeoRe deleted := 0 for _, r := range existing { if err := g.client.DeleteRule(ctx, zoneID, rulesetID, r.ID); err != nil { - g.logger.Printf("[GEO] Warning: could not delete rule %s: %v", r.ID, err) + g.logger.Printf("[ERROR] [cloudflare] Failed to delete WAF rule %s: %v", r.ID, err) } else { deleted++ } } if len(existing) > 0 { - g.logger.Printf("[GEO] Deleted %d felhom-geo rules (feature disabled)", len(existing)) + g.logger.Printf("[INFO] [cloudflare] Deleted %d felhom-geo rules (feature disabled)", len(existing)) if g.debug { g.logger.Printf("[DEBUG] [cloudflare] deleteAllRules: successfully deleted %d/%d rules", deleted, len(existing)) } @@ -296,6 +300,7 @@ func (g *GeoSyncManager) applyDiff(ctx context.Context, zoneID, rulesetID string if err := g.client.UpdateRule(ctx, zoneID, rulesetID, ex.ID, r); err != nil { return fmt.Errorf("update rule %q: %w", d.description, err) } + g.logger.Printf("[INFO] [cloudflare] Updated WAF rule %s", d.description) } else if g.debug { g.logger.Printf("[DEBUG] [cloudflare] applyDiff: rule %q unchanged, skipping", d.description) } @@ -308,6 +313,7 @@ func (g *GeoSyncManager) applyDiff(ctx context.Context, zoneID, rulesetID string if _, err := g.client.CreateRule(ctx, zoneID, rulesetID, r); err != nil { return fmt.Errorf("create rule %q: %w", d.description, err) } + g.logger.Printf("[INFO] [cloudflare] Created WAF rule %s", d.description) } } @@ -320,6 +326,7 @@ func (g *GeoSyncManager) applyDiff(ctx context.Context, zoneID, rulesetID string if err := g.client.DeleteRule(ctx, zoneID, rulesetID, ex.ID); err != nil { return fmt.Errorf("delete rule %q: %w", ex.Description, err) } + g.logger.Printf("[INFO] [cloudflare] Deleted WAF rule %s", ex.Description) } } @@ -328,7 +335,10 @@ func (g *GeoSyncManager) applyDiff(ctx context.Context, zoneID, rulesetID string // saveError updates the sync state in settings. func (g *GeoSyncManager) saveError(zoneID, rulesetID, errMsg string) { + if errMsg != "" { + g.logger.Printf("[ERROR] [cloudflare] Geo-sync error: %v", errMsg) + } if err := g.settings.SetGeoSyncState(zoneID, rulesetID, errMsg); err != nil { - g.logger.Printf("[GEO] Warning: failed to save sync state: %v", err) + g.logger.Printf("[WARN] [cloudflare] Failed to save sync state: %v", err) } } diff --git a/controller/internal/cloudflare/waf.go b/controller/internal/cloudflare/waf.go index e2ddac5..392c1dd 100644 --- a/controller/internal/cloudflare/waf.go +++ b/controller/internal/cloudflare/waf.go @@ -63,6 +63,7 @@ func (c *Client) GetCustomRulesetID(ctx context.Context, zoneID string) (string, path := fmt.Sprintf("/zones/%s/rulesets", zoneID) resp, err := c.do(ctx, "GET", path, nil) if err != nil { + c.logger.Printf("[ERROR] [cloudflare] Failed to get custom ruleset: %v", err) return "", fmt.Errorf("list rulesets: %w", err) } @@ -120,6 +121,7 @@ func (c *Client) GetRules(ctx context.Context, zoneID, rulesetID string) ([]rule path := fmt.Sprintf("/zones/%s/rulesets/%s", zoneID, rulesetID) resp, err := c.do(ctx, "GET", path, nil) if err != nil { + c.logger.Printf("[WARN] [cloudflare] Failed to get WAF rules: %v", err) return nil, fmt.Errorf("get ruleset: %w", err) } @@ -165,9 +167,11 @@ func (c *Client) GetFelhomRules(ctx context.Context, zoneID, rulesetID string) ( // CreateRule adds a new rule to the ruleset. func (c *Client) CreateRule(ctx context.Context, zoneID, rulesetID string, r rule) (string, error) { + c.logger.Printf("[INFO] [cloudflare] Creating WAF rule: %s", r.Description) path := fmt.Sprintf("/zones/%s/rulesets/%s/rules", zoneID, rulesetID) resp, err := c.do(ctx, "POST", path, r) if err != nil { + c.logger.Printf("[ERROR] [cloudflare] Failed to create WAF rule: %v", err) return "", fmt.Errorf("create rule: %w", err) } @@ -198,9 +202,11 @@ func (c *Client) CreateRule(ctx context.Context, zoneID, rulesetID string, r rul // UpdateRule updates an existing rule in the ruleset. func (c *Client) UpdateRule(ctx context.Context, zoneID, rulesetID, ruleID string, r rule) error { + c.logger.Printf("[INFO] [cloudflare] Updating WAF rule %s", ruleID) path := fmt.Sprintf("/zones/%s/rulesets/%s/rules/%s", zoneID, rulesetID, ruleID) _, err := c.do(ctx, "PATCH", path, r) if err != nil { + c.logger.Printf("[ERROR] [cloudflare] Failed to update WAF rule %s: %v", ruleID, err) return fmt.Errorf("update rule %s: %w", ruleID, err) } c.logger.Printf("[CF] Updated rule %q (%s)", r.Description, ruleID) @@ -216,9 +222,11 @@ func (c *Client) UpdateRule(ctx context.Context, zoneID, rulesetID, ruleID strin // DeleteRule removes a rule from the ruleset. func (c *Client) DeleteRule(ctx context.Context, zoneID, rulesetID, ruleID string) error { + c.logger.Printf("[INFO] [cloudflare] Deleting WAF rule %s", ruleID) path := fmt.Sprintf("/zones/%s/rulesets/%s/rules/%s", zoneID, rulesetID, ruleID) _, err := c.do(ctx, "DELETE", path, nil) if err != nil { + c.logger.Printf("[ERROR] [cloudflare] Failed to delete WAF rule %s: %v", ruleID, err) return fmt.Errorf("delete rule %s: %w", ruleID, err) } c.logger.Printf("[CF] Deleted rule %s", ruleID) diff --git a/controller/internal/cloudflare/zone.go b/controller/internal/cloudflare/zone.go index 75851f2..0bb9c84 100644 --- a/controller/internal/cloudflare/zone.go +++ b/controller/internal/cloudflare/zone.go @@ -52,6 +52,7 @@ func (c *Client) GetZoneID(ctx context.Context, domain string) (string, error) { } } + c.logger.Printf("[WARN] [cloudflare] No zone found for domain %s", domain) return "", fmt.Errorf("no Cloudflare zone found for domain %q", domain) } diff --git a/controller/internal/integrations/lifecycle.go b/controller/internal/integrations/lifecycle.go index 71e4368..af0e254 100644 --- a/controller/internal/integrations/lifecycle.go +++ b/controller/internal/integrations/lifecycle.go @@ -41,7 +41,7 @@ func (m *Manager) OnStackStop(_ context.Context, stackName string) { ac, err := m.buildApplyContext(provider, target) if err != nil { - m.logger.Printf("[WARN] Cannot build context for integration %s revoke: %v", key, err) + m.logger.Printf("[WARN] [integrations] Cannot build context for integration %s revoke: %v", key, err) continue } @@ -49,7 +49,7 @@ func (m *Manager) OnStackStop(_ context.Context, stackName string) { m.logger.Printf("[DEBUG] [integrations] OnStackStop: revoking %s", key) } if err := handler.Revoke(ac); err != nil { - m.logger.Printf("[WARN] Integration revoke on stop failed for %s: %v", key, err) + m.logger.Printf("[WARN] [integrations] Integration revoke on stop failed for %s: %v", key, err) } if provider == stackName { @@ -58,7 +58,7 @@ func (m *Manager) OnStackStop(_ context.Context, stackName string) { state.Status = "target_unavailable" } _ = m.sett.SetIntegrationState(key, state) - m.logger.Printf("[INFO] Integration %s suspended (stack %s stopped)", key, stackName) + m.logger.Printf("[INFO] [integrations] Integration %s suspended (stack %s stopped)", key, stackName) } } @@ -126,12 +126,12 @@ func (m *Manager) OnStackStart(_ context.Context, stackName string) { ac, err := m.buildApplyContext(provider, target) if err != nil { - m.logger.Printf("[WARN] Cannot re-apply integration %s on start: %v", key, err) + m.logger.Printf("[WARN] [integrations] Cannot re-apply integration %s on start: %v", key, err) continue } if err := handler.Apply(ac); err != nil { - m.logger.Printf("[WARN] Integration re-apply on start failed for %s: %v", key, err) + m.logger.Printf("[ERROR] [integrations] Integration re-apply on start failed for %s: %v", key, err) state.Status = "error" state.LastError = err.Error() _ = m.sett.SetIntegrationState(key, state) @@ -141,7 +141,7 @@ func (m *Manager) OnStackStart(_ context.Context, stackName string) { state.Status = "active" state.LastError = "" _ = m.sett.SetIntegrationState(key, state) - m.logger.Printf("[INFO] Integration %s re-activated (stack %s started)", key, stackName) + m.logger.Printf("[INFO] [integrations] Integration %s re-activated (stack %s started)", key, stackName) } } @@ -186,6 +186,6 @@ func (m *Manager) OnStackRemove(_ context.Context, stackName string) { } _ = m.sett.RemoveIntegrationState(key) - m.logger.Printf("[INFO] Integration %s removed (stack %s removed)", key, stackName) + m.logger.Printf("[INFO] [integrations] Integration %s removed (stack %s removed)", key, stackName) } } diff --git a/controller/internal/integrations/manager.go b/controller/internal/integrations/manager.go index 5e1fcc3..20d58fe 100644 --- a/controller/internal/integrations/manager.go +++ b/controller/internal/integrations/manager.go @@ -125,6 +125,7 @@ func (m *Manager) Toggle(ctx context.Context, provider, target string, enable bo start := time.Now() if err := handler.Apply(ac); err != nil { + m.logger.Printf("[ERROR] [integrations] Integration %s apply failed: %v", key, err) if m.isDebug() { m.logger.Printf("[DEBUG] [integrations] Toggle: Apply failed for %s in %v: %v", key, time.Since(start), err) } @@ -210,6 +211,7 @@ func (m *Manager) ListForProvider(providerSlug string) []StatusInfo { func (m *Manager) buildApplyContext(provider, target string) (*ApplyContext, error) { provStack, ok := m.stacks.GetStack(provider) if !ok { + m.logger.Printf("[WARN] [integrations] Failed to build context for %s:%s: provider stack not found", provider, target) return nil, fmt.Errorf("szolgáltató stack %q nem található", provider) } @@ -299,6 +301,7 @@ func (m *Manager) loadDecryptedEnv(s *stacks.Stack) map[string]string { stackDir := filepath.Dir(s.ComposePath) cfg := stacks.LoadAppConfig(stackDir) if cfg == nil { + m.logger.Printf("[WARN] [integrations] Failed to load env for %s: app config is nil", s.Name) return nil } if m.encKey != nil { diff --git a/controller/internal/integrations/onlyoffice_filebrowser.go b/controller/internal/integrations/onlyoffice_filebrowser.go index 0139e88..3e52dc2 100644 --- a/controller/internal/integrations/onlyoffice_filebrowser.go +++ b/controller/internal/integrations/onlyoffice_filebrowser.go @@ -13,6 +13,7 @@ type OnlyOfficeFileBrowserHandler struct{} func (h *OnlyOfficeFileBrowserHandler) Apply(ac *ApplyContext) error { jwtSecret := ac.ProviderEnv["JWT_SECRET"] if jwtSecret == "" { + ac.Logger.Printf("[ERROR] [integrations] OnlyOffice-FileBrowser apply: JWT_SECRET not set") return fmt.Errorf("OnlyOffice JWT_SECRET nincs beállítva — telepítsd újra az alkalmazást") } @@ -21,6 +22,7 @@ func (h *OnlyOfficeFileBrowserHandler) Apply(ac *ApplyContext) error { subdomain = ac.ProviderMeta.Subdomain } if subdomain == "" { + ac.Logger.Printf("[ERROR] [integrations] OnlyOffice-FileBrowser apply: subdomain unknown") return fmt.Errorf("OnlyOffice aldomain nem ismert") } @@ -31,6 +33,7 @@ func (h *OnlyOfficeFileBrowserHandler) Apply(ac *ApplyContext) error { configData, err := os.ReadFile(configPath) if err != nil { + ac.Logger.Printf("[ERROR] [integrations] OnlyOffice-FileBrowser apply: %v", err) return fmt.Errorf("FileBrowser config olvasási hiba: %w", err) } @@ -47,10 +50,12 @@ func (h *OnlyOfficeFileBrowserHandler) Apply(ac *ApplyContext) error { // Atomic write tmpPath := configPath + ".tmp" if err := os.WriteFile(tmpPath, []byte(configStr), 0644); err != nil { + ac.Logger.Printf("[ERROR] [integrations] OnlyOffice-FileBrowser apply: %v", err) return fmt.Errorf("config írási hiba: %w", err) } if err := os.Rename(tmpPath, configPath); err != nil { _ = os.Remove(tmpPath) + ac.Logger.Printf("[ERROR] [integrations] OnlyOffice-FileBrowser apply: %v", err) return fmt.Errorf("config átnevezési hiba: %w", err) } @@ -67,6 +72,7 @@ func (h *OnlyOfficeFileBrowserHandler) Revoke(ac *ApplyContext) error { if os.IsNotExist(err) { return nil } + ac.Logger.Printf("[ERROR] [integrations] OnlyOffice-FileBrowser revoke: %v", err) return fmt.Errorf("config olvasási hiba: %w", err) } @@ -79,10 +85,12 @@ func (h *OnlyOfficeFileBrowserHandler) Revoke(ac *ApplyContext) error { tmpPath := configPath + ".tmp" if err := os.WriteFile(tmpPath, []byte(cleaned), 0644); err != nil { + ac.Logger.Printf("[ERROR] [integrations] OnlyOffice-FileBrowser revoke: %v", err) return fmt.Errorf("config írási hiba: %w", err) } if err := os.Rename(tmpPath, configPath); err != nil { _ = os.Remove(tmpPath) + ac.Logger.Printf("[ERROR] [integrations] OnlyOffice-FileBrowser revoke: %v", err) return fmt.Errorf("config átnevezési hiba: %w", err) } diff --git a/controller/internal/integrations/onlyoffice_nextcloud.go b/controller/internal/integrations/onlyoffice_nextcloud.go index 6d9445b..5c90922 100644 --- a/controller/internal/integrations/onlyoffice_nextcloud.go +++ b/controller/internal/integrations/onlyoffice_nextcloud.go @@ -14,6 +14,7 @@ type OnlyOfficeNextcloudHandler struct{} func (h *OnlyOfficeNextcloudHandler) Apply(ac *ApplyContext) error { jwtSecret := ac.ProviderEnv["JWT_SECRET"] if jwtSecret == "" { + ac.Logger.Printf("[ERROR] [integrations] OnlyOffice-Nextcloud apply: JWT_SECRET not set") return fmt.Errorf("OnlyOffice JWT_SECRET nincs beállítva") } @@ -22,6 +23,7 @@ func (h *OnlyOfficeNextcloudHandler) Apply(ac *ApplyContext) error { subdomain = ac.ProviderMeta.Subdomain } if subdomain == "" { + ac.Logger.Printf("[ERROR] [integrations] OnlyOffice-Nextcloud apply: subdomain unknown") return fmt.Errorf("OnlyOffice aldomain nem ismert") } @@ -72,6 +74,7 @@ func (h *OnlyOfficeNextcloudHandler) Apply(ac *ApplyContext) error { ac.Logger.Printf("[DEBUG] Nextcloud occ: tolerated — %s", strings.TrimSpace(string(out))) continue } + ac.Logger.Printf("[ERROR] [integrations] OnlyOffice-Nextcloud apply: occ %s failed: %v", cmd.args[len(cmd.args)-1], err) return fmt.Errorf("occ parancs sikertelen (%s): %v (kimenet: %s)", cmd.args[len(cmd.args)-1], err, strings.TrimSpace(string(out))) } ac.Logger.Printf("[DEBUG] Nextcloud occ %s: ok", strings.Join(cmd.args[7:], " ")) @@ -96,6 +99,7 @@ func (h *OnlyOfficeNextcloudHandler) Revoke(ac *ApplyContext) error { ac.Logger.Printf("[DEBUG] Nextcloud occ app:disable skipped — %s", strings.TrimSpace(outStr)) return nil } + ac.Logger.Printf("[ERROR] [integrations] OnlyOffice-Nextcloud revoke: occ app:disable failed: %v", err) return fmt.Errorf("occ app:disable sikertelen: %v (kimenet: %s)", err, strings.TrimSpace(outStr)) } diff --git a/controller/internal/monitor/healthcheck.go b/controller/internal/monitor/healthcheck.go index a843eaa..ec2d325 100644 --- a/controller/internal/monitor/healthcheck.go +++ b/controller/internal/monitor/healthcheck.go @@ -48,11 +48,17 @@ func RunHealthCheck(cfg *config.Config, cpuCollector *system.CPUCollector, stora if sysInfo.DiskPercent > 0 { if sysInfo.DiskPercent >= float64(cfg.Monitoring.Thresholds.DiskCritPercent) { report.Issues = append(report.Issues, fmt.Sprintf("SSD disk usage critical: %.0f%%", sysInfo.DiskPercent)) + if logger != nil { + logger.Printf("[WARN] [monitor] Disk (SSD) threshold breached: %.0f%% (limit: %d%%)", sysInfo.DiskPercent, cfg.Monitoring.Thresholds.DiskCritPercent) + } if debug { logger.Printf("[DEBUG] [HEALTH] SSD disk: CRITICAL (%.0f%% >= %d%%)", sysInfo.DiskPercent, cfg.Monitoring.Thresholds.DiskCritPercent) } } else if sysInfo.DiskPercent >= float64(cfg.Monitoring.Thresholds.DiskWarnPercent) { report.Warnings = append(report.Warnings, fmt.Sprintf("SSD disk usage high: %.0f%%", sysInfo.DiskPercent)) + if logger != nil { + logger.Printf("[WARN] [monitor] Disk (SSD) threshold breached: %.0f%% (limit: %d%%)", sysInfo.DiskPercent, cfg.Monitoring.Thresholds.DiskWarnPercent) + } if debug { logger.Printf("[DEBUG] [HEALTH] SSD disk: WARN (%.0f%% >= %d%%)", sysInfo.DiskPercent, cfg.Monitoring.Thresholds.DiskWarnPercent) } @@ -68,8 +74,14 @@ func RunHealthCheck(cfg *config.Config, cpuCollector *system.CPUCollector, stora if sysInfo.HDDConfigured && sysInfo.HDDPercent > 0 { if sysInfo.HDDPercent >= float64(cfg.Monitoring.Thresholds.DiskCritPercent) { report.Issues = append(report.Issues, fmt.Sprintf("HDD disk usage critical: %.0f%%", sysInfo.HDDPercent)) + if logger != nil { + logger.Printf("[WARN] [monitor] Disk (HDD) threshold breached: %.0f%% (limit: %d%%)", sysInfo.HDDPercent, cfg.Monitoring.Thresholds.DiskCritPercent) + } } else if sysInfo.HDDPercent >= float64(cfg.Monitoring.Thresholds.DiskWarnPercent) { report.Warnings = append(report.Warnings, fmt.Sprintf("HDD disk usage high: %.0f%%", sysInfo.HDDPercent)) + if logger != nil { + logger.Printf("[WARN] [monitor] Disk (HDD) threshold breached: %.0f%% (limit: %d%%)", sysInfo.HDDPercent, cfg.Monitoring.Thresholds.DiskWarnPercent) + } } } @@ -77,6 +89,9 @@ func RunHealthCheck(cfg *config.Config, cpuCollector *system.CPUCollector, stora if sysInfo.MemPercent > 0 { if sysInfo.MemPercent >= float64(cfg.Monitoring.Thresholds.MemoryWarnPercent) { report.Warnings = append(report.Warnings, fmt.Sprintf("Memory usage high: %.0f%%", sysInfo.MemPercent)) + if logger != nil { + logger.Printf("[WARN] [monitor] Memory threshold breached: %.0f%% (limit: %d%%)", sysInfo.MemPercent, cfg.Monitoring.Thresholds.MemoryWarnPercent) + } if debug { logger.Printf("[DEBUG] [HEALTH] Memory: WARN (%.0f%% >= %d%%)", sysInfo.MemPercent, cfg.Monitoring.Thresholds.MemoryWarnPercent) } @@ -92,6 +107,9 @@ func RunHealthCheck(cfg *config.Config, cpuCollector *system.CPUCollector, stora if sysInfo.CPUPercent > 0 { if sysInfo.CPUPercent >= float64(cfg.Monitoring.Thresholds.CPUWarnPercent) { report.Warnings = append(report.Warnings, fmt.Sprintf("CPU usage high: %.0f%%", sysInfo.CPUPercent)) + if logger != nil { + logger.Printf("[WARN] [monitor] CPU threshold breached: %.0f%% (limit: %d%%)", sysInfo.CPUPercent, cfg.Monitoring.Thresholds.CPUWarnPercent) + } if debug { logger.Printf("[DEBUG] [HEALTH] CPU: WARN (%.0f%% >= %d%%)", sysInfo.CPUPercent, cfg.Monitoring.Thresholds.CPUWarnPercent) } @@ -107,6 +125,9 @@ func RunHealthCheck(cfg *config.Config, cpuCollector *system.CPUCollector, stora if sysInfo.TemperatureCelsius > 0 { if sysInfo.TemperatureCelsius >= float64(cfg.Monitoring.Thresholds.TemperatureWarnCelsius) { report.Warnings = append(report.Warnings, fmt.Sprintf("Temperature high: %.0f°C (%s)", sysInfo.TemperatureCelsius, sysInfo.TemperatureSource)) + if logger != nil { + logger.Printf("[WARN] [monitor] Temperature threshold breached: %.0f°C (limit: %d°C)", sysInfo.TemperatureCelsius, cfg.Monitoring.Thresholds.TemperatureWarnCelsius) + } if debug { logger.Printf("[DEBUG] [HEALTH] Temperature: WARN (%.0f°C >= %d°C)", sysInfo.TemperatureCelsius, cfg.Monitoring.Thresholds.TemperatureWarnCelsius) } @@ -159,6 +180,10 @@ func RunHealthCheck(cfg *config.Config, cpuCollector *system.CPUCollector, stora report.Status = "warn" } + if logger != nil { + logger.Printf("[INFO] [monitor] Health check: status=%s", report.Status) + } + if debug { logger.Printf("[DEBUG] [HEALTH] Final status: %s (issues=%d, warnings=%d, info=%d)", report.Status, len(report.Issues), len(report.Warnings), len(report.Info)) diff --git a/controller/internal/report/builder.go b/controller/internal/report/builder.go index 0cb94c0..c04cb8b 100644 --- a/controller/internal/report/builder.go +++ b/controller/internal/report/builder.go @@ -34,6 +34,9 @@ func BuildReport( logger *log.Logger, ) *Report { debug := cfg.Logging.Level == "debug" + if logger != nil { + logger.Printf("[INFO] [report] Building system report") + } if debug && logger != nil { logger.Printf("[DEBUG] BuildReport: starting — version=%s, storagePaths=%d", version, len(storagePaths)) } diff --git a/controller/internal/report/pusher.go b/controller/internal/report/pusher.go index b424a6f..ebf0b36 100644 --- a/controller/internal/report/pusher.go +++ b/controller/internal/report/pusher.go @@ -125,6 +125,8 @@ func (p *Pusher) Push(report *Report) error { lastErr = fmt.Errorf("HTTP %d", resp.StatusCode) } + p.logger.Printf("[WARN] [report] Push failed: %v", lastErr) + p.statusMu.Lock() p.status.LastError = lastErr.Error() p.status.Consecutive++ @@ -186,6 +188,7 @@ func (p *Pusher) PushInfraBackup(data []byte) error { } } + p.logger.Printf("[WARN] [report] InfraBackup push failed: %v", lastErr) return fmt.Errorf("infra backup push failed after 3 attempts: %w", lastErr) } diff --git a/controller/internal/scheduler/scheduler.go b/controller/internal/scheduler/scheduler.go index 1ee2882..7c9a4c2 100644 --- a/controller/internal/scheduler/scheduler.go +++ b/controller/internal/scheduler/scheduler.go @@ -75,7 +75,7 @@ func New(logger *log.Logger) *Scheduler { // If the scheduler is already started, the job's goroutine is launched immediately. func (s *Scheduler) Every(name string, interval time.Duration, fn JobFunc) { if interval <= 0 { - s.logger.Printf("[ERROR] Periodic job %s has invalid interval %s — job not registered", name, interval) + s.logger.Printf("[ERROR] [scheduler] Periodic job %s has invalid interval %s — job not registered", name, interval) return } @@ -88,7 +88,7 @@ func (s *Scheduler) Every(name string, interval time.Duration, fn JobFunc) { Interval: interval, } s.jobs = append(s.jobs, job) - s.logger.Printf("[SCHED] Registered periodic job: %s (every %s)", name, interval) + s.logger.Printf("[INFO] [scheduler] Registered periodic job: %s (every %s)", name, interval) s.dbg("periodic job registered: name=%q interval=%s totalJobs=%d", name, interval, len(s.jobs)) if s.started { @@ -105,7 +105,7 @@ func (s *Scheduler) Daily(name string, timeStr string, fn JobFunc) { // Validate time format if _, _, err := parseDailyTime(timeStr); err != nil { - s.logger.Printf("[ERROR] Daily job %s has invalid schedule %q: %v — job not started", name, timeStr, err) + s.logger.Printf("[ERROR] [scheduler] Daily job %s has invalid schedule %q: %v — job not started", name, timeStr, err) return } @@ -117,7 +117,7 @@ func (s *Scheduler) Daily(name string, timeStr string, fn JobFunc) { s.jobs = append(s.jobs, job) nextRun := nextDailyRun(timeStr) - s.logger.Printf("[SCHED] Daily job %s scheduled for %s", name, nextRun.Format("2006-01-02 15:04 MST")) + s.logger.Printf("[INFO] [scheduler] Daily job %s scheduled for %s", name, nextRun.Format("2006-01-02 15:04 MST")) s.dbg("daily job registered: name=%q schedule=%q nextRun=%s totalJobs=%d", name, timeStr, nextRun.Format(time.RFC3339), len(s.jobs)) if s.started { @@ -131,7 +131,7 @@ func (s *Scheduler) Start(ctx context.Context) { s.mu.Lock() if s.cancel != nil { s.mu.Unlock() - s.logger.Println("[WARN] Scheduler already started — ignoring duplicate Start()") + s.logger.Println("[WARN] [scheduler] Scheduler already started — ignoring duplicate Start()") return } s.ctx, s.cancel = context.WithCancel(ctx) @@ -147,7 +147,7 @@ func (s *Scheduler) Start(ctx context.Context) { } } - s.logger.Printf("[SCHED] Scheduler started with %d jobs", len(s.jobs)) + s.logger.Printf("[INFO] [scheduler] Starting scheduler with %d jobs", len(s.jobs)) s.dbg("scheduler started: periodic=%d daily=%d", func() int { n := 0 for _, j := range s.jobs { @@ -173,6 +173,7 @@ func (s *Scheduler) Stop() { s.mu.Lock() cancel := s.cancel s.mu.Unlock() + s.logger.Printf("[INFO] [scheduler] Stopping scheduler") if cancel != nil { cancel() } @@ -185,9 +186,9 @@ func (s *Scheduler) Stop() { select { case <-done: - s.logger.Println("[SCHED] All jobs stopped") + s.logger.Println("[INFO] [scheduler] All jobs stopped") case <-time.After(30 * time.Second): - s.logger.Println("[WARN] Scheduler stop timed out after 30s — some jobs may still be running") + s.logger.Println("[WARN] [scheduler] Scheduler stop timed out after 30s — some jobs may still be running") } } @@ -251,7 +252,7 @@ func (s *Scheduler) executeJob(job *Job, quiet bool) { s.mu.Lock() if job.Running { s.mu.Unlock() - s.logger.Printf("[WARN] Job %s still running, skipping", job.Name) + s.logger.Printf("[WARN] [scheduler] Job %s still running, skipping", job.Name) return } job.Running = true @@ -270,12 +271,12 @@ func (s *Scheduler) executeJob(job *Job, quiet bool) { job.LastErr = fmt.Errorf("panic: %v", r) job.LastRun = time.Now() s.mu.Unlock() - s.logger.Printf("[ERROR] Job %s panicked: %v", job.Name, r) + s.logger.Printf("[ERROR] [scheduler] Job %s panicked: %v", job.Name, r) } }() if !quiet { - s.logger.Printf("[SCHED] Running job: %s", job.Name) + s.logger.Printf("[INFO] [scheduler] Running job: %s", job.Name) } s.dbg("job %s: execution starting", job.Name) @@ -289,10 +290,10 @@ func (s *Scheduler) executeJob(job *Job, quiet bool) { s.mu.Unlock() if err != nil { - s.logger.Printf("[WARN] Job %s failed: %v (took %s)", job.Name, err, elapsed.Round(time.Millisecond)) + s.logger.Printf("[ERROR] [scheduler] Job %s failed: %v (took %s)", job.Name, err, elapsed.Round(time.Millisecond)) s.dbg("job %s: failed after %s: %v", job.Name, elapsed.Round(time.Millisecond), err) } else if !quiet { - s.logger.Printf("[SCHED] Job %s completed (took %s)", job.Name, elapsed.Round(time.Millisecond)) + s.logger.Printf("[INFO] [scheduler] Job %s completed (took %s)", job.Name, elapsed.Round(time.Millisecond)) } s.dbg("job %s: finished in %s (err=%v)", job.Name, elapsed.Round(time.Millisecond), err) } diff --git a/controller/internal/selfupdate/state.go b/controller/internal/selfupdate/state.go index a2efbf1..84bfa9f 100644 --- a/controller/internal/selfupdate/state.go +++ b/controller/internal/selfupdate/state.go @@ -67,5 +67,7 @@ func ClearState(dataDir string, logger *log.Logger) { path := filepath.Join(dataDir, stateFileName) if err := os.Remove(path); err != nil && !os.IsNotExist(err) { logger.Printf("[WARN] Failed to clear update state file: %v", err) + return } + logger.Printf("[INFO] [selfupdate] Update state cleared") } diff --git a/controller/internal/selfupdate/updater.go b/controller/internal/selfupdate/updater.go index 4c45078..6f6b0fe 100644 --- a/controller/internal/selfupdate/updater.go +++ b/controller/internal/selfupdate/updater.go @@ -144,6 +144,9 @@ func (u *Updater) CheckForUpdate() CheckResult { cmp := latestVer.Compare(currentVer) if cmp > 0 { result.UpdateAvailable = true + u.logger.Printf("[INFO] [selfupdate] Update available: %s → %s", u.currentVer, latestStr) + } else { + u.logger.Printf("[INFO] [selfupdate] Current version %s is up to date", u.currentVer) } u.dbg("version comparison: current=%s (%d.%d.%d), latest=%s (%d.%d.%d), cmp=%d, updateAvailable=%v", @@ -422,8 +425,10 @@ func (u *Updater) performUpdate(targetVersion, targetImage, previousImage, initi // updateComposeFile reads the compose file, replaces the image tag, and writes it back atomically. func (u *Updater) updateComposeFile(newImage string) error { + u.logger.Printf("[INFO] [selfupdate] Updating compose file") data, err := os.ReadFile(u.composePath) if err != nil { + u.logger.Printf("[ERROR] [selfupdate] Failed to update compose file: %v", err) return fmt.Errorf("reading compose file: %w", err) } @@ -441,15 +446,18 @@ func (u *Updater) updateComposeFile(newImage string) error { newData := re.ReplaceAll(data, []byte("${1}"+newImage)) if bytes.Equal(data, newData) { + u.logger.Printf("[ERROR] [selfupdate] Failed to update compose file: no image line found to replace") return fmt.Errorf("no image line found to replace in compose file") } // Atomic write: write to .tmp, then rename tmpPath := u.composePath + ".tmp" if err := os.WriteFile(tmpPath, newData, 0644); err != nil { + u.logger.Printf("[ERROR] [selfupdate] Failed to update compose file: %v", err) return fmt.Errorf("writing temp compose file: %w", err) } if err := os.Rename(tmpPath, u.composePath); err != nil { + u.logger.Printf("[ERROR] [selfupdate] Failed to update compose file: %v", err) return fmt.Errorf("renaming compose file: %w", err) } diff --git a/controller/internal/settings/settings.go b/controller/internal/settings/settings.go index 3dd7eef..1672ceb 100644 --- a/controller/internal/settings/settings.go +++ b/controller/internal/settings/settings.go @@ -183,7 +183,7 @@ func Load(path string, logger *log.Logger) (*Settings, error) { return nil, fmt.Errorf("parsing settings file: %w", err) } - logger.Printf("[DEBUG] Settings loaded from %s", path) + logger.Printf("[INFO] [settings] Loaded settings 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)) @@ -218,27 +218,42 @@ func (s *Settings) migrateResticToRsync() { func (s *Settings) save() error { data, err := json.MarshalIndent(s, "", " ") if err != nil { + if s.log != nil { + s.log.Printf("[ERROR] [settings] Failed to save: %v", err) + } return fmt.Errorf("marshaling settings: %w", err) } tmpPath := s.path + ".tmp" if err := os.MkdirAll(filepath.Dir(s.path), 0755); err != nil { + if s.log != nil { + s.log.Printf("[ERROR] [settings] Failed to save: %v", err) + } return fmt.Errorf("creating settings dir: %w", err) } if err := os.WriteFile(tmpPath, data, 0644); err != nil { os.Remove(tmpPath) // clean up partial file + if s.log != nil { + s.log.Printf("[ERROR] [settings] Failed to save: %v", err) + } return fmt.Errorf("writing tmp settings: %w", err) } if err := os.Rename(tmpPath, s.path); err != nil { os.Remove(tmpPath) + if s.log != nil { + s.log.Printf("[ERROR] [settings] Failed to save: %v", err) + } return fmt.Errorf("renaming settings file: %w", err) } if s.debug { s.log.Printf("[DEBUG] [settings] saved to %s (%d bytes)", s.path, len(data)) } + if s.log != nil { + s.log.Printf("[INFO] [settings] Settings saved") + } return nil } @@ -461,6 +476,9 @@ func (s *Settings) AddStoragePath(sp StoragePath) error { } } s.StoragePaths = append(s.StoragePaths, sp) + if s.log != nil { + s.log.Printf("[INFO] [settings] Added storage path: %s", sp.Path) + } return s.save() } @@ -478,6 +496,9 @@ func (s *Settings) RemoveStoragePath(path string) error { } } s.StoragePaths = kept + if s.log != nil { + s.log.Printf("[INFO] [settings] Removed storage path: %s", path) + } return s.save() } @@ -597,6 +618,9 @@ func (s *Settings) SetDisconnected(path string, disconnected bool, stoppedStacks if s.debug { s.log.Printf("[DEBUG] [settings] SetDisconnected path=%q disconnected=%v stopped_stacks=%d", path, disconnected, len(stoppedStacks)) } + if s.log != nil { + s.log.Printf("[INFO] [settings] Node disconnected: %v", disconnected) + } for i := range s.StoragePaths { if s.StoragePaths[i].Path == path { s.StoragePaths[i].Disconnected = disconnected @@ -707,6 +731,9 @@ func (s *Settings) SetDecommissioned(path, migratedTo string) error { if s.debug { s.log.Printf("[DEBUG] [settings] SetDecommissioned path=%q migrated_to=%q", path, migratedTo) } + if s.log != nil { + s.log.Printf("[INFO] [settings] Node decommissioned") + } for i := range s.StoragePaths { if s.StoragePaths[i].Path == path { s.StoragePaths[i].Decommissioned = true @@ -842,6 +869,9 @@ func (s *Settings) AddPendingEvent(event PendingEvent) error { if s.debug { s.log.Printf("[DEBUG] [settings] AddPendingEvent type=%q severity=%q", event.EventType, event.Severity) } + if s.log != nil { + s.log.Printf("[INFO] [settings] Added pending event: %s", event.EventType) + } s.PendingEvents = append(s.PendingEvents, event) return s.save() } diff --git a/controller/internal/stacks/delete.go b/controller/internal/stacks/delete.go index 2d50f7d..ce70adb 100644 --- a/controller/internal/stacks/delete.go +++ b/controller/internal/stacks/delete.go @@ -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 } diff --git a/controller/internal/stacks/deploy.go b/controller/internal/stacks/deploy.go index 3509af3..9602fd7 100644 --- a/controller/internal/stacks/deploy.go +++ b/controller/internal/stacks/deploy.go @@ -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 { diff --git a/controller/internal/stacks/healthprobe.go b/controller/internal/stacks/healthprobe.go index d8873bf..8652709 100644 --- a/controller/internal/stacks/healthprobe.go +++ b/controller/internal/stacks/healthprobe.go @@ -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 diff --git a/controller/internal/stacks/manager.go b/controller/internal/stacks/manager.go index d4d1e8a..15a8a8d 100644 --- a/controller/internal/stacks/manager.go +++ b/controller/internal/stacks/manager.go @@ -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) diff --git a/controller/internal/stacks/metadata.go b/controller/internal/stacks/metadata.go index 8c49de1..0f4bae8 100644 --- a/controller/internal/stacks/metadata.go +++ b/controller/internal/stacks/metadata.go @@ -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 diff --git a/controller/internal/storage/attach_linux.go b/controller/internal/storage/attach_linux.go index 3661d0a..3fa3595 100644 --- a/controller/internal/storage/attach_linux.go +++ b/controller/internal/storage/attach_linux.go @@ -184,6 +184,9 @@ func FinalizeAttach(req AttachRequest, progress chan<- FormatProgress) (string, errStr = err.Error() } progress <- FormatProgress{Step: "error", Message: msg, Error: errStr, Percent: 0} + if req.Logger != nil { + req.Logger.Printf("[ERROR] [storage] Failed to attach disk: %v", err) + } return fmt.Errorf("%s: %w", msg, err) } dbg := func(format string, args ...interface{}) { @@ -193,6 +196,9 @@ func FinalizeAttach(req AttachRequest, progress chan<- FormatProgress) (string, } mountPath := "/mnt/" + req.MountName + if req.Logger != nil { + req.Logger.Printf("[INFO] [storage] Attaching disk %s at %s", req.DevicePath, mountPath) + } dbg("starting: device=%s mountName=%s subPath=%s", req.DevicePath, req.MountName, req.SubPath) // --- Step 1: Validate --- @@ -322,6 +328,9 @@ func FinalizeAttach(req AttachRequest, progress chan<- FormatProgress) (string, } dbg("attach completed successfully: %s", mountPath) + if req.Logger != nil { + req.Logger.Printf("[INFO] [storage] Disk attached successfully") + } send("done", "Meghajtó sikeresen csatolva: "+mountPath, 100) return mountPath, nil diff --git a/controller/internal/storage/format_linux.go b/controller/internal/storage/format_linux.go index 941d4a5..7071779 100644 --- a/controller/internal/storage/format_linux.go +++ b/controller/internal/storage/format_linux.go @@ -27,6 +27,9 @@ func FormatAndMount(req FormatRequest, progress chan<- FormatProgress) (string, errStr = err.Error() } progress <- FormatProgress{Step: "error", Message: msg, Error: errStr, Percent: 0} + if req.Logger != nil { + req.Logger.Printf("[ERROR] [storage] Format failed: %v", err) + } return fmt.Errorf("%s: %w", msg, err) } dbg := func(format string, args ...interface{}) { @@ -36,6 +39,9 @@ func FormatAndMount(req FormatRequest, progress chan<- FormatProgress) (string, } mountPath := "/mnt/" + req.MountName + if req.Logger != nil { + req.Logger.Printf("[INFO] [storage] Formatting disk %s as ext4", req.DevicePath) + } dbg("starting: device=%s mountName=%s createPartition=%v", req.DevicePath, req.MountName, req.CreatePartition) // --- Step 1: Validate --- @@ -216,6 +222,9 @@ func FormatAndMount(req FormatRequest, progress chan<- FormatProgress) (string, } dbg("format and mount completed successfully: %s", mountPath) + if req.Logger != nil { + req.Logger.Printf("[INFO] [storage] Disk formatted and mounted at %s", mountPath) + } send("done", "Meghajtó sikeresen inicializálva: "+mountPath, 100) return mountPath, nil diff --git a/controller/internal/storage/migrate.go b/controller/internal/storage/migrate.go index 01894bf..cfa5f06 100644 --- a/controller/internal/storage/migrate.go +++ b/controller/internal/storage/migrate.go @@ -82,6 +82,9 @@ func MigrateAppData( Error: errStr, ElapsedSeconds: int(time.Since(start).Seconds()), } + if req.Logger != nil { + req.Logger.Printf("[ERROR] [storage] Migration %s failed at %s: %v", req.StackName, step, err) + } return fmt.Errorf("%s: %w", msg, err) } @@ -91,6 +94,9 @@ func MigrateAppData( } } + if req.Logger != nil { + req.Logger.Printf("[INFO] [storage] Starting migration: stack=%s from=%s to=%s", req.StackName, req.CurrentHDDPath, req.TargetPath) + } dbg("starting migration: stack=%s from=%s to=%s mounts=%d", req.StackName, req.CurrentHDDPath, req.TargetPath, len(req.HDDMounts)) // --- Step 1: Validate --- @@ -196,6 +202,9 @@ func MigrateAppData( return fail("starting", "Alkalmazás indítása sikertelen az új tárolóról", err) } + if req.Logger != nil { + req.Logger.Printf("[INFO] [storage] Migration completed: stack=%s elapsed=%ds", req.StackName, int(time.Since(start).Seconds())) + } dbg("migration completed: stack=%s bytesCopied=%d elapsed=%ds", req.StackName, bytesCopied, int(time.Since(start).Seconds())) send("done", fmt.Sprintf("Áthelyezés kész! Az alkalmazás az új tárolóról fut. (Régi adat: %s, idő: %ds)", diff --git a/controller/internal/storage/scan_linux.go b/controller/internal/storage/scan_linux.go index d496605..3b96394 100644 --- a/controller/internal/storage/scan_linux.go +++ b/controller/internal/storage/scan_linux.go @@ -246,6 +246,9 @@ func ScanDisks(logger *log.Logger, debug bool) (*ScanResult, error) { } } + if logger != nil { + logger.Printf("[INFO] [storage] Scanning disks") + } dbg("starting disk scan") scanStart := time.Now() @@ -341,6 +344,9 @@ func ScanDisks(logger *log.Logger, debug bool) (*ScanResult, error) { enrichWithBlkid(result.AvailableDisks, logger, debug) enrichWithBlkid(result.SystemDisks, logger, debug) + if logger != nil { + logger.Printf("[INFO] [storage] Found %d disks", len(result.AvailableDisks)+len(result.SystemDisks)) + } dbg("disk scan completed in %s", time.Since(scanStart).Round(time.Millisecond)) return result, nil diff --git a/controller/internal/sync/sync.go b/controller/internal/sync/sync.go index 8c106ee..97dd404 100644 --- a/controller/internal/sync/sync.go +++ b/controller/internal/sync/sync.go @@ -178,8 +178,11 @@ func (s *Syncer) doSync() SyncResult { result := SyncResult{OK: true} + s.logger.Printf("[INFO] [sync] Starting catalog sync") + // Step 1: Clone or pull if err := s.gitCloneOrPull(); err != nil { + s.logger.Printf("[ERROR] [sync] Catalog sync failed: %v", err) s.mu.Lock() s.lastErr = err s.lastSync = time.Now() @@ -190,6 +193,7 @@ func (s *Syncer) doSync() SyncResult { // Step 2: Copy templates to stacks dir newApps, updated, err := s.copyTemplates() if err != nil { + s.logger.Printf("[ERROR] [sync] Catalog sync failed: %v", err) s.mu.Lock() s.lastErr = err s.lastSync = time.Now() @@ -229,6 +233,8 @@ func (s *Syncer) doSync() SyncResult { result.Message = "Sablonok frissítve — " + strings.Join(parts, "; ") } + s.logger.Printf("[INFO] [sync] Catalog sync complete") + s.mu.Lock() s.lastErr = nil s.lastSync = time.Now() @@ -344,7 +350,7 @@ func (s *Syncer) copyTemplates() (newApps []string, updated []string, err error) changed, err := copyIfChanged(src, dst) if err != nil { - s.logger.Printf("[SYNC] Failed to copy %s/%s: %v", appName, filename, err) + s.logger.Printf("[WARN] [sync] Failed to copy catalog file %s/%s: %v", appName, filename, err) continue } if changed { diff --git a/controller/internal/system/mounts_linux.go b/controller/internal/system/mounts_linux.go index 97c733d..369bb7b 100644 --- a/controller/internal/system/mounts_linux.go +++ b/controller/internal/system/mounts_linux.go @@ -5,6 +5,7 @@ package system import ( "bufio" "fmt" + "log" "os" "os/exec" "path/filepath" @@ -150,6 +151,7 @@ func CheckBackupDestination(path string) DestinationHealth { h.Warning = "A cél tárhely (" + path + ") nem létezik!" h.Blocked = true h.Severity = "critical" + log.Printf("[WARN] [system] Backup destination %s is not safe: path does not exist", path) debugf("[DEBUG] [system] CheckBackupDestination: path=%q — tier1 FAIL (not exists)", path) return h } @@ -160,6 +162,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" + log.Printf("[WARN] [system] Backup destination %s is not safe: not writable", path) debugf("[DEBUG] [system] CheckBackupDestination: path=%q — tier2 FAIL (not writable)", path) return h } @@ -191,10 +194,12 @@ func CheckBackupDestination(path string) DestinationHealth { h.Warning = fmt.Sprintf("A rendszermeghajtón csak %.1f GB szabad — legalább 10 GB szükséges a rendszer stabilitásához!", di.AvailGB) h.Blocked = true h.Severity = "critical" + log.Printf("[WARN] [system] Backup destination %s is not safe: system drive low space (%.1f GB free)", path, di.AvailGB) } else if di.UsedPercent >= 90 { h.Warning = fmt.Sprintf("A rendszermeghajtó %.0f%%-ban megtelt — maximum 90%% megengedett.", di.UsedPercent) h.Blocked = true h.Severity = "critical" + log.Printf("[WARN] [system] Backup destination %s is not safe: system drive %.0f%% full", path, di.UsedPercent) } // If neither triggers, keep the Tier 3 system-drive warning } else { @@ -203,6 +208,7 @@ func CheckBackupDestination(path string) DestinationHealth { h.Warning = fmt.Sprintf("A mentési meghajtó megtelt (%.0f%% használt)!", di.UsedPercent) h.Blocked = true h.Severity = "critical" + log.Printf("[WARN] [system] Backup destination %s is not safe: drive %.0f%% full", path, di.UsedPercent) } else if di.UsedPercent >= 90 { h.Warning = fmt.Sprintf("A mentési meghajtó majdnem megtelt (%.0f%% használt).", di.UsedPercent) h.Severity = "warning" @@ -273,6 +279,7 @@ func ProbeStoragePath(path string) ProbeResult { // Quick check: does the path exist at all? if _, err := os.Lstat(path); os.IsNotExist(err) { + log.Printf("[WARN] [system] Storage path %s probe failed: %v", path, err) debugf("[DEBUG] [system] ProbeStoragePath: path=%q — not exists (%s)", path, time.Since(start).Round(time.Millisecond)) return ProbeResult{Status: ProbeDisconnected, Err: err} } @@ -298,12 +305,15 @@ func ProbeStoragePath(path string) ProbeResult { if strings.Contains(errStr, "transport endpoint") || strings.Contains(errStr, "input/output error") || strings.Contains(errStr, "no such device") { + log.Printf("[WARN] [system] Storage path %s probe failed: %v", path, res.err) debugf("[DEBUG] [system] ProbeStoragePath: path=%q — disconnected: %v (%s)", path, res.err, elapsed) return ProbeResult{Status: ProbeDisconnected, Err: res.err} } + log.Printf("[WARN] [system] Storage path %s probe failed: %v", path, 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): + log.Printf("[WARN] [system] Storage path %s probe failed: stat timed out after 3s", path) debugf("[DEBUG] [system] ProbeStoragePath: path=%q — TIMEOUT (3s)", path) return ProbeResult{Status: ProbeTimeout, Err: fmt.Errorf("stat timed out after 3s")} } diff --git a/controller/internal/web/auth.go b/controller/internal/web/auth.go index 05eadb5..41db64f 100644 --- a/controller/internal/web/auth.go +++ b/controller/internal/web/auth.go @@ -88,6 +88,7 @@ func (s *Server) RequireAuth(next http.Handler) http.Handler { s.logger.Printf("[DEBUG] [web] auth: rejected %s %s from %s (%s)", r.Method, r.URL.Path, r.RemoteAddr, reason) } if strings.HasPrefix(r.URL.Path, "/api/") { + s.logger.Printf("[WARN] [api] Unauthorized request to %s from %s", r.URL.Path, r.RemoteAddr) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusUnauthorized) fmt.Fprint(w, `{"ok":false,"error":"authentication required"}`) @@ -203,6 +204,7 @@ func (s *Server) handleLogout(w http.ResponseWriter, r *http.Request) { delete(s.sessions, cookie.Value) s.sessionsMu.Unlock() } + s.logger.Printf("[INFO] [web] User logged out from %s", r.RemoteAddr) http.SetCookie(w, &http.Cookie{Name: sessionCookieName, Value: "", Path: "/", MaxAge: -1}) http.Redirect(w, r, "/login", http.StatusFound) } @@ -257,6 +259,7 @@ func (s *Server) invalidateAllSessions() { count := len(s.sessions) s.sessions = make(map[string]*session) s.sessionsMu.Unlock() + s.logger.Printf("[INFO] [web] All sessions invalidated (cleared %d)", count) if s.isDebug() { s.logger.Printf("[DEBUG] [web] invalidated all sessions (cleared %d)", count) } @@ -281,6 +284,9 @@ func (s *Server) cleanupSessions() { } remaining := len(s.sessions) s.sessionsMu.Unlock() + if expired > 0 { + s.logger.Printf("[INFO] [web] Cleaned up %d expired sessions, %d remaining", expired, remaining) + } if s.isDebug() && expired > 0 { s.logger.Printf("[DEBUG] [web] session cleanup: expired=%d remaining=%d", expired, remaining) } diff --git a/controller/internal/web/handler_export.go b/controller/internal/web/handler_export.go index 2d680f1..9f8d9f4 100644 --- a/controller/internal/web/handler_export.go +++ b/controller/internal/web/handler_export.go @@ -109,6 +109,7 @@ func (s *Server) apiExportEstimate(w http.ResponseWriter, r *http.Request) { est, err := s.appExporter.EstimateExport(stackName, drive) if err != nil { + s.logger.Printf("[ERROR] [web] Export estimate failed for %s: %v", stackName, err) s.logger.Printf("[DEBUG] [handler_export] apiExportEstimate error: %v", err) jsonError(w, err.Error(), http.StatusInternalServerError) return @@ -162,6 +163,7 @@ func (s *Server) apiExportStart(w http.ResponseWriter, r *http.Request) { StopApp: req.StopApp, }) if err != nil { + s.logger.Printf("[ERROR] [web] Export start failed for %s: %v", req.StackName, err) s.logger.Printf("[DEBUG] [handler_export] apiExportStart error: %v", err) jsonError(w, err.Error(), http.StatusConflict) return @@ -306,6 +308,7 @@ func (s *Server) apiImportStart(w http.ResponseWriter, r *http.Request) { Password: req.Password, }) if err != nil { + s.logger.Printf("[ERROR] [web] Import start failed for %s: %v", req.Path, err) s.logger.Printf("[DEBUG] [handler_export] apiImportStart error: %v", err) jsonError(w, err.Error(), http.StatusConflict) return diff --git a/controller/internal/web/handler_restore.go b/controller/internal/web/handler_restore.go index 7be5a69..53ad0be 100644 --- a/controller/internal/web/handler_restore.go +++ b/controller/internal/web/handler_restore.go @@ -89,6 +89,7 @@ func (s *Server) apiRestoreAll(w http.ResponseWriter, r *http.Request) { jsonError(w, "restore already in progress", http.StatusConflict) return } + s.logger.Printf("[INFO] [web] Restore-all initiated from %s", r.RemoteAddr) go s.executeAllRestores() jsonResponse(w, map[string]interface{}{ diff --git a/controller/internal/web/handlers.go b/controller/internal/web/handlers.go index 99831e2..2d443e9 100644 --- a/controller/internal/web/handlers.go +++ b/controller/internal/web/handlers.go @@ -1568,6 +1568,7 @@ func (s *Server) settingsStorageDefaultHandler(w http.ResponseWriter, r *http.Re http.Redirect(w, r, "/settings", http.StatusFound) return } + s.logger.Printf("[INFO] [web] Default storage path set to %s", path) http.Redirect(w, r, "/settings?storage_msg=success&storage_detail="+url.QueryEscape("Alapértelmezett adattároló beállítva: "+path), http.StatusFound) } @@ -1585,6 +1586,7 @@ func (s *Server) settingsStorageSchedulableHandler(w http.ResponseWriter, r *htt http.Redirect(w, r, "/settings", http.StatusFound) return } + s.logger.Printf("[INFO] [web] Storage schedulable updated: %s → %v", path, schedulable) http.Redirect(w, r, "/settings?storage_msg=success&storage_detail="+url.QueryEscape("Adattároló állapot módosítva: "+path), http.StatusFound) } diff --git a/controller/internal/web/server.go b/controller/internal/web/server.go index f666be2..cafd139 100644 --- a/controller/internal/web/server.go +++ b/controller/internal/web/server.go @@ -345,6 +345,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { } s.debugPageHandler(w, r) default: + s.logger.Printf("[WARN] [web] 404 Not Found: %s %s", r.Method, path) http.NotFound(w, r) } } diff --git a/controller/internal/web/storage_handlers.go b/controller/internal/web/storage_handlers.go index a1d5291..0ee9624 100644 --- a/controller/internal/web/storage_handlers.go +++ b/controller/internal/web/storage_handlers.go @@ -1222,6 +1222,7 @@ func (s *Server) storageDisconnectHandler(w http.ResponseWriter, r *http.Request return } + s.logger.Printf("[INFO] [web] Disk disconnect completed: %s (stopped %d stacks)", req.Path, len(stoppedStacks)) if s.isDebug() { s.logger.Printf("[DEBUG] [web] storageDisconnect: path=%s success, stopped %d stacks", req.Path, len(stoppedStacks)) } @@ -1264,6 +1265,7 @@ func (s *Server) storageReconnectHandler(w http.ResponseWriter, r *http.Request) return } + s.logger.Printf("[INFO] [web] Disk reconnect completed: %s", req.Path) if s.isDebug() { s.logger.Printf("[DEBUG] [web] storageReconnect: path=%s success, previously stopped stacks=%v", req.Path, stoppedStacks) } @@ -1309,6 +1311,7 @@ func (s *Server) storageRestartAppsHandler(w http.ResponseWriter, r *http.Reques } started, failed := s.storageWatchdog.RestartStoppedApps(req.Path) + s.logger.Printf("[INFO] [web] Restart apps for %s: started=%d failed=%d", req.Path, len(started), len(failed)) if s.isDebug() { s.logger.Printf("[DEBUG] [web] storageRestartApps: path=%s started=%v failed=%v", req.Path, started, failed) }