From 06e6f0a5f27b934516bfc9cb1536cdd5cea5ab1c Mon Sep 17 00:00:00 2001 From: hkfires <10558748+hkfires@users.noreply.github.com> Date: Mon, 13 Oct 2025 08:38:57 +0800 Subject: [PATCH] refactor(watcher): Extract config change logging to new function --- internal/watcher/watcher.go | 206 +++++++++++++++++++++++++----------- 1 file changed, 143 insertions(+), 63 deletions(-) diff --git a/internal/watcher/watcher.go b/internal/watcher/watcher.go index e8586c2b..e30dfb0e 100644 --- a/internal/watcher/watcher.go +++ b/internal/watcher/watcher.go @@ -538,71 +538,16 @@ func (w *Watcher) reloadConfig() bool { log.Debugf("log level updated - debug mode changed from %t to %t", oldConfig.Debug, newConfig.Debug) } - // Log configuration changes in debug mode + // Log configuration changes in debug mode, only when there are material diffs if oldConfig != nil { - log.Debugf("config changes detected:") - if oldConfig.Port != newConfig.Port { - log.Debugf(" port: %d -> %d", oldConfig.Port, newConfig.Port) - } - if oldConfig.AuthDir != newConfig.AuthDir { - log.Debugf(" auth-dir: %s -> %s", oldConfig.AuthDir, newConfig.AuthDir) - } - if oldConfig.Debug != newConfig.Debug { - log.Debugf(" debug: %t -> %t", oldConfig.Debug, newConfig.Debug) - } - if oldConfig.ProxyURL != newConfig.ProxyURL { - log.Debugf(" proxy-url: %s -> %s", oldConfig.ProxyURL, newConfig.ProxyURL) - } - if oldConfig.RequestLog != newConfig.RequestLog { - log.Debugf(" request-log: %t -> %t", oldConfig.RequestLog, newConfig.RequestLog) - } - if oldConfig.RequestRetry != newConfig.RequestRetry { - log.Debugf(" request-retry: %d -> %d", oldConfig.RequestRetry, newConfig.RequestRetry) - } - if len(oldConfig.APIKeys) != len(newConfig.APIKeys) { - log.Debugf(" api-keys count: %d -> %d", len(oldConfig.APIKeys), len(newConfig.APIKeys)) - } - if len(oldConfig.GlAPIKey) != len(newConfig.GlAPIKey) { - log.Debugf(" generative-language-api-key count: %d -> %d", len(oldConfig.GlAPIKey), len(newConfig.GlAPIKey)) - } - if len(oldConfig.ClaudeKey) != len(newConfig.ClaudeKey) { - log.Debugf(" claude-api-key count: %d -> %d", len(oldConfig.ClaudeKey), len(newConfig.ClaudeKey)) - } - if len(oldConfig.CodexKey) != len(newConfig.CodexKey) { - log.Debugf(" codex-api-key count: %d -> %d", len(oldConfig.CodexKey), len(newConfig.CodexKey)) - } - if oldConfig.RemoteManagement.AllowRemote != newConfig.RemoteManagement.AllowRemote { - log.Debugf(" remote-management.allow-remote: %t -> %t", oldConfig.RemoteManagement.AllowRemote, newConfig.RemoteManagement.AllowRemote) - } - if oldConfig.RemoteManagement.SecretKey != newConfig.RemoteManagement.SecretKey { - switch { - case oldConfig.RemoteManagement.SecretKey == "" && newConfig.RemoteManagement.SecretKey != "": - log.Debug(" remote-management.secret-key: created") - case oldConfig.RemoteManagement.SecretKey != "" && newConfig.RemoteManagement.SecretKey == "": - log.Debug(" remote-management.secret-key: deleted") - default: - log.Debug(" remote-management.secret-key: updated") - } - if newConfig.RemoteManagement.SecretKey == "" { - log.Info("management routes will be disabled after secret key removal") - } else { - log.Info("management routes will be enabled after secret key update") - } - } - if oldConfig.RemoteManagement.DisableControlPanel != newConfig.RemoteManagement.DisableControlPanel { - log.Debugf(" remote-management.disable-control-panel: %t -> %t", oldConfig.RemoteManagement.DisableControlPanel, newConfig.RemoteManagement.DisableControlPanel) - } - if oldConfig.LoggingToFile != newConfig.LoggingToFile { - log.Debugf(" logging-to-file: %t -> %t", oldConfig.LoggingToFile, newConfig.LoggingToFile) - } - if oldConfig.UsageStatisticsEnabled != newConfig.UsageStatisticsEnabled { - log.Debugf(" usage-statistics-enabled: %t -> %t", oldConfig.UsageStatisticsEnabled, newConfig.UsageStatisticsEnabled) - } - if changes := diffOpenAICompatibility(oldConfig.OpenAICompatibility, newConfig.OpenAICompatibility); len(changes) > 0 { - log.Debugf(" openai-compatibility:") - for _, change := range changes { - log.Debugf(" %s", change) + details := buildConfigChangeDetails(oldConfig, newConfig) + if len(details) > 0 { + log.Debugf("config changes detected:") + for _, d := range details { + log.Debugf(" %s", d) } + } else { + log.Debugf("no material config field changes detected") } if oldConfig.QuotaExceeded.SwitchProject != newConfig.QuotaExceeded.SwitchProject { log.Debugf(" quota-exceeded.switch-project: %t -> %t", oldConfig.QuotaExceeded.SwitchProject, newConfig.QuotaExceeded.SwitchProject) @@ -1213,3 +1158,138 @@ func openAICompatKey(entry config.OpenAICompatibility, index int) (string, strin } return fmt.Sprintf("index:%d", index), fmt.Sprintf("entry-%d", index+1) } + +// buildConfigChangeDetails computes a redacted, human-readable list of config changes. +// It avoids printing secrets (like API keys) and focuses on structural or non-sensitive fields. +func buildConfigChangeDetails(oldCfg, newCfg *config.Config) []string { + changes := make([]string, 0, 16) + if oldCfg == nil || newCfg == nil { + return changes + } + + // Simple scalars + if oldCfg.Port != newCfg.Port { + changes = append(changes, fmt.Sprintf("port: %d -> %d", oldCfg.Port, newCfg.Port)) + } + if oldCfg.AuthDir != newCfg.AuthDir { + changes = append(changes, fmt.Sprintf("auth-dir: %s -> %s", oldCfg.AuthDir, newCfg.AuthDir)) + } + if oldCfg.Debug != newCfg.Debug { + changes = append(changes, fmt.Sprintf("debug: %t -> %t", oldCfg.Debug, newCfg.Debug)) + } + if oldCfg.LoggingToFile != newCfg.LoggingToFile { + changes = append(changes, fmt.Sprintf("logging-to-file: %t -> %t", oldCfg.LoggingToFile, newCfg.LoggingToFile)) + } + if oldCfg.UsageStatisticsEnabled != newCfg.UsageStatisticsEnabled { + changes = append(changes, fmt.Sprintf("usage-statistics-enabled: %t -> %t", oldCfg.UsageStatisticsEnabled, newCfg.UsageStatisticsEnabled)) + } + if oldCfg.RequestLog != newCfg.RequestLog { + changes = append(changes, fmt.Sprintf("request-log: %t -> %t", oldCfg.RequestLog, newCfg.RequestLog)) + } + if oldCfg.RequestRetry != newCfg.RequestRetry { + changes = append(changes, fmt.Sprintf("request-retry: %d -> %d", oldCfg.RequestRetry, newCfg.RequestRetry)) + } + if oldCfg.ProxyURL != newCfg.ProxyURL { + changes = append(changes, fmt.Sprintf("proxy-url: %s -> %s", oldCfg.ProxyURL, newCfg.ProxyURL)) + } + + // Quota-exceeded behavior + if oldCfg.QuotaExceeded.SwitchProject != newCfg.QuotaExceeded.SwitchProject { + changes = append(changes, fmt.Sprintf("quota-exceeded.switch-project: %t -> %t", oldCfg.QuotaExceeded.SwitchProject, newCfg.QuotaExceeded.SwitchProject)) + } + if oldCfg.QuotaExceeded.SwitchPreviewModel != newCfg.QuotaExceeded.SwitchPreviewModel { + changes = append(changes, fmt.Sprintf("quota-exceeded.switch-preview-model: %t -> %t", oldCfg.QuotaExceeded.SwitchPreviewModel, newCfg.QuotaExceeded.SwitchPreviewModel)) + } + + // API keys (redacted) and counts + if len(oldCfg.APIKeys) != len(newCfg.APIKeys) { + changes = append(changes, fmt.Sprintf("api-keys count: %d -> %d", len(oldCfg.APIKeys), len(newCfg.APIKeys))) + } else if !reflect.DeepEqual(trimStrings(oldCfg.APIKeys), trimStrings(newCfg.APIKeys)) { + changes = append(changes, "api-keys: values updated (count unchanged, redacted)") + } + if len(oldCfg.GlAPIKey) != len(newCfg.GlAPIKey) { + changes = append(changes, fmt.Sprintf("generative-language-api-key count: %d -> %d", len(oldCfg.GlAPIKey), len(newCfg.GlAPIKey))) + } else if !reflect.DeepEqual(trimStrings(oldCfg.GlAPIKey), trimStrings(newCfg.GlAPIKey)) { + changes = append(changes, "generative-language-api-key: values updated (count unchanged, redacted)") + } + + // Claude keys (do not print key material) + if len(oldCfg.ClaudeKey) != len(newCfg.ClaudeKey) { + changes = append(changes, fmt.Sprintf("claude-api-key count: %d -> %d", len(oldCfg.ClaudeKey), len(newCfg.ClaudeKey))) + } else { + for i := range oldCfg.ClaudeKey { + if i >= len(newCfg.ClaudeKey) { + break + } + o := oldCfg.ClaudeKey[i] + n := newCfg.ClaudeKey[i] + if strings.TrimSpace(o.BaseURL) != strings.TrimSpace(n.BaseURL) { + changes = append(changes, fmt.Sprintf("claude[%d].base-url: %s -> %s", i, strings.TrimSpace(o.BaseURL), strings.TrimSpace(n.BaseURL))) + } + if strings.TrimSpace(o.ProxyURL) != strings.TrimSpace(n.ProxyURL) { + changes = append(changes, fmt.Sprintf("claude[%d].proxy-url: %s -> %s", i, strings.TrimSpace(o.ProxyURL), strings.TrimSpace(n.ProxyURL))) + } + if strings.TrimSpace(o.APIKey) != strings.TrimSpace(n.APIKey) { + changes = append(changes, fmt.Sprintf("claude[%d].api-key: updated", i)) + } + } + } + + // Codex keys (do not print key material) + if len(oldCfg.CodexKey) != len(newCfg.CodexKey) { + changes = append(changes, fmt.Sprintf("codex-api-key count: %d -> %d", len(oldCfg.CodexKey), len(newCfg.CodexKey))) + } else { + for i := range oldCfg.CodexKey { + if i >= len(newCfg.CodexKey) { + break + } + o := oldCfg.CodexKey[i] + n := newCfg.CodexKey[i] + if strings.TrimSpace(o.BaseURL) != strings.TrimSpace(n.BaseURL) { + changes = append(changes, fmt.Sprintf("codex[%d].base-url: %s -> %s", i, strings.TrimSpace(o.BaseURL), strings.TrimSpace(n.BaseURL))) + } + if strings.TrimSpace(o.ProxyURL) != strings.TrimSpace(n.ProxyURL) { + changes = append(changes, fmt.Sprintf("codex[%d].proxy-url: %s -> %s", i, strings.TrimSpace(o.ProxyURL), strings.TrimSpace(n.ProxyURL))) + } + if strings.TrimSpace(o.APIKey) != strings.TrimSpace(n.APIKey) { + changes = append(changes, fmt.Sprintf("codex[%d].api-key: updated", i)) + } + } + } + + // Remote management (never print the key) + if oldCfg.RemoteManagement.AllowRemote != newCfg.RemoteManagement.AllowRemote { + changes = append(changes, fmt.Sprintf("remote-management.allow-remote: %t -> %t", oldCfg.RemoteManagement.AllowRemote, newCfg.RemoteManagement.AllowRemote)) + } + if oldCfg.RemoteManagement.DisableControlPanel != newCfg.RemoteManagement.DisableControlPanel { + changes = append(changes, fmt.Sprintf("remote-management.disable-control-panel: %t -> %t", oldCfg.RemoteManagement.DisableControlPanel, newCfg.RemoteManagement.DisableControlPanel)) + } + if oldCfg.RemoteManagement.SecretKey != newCfg.RemoteManagement.SecretKey { + switch { + case oldCfg.RemoteManagement.SecretKey == "" && newCfg.RemoteManagement.SecretKey != "": + changes = append(changes, "remote-management.secret-key: created") + case oldCfg.RemoteManagement.SecretKey != "" && newCfg.RemoteManagement.SecretKey == "": + changes = append(changes, "remote-management.secret-key: deleted") + default: + changes = append(changes, "remote-management.secret-key: updated") + } + } + + // OpenAI compatibility providers (summarized) + if compat := diffOpenAICompatibility(oldCfg.OpenAICompatibility, newCfg.OpenAICompatibility); len(compat) > 0 { + changes = append(changes, "openai-compatibility:") + for _, c := range compat { + changes = append(changes, " "+c) + } + } + + return changes +} + +func trimStrings(in []string) []string { + out := make([]string, len(in)) + for i := range in { + out[i] = strings.TrimSpace(in[i]) + } + return out +}