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
+46
View File
@@ -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
+7 -2
View File
@@ -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 |
|--------|------------|--------|-----------|
+5
View File
@@ -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
}
+1
View File
@@ -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() {
+6 -2
View File
@@ -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) {
+6
View File
@@ -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.
+8
View File
@@ -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
}
+7
View File
@@ -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
}
@@ -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.
+2
View File
@@ -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
}
+1 -1
View File
@@ -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
@@ -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++
}
@@ -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
}
+16 -6
View File
@@ -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)
}
}
+8
View File
@@ -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)
+1
View File
@@ -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)
}
@@ -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)
}
}
@@ -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 {
@@ -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)
}
@@ -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))
}
@@ -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))
+3
View File
@@ -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))
}
+3
View File
@@ -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)
}
+14 -13
View File
@@ -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)
}
+2
View File
@@ -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")
}
@@ -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)
}
+31 -1
View File
@@ -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()
}
+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
@@ -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
@@ -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
+9
View File
@@ -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)",
@@ -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
+7 -1
View File
@@ -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 {
@@ -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")}
}
+6
View File
@@ -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)
}
@@ -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
@@ -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{}{
+2
View File
@@ -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)
}
+1
View File
@@ -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)
}
}
@@ -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)
}