feat: comprehensive debug logging across all controller modules

Add detailed [DEBUG] logging to every controller module when
logging.level is set to "debug". Each module with stateful debug
uses SetDebug(bool) wired from main.go. Covers stacks, backup,
cloudflare, integrations, system, monitor, settings, scheduler,
web handlers, storage, metrics, API, selfupdate, and assets.

Also includes the app export/import (.fab bundles) feature from
v0.32.0 and its debug page integration.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-26 18:14:43 +01:00
parent f6caea8067
commit 95c821deb2
54 changed files with 5015 additions and 82 deletions
@@ -20,6 +20,10 @@ func (m *Manager) OnStackStop(_ context.Context, stackName string) {
all[k] = v
}
if m.isDebug() {
m.logger.Printf("[DEBUG] [integrations] OnStackStop: stack=%s integrationsFound=%d", stackName, len(all))
}
for key, state := range all {
if !state.Enabled || state.Status == "disabled" {
continue
@@ -41,6 +45,9 @@ func (m *Manager) OnStackStop(_ context.Context, stackName string) {
continue
}
if m.isDebug() {
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)
}
@@ -59,6 +66,9 @@ func (m *Manager) OnStackStop(_ context.Context, stackName string) {
// Re-applies integrations that were previously enabled but are not currently active.
// Waits briefly for the stack manager to refresh container state.
func (m *Manager) OnStackStart(_ context.Context, stackName string) {
if m.isDebug() {
m.logger.Printf("[DEBUG] [integrations] OnStackStart: stack=%s, waiting 5s for state refresh", stackName)
}
// Brief delay so the stack manager's periodic status refresh
// picks up the new container state (runs every 30s).
time.Sleep(5 * time.Second)
@@ -72,6 +82,10 @@ func (m *Manager) OnStackStart(_ context.Context, stackName string) {
all[k] = v
}
if m.isDebug() {
m.logger.Printf("[DEBUG] [integrations] OnStackStart: stack=%s integrationsFound=%d", stackName, len(all))
}
for key, state := range all {
if !state.Enabled || state.Status == "active" {
continue
@@ -86,11 +100,17 @@ func (m *Manager) OnStackStart(_ context.Context, stackName string) {
// StateStarting = container running but healthcheck hasn't passed yet — still connectable.
provStack, pOk := m.stacks.GetStack(provider)
if !pOk || !provStack.Deployed || !isStackUp(provStack.State) {
if m.isDebug() {
m.logger.Printf("[DEBUG] [integrations] OnStackStart: skipping %s — provider %s not up (found=%v deployed=%v state=%v)", key, provider, pOk, pOk && provStack.Deployed, func() stacks.ContainerState { if pOk { return provStack.State }; return "" }())
}
continue
}
if target != "filebrowser" {
tgtStack, tOk := m.stacks.GetStack(target)
if !tOk || !tgtStack.Deployed || !isStackUp(tgtStack.State) {
if m.isDebug() {
m.logger.Printf("[DEBUG] [integrations] OnStackStart: skipping %s — target %s not up (found=%v deployed=%v state=%v)", key, target, tOk, tOk && tgtStack.Deployed, func() stacks.ContainerState { if tOk { return tgtStack.State }; return "" }())
}
continue
}
}
@@ -100,6 +120,10 @@ func (m *Manager) OnStackStart(_ context.Context, stackName string) {
continue
}
if m.isDebug() {
m.logger.Printf("[DEBUG] [integrations] OnStackStart: re-applying %s (currentStatus=%s)", key, state.Status)
}
ac, err := m.buildApplyContext(provider, target)
if err != nil {
m.logger.Printf("[WARN] Cannot re-apply integration %s on start: %v", key, err)
@@ -138,6 +162,10 @@ func (m *Manager) OnStackRemove(_ context.Context, stackName string) {
all[k] = v
}
if m.isDebug() {
m.logger.Printf("[DEBUG] [integrations] OnStackRemove: stack=%s integrationsFound=%d", stackName, len(all))
}
for key, state := range all {
provider, target, ok := ParseIntegrationKey(key)
if !ok {
@@ -145,6 +173,9 @@ func (m *Manager) OnStackRemove(_ context.Context, stackName string) {
}
if state.Enabled {
if m.isDebug() {
m.logger.Printf("[DEBUG] [integrations] OnStackRemove: revoking enabled integration %s", key)
}
handler, hOk := m.handlers[key]
if hOk {
ac, _ := m.buildApplyContext(provider, target)
@@ -31,6 +31,7 @@ type Manager struct {
handlers map[string]Handler // key: "provider:target" -> Handler
mu sync.Mutex // serialize apply/revoke operations
debug bool
}
// NewManager creates an integration manager and registers built-in handlers.
@@ -47,9 +48,28 @@ func NewManager(sett *settings.Settings, sp StackProvider, domain, stacksDir str
// Register built-in handlers
m.RegisterHandler("onlyoffice:filebrowser", &OnlyOfficeFileBrowserHandler{})
m.RegisterHandler("onlyoffice:nextcloud", &OnlyOfficeNextcloudHandler{})
if m.isDebug() {
keys := make([]string, 0, len(m.handlers))
for k := range m.handlers {
keys = append(keys, k)
}
m.logger.Printf("[DEBUG] [integrations] NewManager: registered handlers: %v", keys)
}
return m
}
// SetDebug enables or disables debug logging.
func (m *Manager) SetDebug(debug bool) {
m.debug = debug
}
// isDebug returns whether debug logging is enabled.
func (m *Manager) isDebug() bool {
return m.debug
}
// RegisterHandler registers a handler for a provider:target integration key.
func (m *Manager) RegisterHandler(key string, h Handler) {
m.handlers[key] = h
@@ -61,6 +81,10 @@ func (m *Manager) Toggle(ctx context.Context, provider, target string, enable bo
defer m.mu.Unlock()
key := IntegrationKey(provider, target)
if m.isDebug() {
m.logger.Printf("[DEBUG] [integrations] Toggle: key=%s provider=%s target=%s enable=%v", key, provider, target, enable)
}
handler, ok := m.handlers[key]
if !ok {
return settings.IntegrationState{}, fmt.Errorf("nincs kezelő a(z) %s integrációhoz", key)
@@ -75,6 +99,9 @@ func (m *Manager) Toggle(ctx context.Context, provider, target string, enable bo
if enable {
// Validate: provider must be deployed and running
provStack, pOk := m.stacks.GetStack(provider)
if m.isDebug() {
m.logger.Printf("[DEBUG] [integrations] Toggle: provider %s found=%v deployed=%v state=%v", provider, pOk, pOk && provStack.Deployed, func() stacks.ContainerState { if pOk { return provStack.State }; return "" }())
}
if !pOk || !provStack.Deployed {
return state, fmt.Errorf("a szolgáltató alkalmazás (%s) nincs telepítve", provider)
}
@@ -85,6 +112,9 @@ func (m *Manager) Toggle(ctx context.Context, provider, target string, enable bo
// Validate: target must be deployed and running (filebrowser is infra, always present)
if target != "filebrowser" {
tgtStack, tOk := m.stacks.GetStack(target)
if m.isDebug() {
m.logger.Printf("[DEBUG] [integrations] Toggle: target %s found=%v deployed=%v state=%v", target, tOk, tOk && tgtStack.Deployed, func() stacks.ContainerState { if tOk { return tgtStack.State }; return "" }())
}
if !tOk || !tgtStack.Deployed {
return state, fmt.Errorf("a célalkalmazás (%s) nincs telepítve", target)
}
@@ -93,7 +123,11 @@ func (m *Manager) Toggle(ctx context.Context, provider, target string, enable bo
}
}
start := time.Now()
if err := handler.Apply(ac); err != nil {
if m.isDebug() {
m.logger.Printf("[DEBUG] [integrations] Toggle: Apply failed for %s in %v: %v", key, time.Since(start), err)
}
state.Enabled = true
state.Status = "error"
state.LastError = err.Error()
@@ -101,15 +135,22 @@ func (m *Manager) Toggle(ctx context.Context, provider, target string, enable bo
_ = m.sett.SetIntegrationState(key, state)
return state, fmt.Errorf("integráció alkalmazása sikertelen: %w", err)
}
if m.isDebug() {
m.logger.Printf("[DEBUG] [integrations] Toggle: Apply succeeded for %s in %v", key, time.Since(start))
}
state.Enabled = true
state.Status = "active"
state.EnabledAt = time.Now().UTC().Format(time.RFC3339)
m.logger.Printf("[INFO] Integration %s enabled", key)
} else {
start := time.Now()
if err := handler.Revoke(ac); err != nil {
m.logger.Printf("[WARN] Integration revoke failed for %s: %v", key, err)
state.LastError = err.Error()
}
if m.isDebug() {
m.logger.Printf("[DEBUG] [integrations] Toggle: Revoke for %s completed in %v", key, time.Since(start))
}
state.Enabled = false
state.Status = "disabled"
m.logger.Printf("[INFO] Integration %s disabled", key)
@@ -128,6 +169,10 @@ func (m *Manager) ListForProvider(providerSlug string) []StatusInfo {
return nil
}
if m.isDebug() {
m.logger.Printf("[DEBUG] [integrations] ListForProvider: provider=%s integrationDefs=%d", providerSlug, len(provStack.Meta.Integrations))
}
var result []StatusInfo
for _, idef := range provStack.Meta.Integrations {
key := IntegrationKey(providerSlug, idef.Target)
@@ -171,6 +216,11 @@ func (m *Manager) buildApplyContext(provider, target string) (*ApplyContext, err
// Load decrypted env from provider's app.yaml
provEnv := m.loadDecryptedEnv(provStack)
if m.isDebug() {
envKeyCount := len(provEnv)
m.logger.Printf("[DEBUG] [integrations] buildApplyContext: provider=%s target=%s domain=%s envKeys=%d", provider, target, m.domain, envKeyCount)
}
provMeta := provStack.Meta
return &ApplyContext{
ProviderName: provider,
@@ -192,6 +242,9 @@ func (m *Manager) ReapplyConfigForTarget(targetName string) {
defer m.mu.Unlock()
all := m.sett.GetIntegrationsForTarget(targetName)
if m.isDebug() {
m.logger.Printf("[DEBUG] [integrations] ReapplyConfigForTarget: target=%s integrations=%d", targetName, len(all))
}
for key, state := range all {
if !state.Enabled || state.Status == "disabled" {
continue
@@ -207,6 +260,10 @@ func (m *Manager) ReapplyConfigForTarget(targetName string) {
continue
}
if m.isDebug() {
m.logger.Printf("[DEBUG] [integrations] ReapplyConfigForTarget: reapplying %s (status=%s)", key, state.Status)
}
ac, err := m.buildApplyContext(provider, target)
if err != nil {
m.logger.Printf("[WARN] Cannot build context for integration %s reapply: %v", key, err)
@@ -218,12 +275,18 @@ func (m *Manager) ReapplyConfigForTarget(targetName string) {
if err := handler.Apply(ac); err != nil {
m.logger.Printf("[WARN] Integration config reapply failed for %s: %v", key, err)
if m.isDebug() {
m.logger.Printf("[DEBUG] [integrations] ReapplyConfigForTarget: %s failed: %v", key, err)
}
state.Status = "error"
state.LastError = err.Error()
_ = m.sett.SetIntegrationState(key, state)
continue
}
if m.isDebug() {
m.logger.Printf("[DEBUG] [integrations] ReapplyConfigForTarget: %s succeeded", key)
}
state.Status = "active"
state.LastError = ""
_ = m.sett.SetIntegrationState(key, state)
@@ -241,5 +304,8 @@ func (m *Manager) loadDecryptedEnv(s *stacks.Stack) map[string]string {
if m.encKey != nil {
cfg.Env = crypto.DecryptMap(m.encKey, cfg.Env)
}
if m.isDebug() {
m.logger.Printf("[DEBUG] [integrations] loadDecryptedEnv: stack=%s envKeys=%d", s.Name, len(cfg.Env))
}
return cfg.Env
}
@@ -25,6 +25,10 @@ func (h *OnlyOfficeFileBrowserHandler) Apply(ac *ApplyContext) error {
}
configPath := filepath.Join(ac.StacksDir, "filebrowser", "config.yaml")
officeURL := fmt.Sprintf("https://%s.%s", subdomain, ac.Domain)
ac.Logger.Printf("[DEBUG] [integrations] OnlyOfficeFileBrowser.Apply: jwtSecretPresent=%v subdomain=%s configPath=%s officeURL=%s", jwtSecret != "", subdomain, configPath, officeURL)
configData, err := os.ReadFile(configPath)
if err != nil {
return fmt.Errorf("FileBrowser config olvasási hiba: %w", err)
@@ -33,7 +37,6 @@ func (h *OnlyOfficeFileBrowserHandler) Apply(ac *ApplyContext) error {
// Remove any existing integrations section, then append the new one
configStr := removeIntegrationsSection(string(configData))
officeURL := fmt.Sprintf("https://%s.%s", subdomain, ac.Domain)
internalURL := "http://onlyoffice:80"
integrationsBlock := fmt.Sprintf("integrations:\n office:\n url: %q\n internalUrl: %q\n secret: %q\n viewOnly: false\n",
@@ -57,6 +60,8 @@ func (h *OnlyOfficeFileBrowserHandler) Apply(ac *ApplyContext) error {
func (h *OnlyOfficeFileBrowserHandler) Revoke(ac *ApplyContext) error {
configPath := filepath.Join(ac.StacksDir, "filebrowser", "config.yaml")
ac.Logger.Printf("[DEBUG] [integrations] OnlyOfficeFileBrowser.Revoke: configPath=%s", configPath)
configData, err := os.ReadFile(configPath)
if err != nil {
if os.IsNotExist(err) {
@@ -66,7 +71,9 @@ func (h *OnlyOfficeFileBrowserHandler) Revoke(ac *ApplyContext) error {
}
cleaned := removeIntegrationsSection(string(configData))
if cleaned == string(configData) {
hadIntegrations := cleaned != string(configData)
ac.Logger.Printf("[DEBUG] [integrations] OnlyOfficeFileBrowser.Revoke: integrationsSectionFound=%v", hadIntegrations)
if !hadIntegrations {
return nil // no integrations section, nothing to remove
}