feat: comprehensive debug logging across all controller modules
Add detailed [DEBUG] logging to every controller module when logging.level is set to "debug". Each module with stateful debug uses SetDebug(bool) wired from main.go. Covers stacks, backup, cloudflare, integrations, system, monitor, settings, scheduler, web handlers, storage, metrics, API, selfupdate, and assets. Also includes the app export/import (.fab bundles) feature from v0.32.0 and its debug page integration. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -23,6 +23,7 @@ type GeoSyncManager struct {
|
||||
domain string
|
||||
stacks StackLister
|
||||
logger *log.Logger
|
||||
debug bool
|
||||
|
||||
mu sync.Mutex
|
||||
running bool
|
||||
@@ -39,6 +40,11 @@ func NewGeoSyncManager(client *Client, sett *settings.Settings, domain string, s
|
||||
}
|
||||
}
|
||||
|
||||
// SetDebug enables or disables debug logging for the geo sync manager.
|
||||
func (g *GeoSyncManager) SetDebug(debug bool) {
|
||||
g.debug = debug
|
||||
}
|
||||
|
||||
// IsRunning returns true if a sync operation is in progress.
|
||||
func (g *GeoSyncManager) IsRunning() bool {
|
||||
g.mu.Lock()
|
||||
@@ -75,17 +81,28 @@ func (g *GeoSyncManager) Sync(ctx context.Context) error {
|
||||
// 1. Resolve zone ID (use cached value if available)
|
||||
zoneID := geo.ZoneID
|
||||
if zoneID == "" {
|
||||
if g.debug {
|
||||
g.logger.Printf("[DEBUG] [cloudflare] Zone ID not cached, resolving via API for domain %s", g.domain)
|
||||
}
|
||||
var err error
|
||||
zoneID, err = g.client.GetZoneID(ctx, g.domain)
|
||||
if err != nil {
|
||||
g.saveError(zoneID, "", err.Error())
|
||||
return fmt.Errorf("resolve zone: %w", err)
|
||||
}
|
||||
if g.debug {
|
||||
g.logger.Printf("[DEBUG] [cloudflare] Resolved zone ID: %s", zoneID)
|
||||
}
|
||||
} else if g.debug {
|
||||
g.logger.Printf("[DEBUG] [cloudflare] Using cached zone ID: %s", zoneID)
|
||||
}
|
||||
|
||||
// 2. Get or create the custom WAF ruleset
|
||||
rulesetID := geo.RulesetID
|
||||
if rulesetID == "" {
|
||||
if g.debug {
|
||||
g.logger.Printf("[DEBUG] [cloudflare] Ruleset ID not cached, looking up for zone %s", zoneID)
|
||||
}
|
||||
var err error
|
||||
rulesetID, err = g.client.GetCustomRulesetID(ctx, zoneID)
|
||||
if err != nil {
|
||||
@@ -93,12 +110,20 @@ func (g *GeoSyncManager) Sync(ctx context.Context) error {
|
||||
return fmt.Errorf("get ruleset: %w", err)
|
||||
}
|
||||
if rulesetID == "" {
|
||||
if g.debug {
|
||||
g.logger.Printf("[DEBUG] [cloudflare] No existing custom ruleset found, creating new one")
|
||||
}
|
||||
rulesetID, err = g.client.CreateCustomRuleset(ctx, zoneID)
|
||||
if err != nil {
|
||||
g.saveError(zoneID, "", err.Error())
|
||||
return fmt.Errorf("create ruleset: %w", err)
|
||||
}
|
||||
}
|
||||
if g.debug {
|
||||
g.logger.Printf("[DEBUG] [cloudflare] Using ruleset ID: %s", rulesetID)
|
||||
}
|
||||
} else if g.debug {
|
||||
g.logger.Printf("[DEBUG] [cloudflare] Using cached ruleset ID: %s", rulesetID)
|
||||
}
|
||||
|
||||
// 3. List existing felhom-managed rules
|
||||
@@ -107,9 +132,21 @@ func (g *GeoSyncManager) Sync(ctx context.Context) error {
|
||||
g.saveError(zoneID, rulesetID, err.Error())
|
||||
return fmt.Errorf("list existing rules: %w", err)
|
||||
}
|
||||
if g.debug {
|
||||
g.logger.Printf("[DEBUG] [cloudflare] Found %d existing felhom rules", len(existing))
|
||||
for _, r := range existing {
|
||||
g.logger.Printf("[DEBUG] [cloudflare] existing: %s (id=%s)", r.Description, r.ID)
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Build desired rules
|
||||
desired := g.buildDesiredRules(geo)
|
||||
if g.debug {
|
||||
g.logger.Printf("[DEBUG] [cloudflare] Built %d desired rules", len(desired))
|
||||
for _, d := range desired {
|
||||
g.logger.Printf("[DEBUG] [cloudflare] desired: %s", d.description)
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Diff and apply
|
||||
if err := g.applyDiff(ctx, zoneID, rulesetID, existing, desired); err != nil {
|
||||
@@ -134,24 +171,41 @@ func (g *GeoSyncManager) deleteAllRules(ctx context.Context, geo *settings.GeoRe
|
||||
}
|
||||
|
||||
if zoneID == "" || rulesetID == "" {
|
||||
if g.debug {
|
||||
g.logger.Printf("[DEBUG] [cloudflare] deleteAllRules: no cached zone/ruleset IDs, nothing to clean up")
|
||||
}
|
||||
// No cached IDs — nothing to clean up
|
||||
return nil
|
||||
}
|
||||
|
||||
if g.debug {
|
||||
g.logger.Printf("[DEBUG] [cloudflare] deleteAllRules: listing rules for zone=%s ruleset=%s", zoneID, rulesetID)
|
||||
}
|
||||
|
||||
existing, err := g.client.GetFelhomRules(ctx, zoneID, rulesetID)
|
||||
if err != nil {
|
||||
g.logger.Printf("[GEO] Warning: could not list rules for cleanup: %v", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
if g.debug {
|
||||
g.logger.Printf("[DEBUG] [cloudflare] deleteAllRules: found %d felhom rules to delete", len(existing))
|
||||
}
|
||||
|
||||
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)
|
||||
} else {
|
||||
deleted++
|
||||
}
|
||||
}
|
||||
|
||||
if len(existing) > 0 {
|
||||
g.logger.Printf("[GEO] 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))
|
||||
}
|
||||
}
|
||||
|
||||
g.saveError(zoneID, rulesetID, "")
|
||||
@@ -169,6 +223,9 @@ func (g *GeoSyncManager) buildDesiredRules(geo *settings.GeoRestriction) []desir
|
||||
var rules []desiredRule
|
||||
|
||||
hostnames := g.stacks.GetDeployedHostnames()
|
||||
if g.debug {
|
||||
g.logger.Printf("[DEBUG] [cloudflare] buildDesiredRules: %d deployed hostnames from stacks", len(hostnames))
|
||||
}
|
||||
|
||||
// Collect app hostnames that have overrides (to exclude from global rule)
|
||||
var excludeHostnames []string
|
||||
@@ -177,6 +234,9 @@ func (g *GeoSyncManager) buildDesiredRules(geo *settings.GeoRestriction) []desir
|
||||
for appName, override := range geo.AppOverrides {
|
||||
hostname, ok := hostnames[appName]
|
||||
if !ok {
|
||||
if g.debug {
|
||||
g.logger.Printf("[DEBUG] [cloudflare] buildDesiredRules: skipping override for %q (not deployed)", appName)
|
||||
}
|
||||
continue // app not deployed, skip
|
||||
}
|
||||
overrideApps[appName] = true
|
||||
@@ -189,15 +249,24 @@ func (g *GeoSyncManager) buildDesiredRules(geo *settings.GeoRestriction) []desir
|
||||
})
|
||||
}
|
||||
|
||||
if g.debug && len(overrideApps) > 0 {
|
||||
g.logger.Printf("[DEBUG] [cloudflare] buildDesiredRules: %d app overrides active (deployed)", len(overrideApps))
|
||||
}
|
||||
|
||||
// Sort exclude hostnames for deterministic expression
|
||||
sort.Strings(excludeHostnames)
|
||||
|
||||
// Global rule (excludes apps with their own rules)
|
||||
globalExpr := BuildGlobalExpression(geo.AllowedCountries, excludeHostnames)
|
||||
rules = append(rules, desiredRule{
|
||||
description: globalRuleDesc,
|
||||
expression: BuildGlobalExpression(geo.AllowedCountries, excludeHostnames),
|
||||
expression: globalExpr,
|
||||
})
|
||||
|
||||
if g.debug {
|
||||
g.logger.Printf("[DEBUG] [cloudflare] buildDesiredRules: global rule expression: %s", globalExpr)
|
||||
}
|
||||
|
||||
return rules
|
||||
}
|
||||
|
||||
@@ -220,13 +289,21 @@ func (g *GeoSyncManager) applyDiff(ctx context.Context, zoneID, rulesetID string
|
||||
if ex, ok := existingByDesc[d.description]; ok {
|
||||
// Rule exists — check if expression changed
|
||||
if ex.Expression != d.expression {
|
||||
if g.debug {
|
||||
g.logger.Printf("[DEBUG] [cloudflare] applyDiff: updating rule %q (id=%s) — expression changed", d.description, ex.ID)
|
||||
}
|
||||
r := newBlockRule(d.description, d.expression)
|
||||
if err := g.client.UpdateRule(ctx, zoneID, rulesetID, ex.ID, r); err != nil {
|
||||
return fmt.Errorf("update rule %q: %w", d.description, err)
|
||||
}
|
||||
} else if g.debug {
|
||||
g.logger.Printf("[DEBUG] [cloudflare] applyDiff: rule %q unchanged, skipping", d.description)
|
||||
}
|
||||
} else {
|
||||
// New rule — create
|
||||
if g.debug {
|
||||
g.logger.Printf("[DEBUG] [cloudflare] applyDiff: creating new rule %q", d.description)
|
||||
}
|
||||
r := newBlockRule(d.description, d.expression)
|
||||
if _, err := g.client.CreateRule(ctx, zoneID, rulesetID, r); err != nil {
|
||||
return fmt.Errorf("create rule %q: %w", d.description, err)
|
||||
@@ -237,6 +314,9 @@ func (g *GeoSyncManager) applyDiff(ctx context.Context, zoneID, rulesetID string
|
||||
// Delete rules that are no longer desired
|
||||
for _, ex := range existing {
|
||||
if _, ok := desiredByDesc[ex.Description]; !ok {
|
||||
if g.debug {
|
||||
g.logger.Printf("[DEBUG] [cloudflare] applyDiff: deleting obsolete rule %q (id=%s)", ex.Description, ex.ID)
|
||||
}
|
||||
if err := g.client.DeleteRule(ctx, zoneID, rulesetID, ex.ID); err != nil {
|
||||
return fmt.Errorf("delete rule %q: %w", ex.Description, err)
|
||||
}
|
||||
|
||||
@@ -71,12 +71,23 @@ func (c *Client) GetCustomRulesetID(ctx context.Context, zoneID string) (string,
|
||||
return "", fmt.Errorf("decode rulesets: %w", err)
|
||||
}
|
||||
|
||||
if c.debug {
|
||||
c.logger.Printf("[CF-DEBUG] GetCustomRulesetID: found %d rulesets for zone %s", len(rulesets), zoneID)
|
||||
}
|
||||
|
||||
for _, rs := range rulesets {
|
||||
if rs.Phase == wafPhase {
|
||||
if c.debug {
|
||||
c.logger.Printf("[CF-DEBUG] GetCustomRulesetID: matched ruleset %s (phase=%s)", rs.ID, wafPhase)
|
||||
}
|
||||
return rs.ID, nil
|
||||
}
|
||||
}
|
||||
|
||||
if c.debug {
|
||||
c.logger.Printf("[CF-DEBUG] GetCustomRulesetID: no ruleset with phase %s found", wafPhase)
|
||||
}
|
||||
|
||||
return "", nil
|
||||
}
|
||||
|
||||
@@ -119,6 +130,10 @@ func (c *Client) GetRules(ctx context.Context, zoneID, rulesetID string) ([]rule
|
||||
return nil, fmt.Errorf("decode rules: %w", err)
|
||||
}
|
||||
|
||||
if c.debug {
|
||||
c.logger.Printf("[CF-DEBUG] GetRules: %d total rules in ruleset %s", len(rs.Rules), rulesetID)
|
||||
}
|
||||
|
||||
return rs.Rules, nil
|
||||
}
|
||||
|
||||
@@ -141,6 +156,10 @@ func (c *Client) GetFelhomRules(ctx context.Context, zoneID, rulesetID string) (
|
||||
}
|
||||
}
|
||||
|
||||
if c.debug {
|
||||
c.logger.Printf("[CF-DEBUG] GetFelhomRules: %d felhom rules out of %d total", len(result), len(rules))
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
@@ -163,6 +182,13 @@ func (c *Client) CreateRule(ctx context.Context, zoneID, rulesetID string, r rul
|
||||
for _, created := range rs.Rules {
|
||||
if created.Description == r.Description {
|
||||
c.logger.Printf("[CF] Created rule %q → %s", r.Description, created.ID)
|
||||
if c.debug {
|
||||
expr := r.Expression
|
||||
if len(expr) > 120 {
|
||||
expr = expr[:120] + "..."
|
||||
}
|
||||
c.logger.Printf("[CF-DEBUG] CreateRule: expression: %s", expr)
|
||||
}
|
||||
return created.ID, nil
|
||||
}
|
||||
}
|
||||
@@ -178,6 +204,13 @@ func (c *Client) UpdateRule(ctx context.Context, zoneID, rulesetID, ruleID strin
|
||||
return fmt.Errorf("update rule %s: %w", ruleID, err)
|
||||
}
|
||||
c.logger.Printf("[CF] Updated rule %q (%s)", r.Description, ruleID)
|
||||
if c.debug {
|
||||
expr := r.Expression
|
||||
if len(expr) > 120 {
|
||||
expr = expr[:120] + "..."
|
||||
}
|
||||
c.logger.Printf("[CF-DEBUG] UpdateRule: expression: %s", expr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,14 @@ type zone struct {
|
||||
// GetZoneID resolves the Cloudflare zone ID for a domain.
|
||||
// It tries the exact domain first, then strips subdomains progressively.
|
||||
func (c *Client) GetZoneID(ctx context.Context, domain string) (string, error) {
|
||||
if c.debug {
|
||||
c.logger.Printf("[CF-DEBUG] GetZoneID: looking up zone for domain %q", domain)
|
||||
}
|
||||
|
||||
// Try exact domain first (e.g., "demo-felhom.eu")
|
||||
if c.debug {
|
||||
c.logger.Printf("[CF-DEBUG] GetZoneID: trying exact domain %q", domain)
|
||||
}
|
||||
id, err := c.lookupZone(ctx, domain)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -32,6 +39,9 @@ func (c *Client) GetZoneID(ctx context.Context, domain string) (string, error) {
|
||||
if parent == "" {
|
||||
break
|
||||
}
|
||||
if c.debug {
|
||||
c.logger.Printf("[CF-DEBUG] GetZoneID: trying parent domain %q", parent)
|
||||
}
|
||||
id, err = c.lookupZone(ctx, parent)
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
||||
Reference in New Issue
Block a user