diff --git a/core/application/runtime_settings_branding_test.go b/core/application/runtime_settings_branding_test.go index 3e19901bd..5dc51bf66 100644 --- a/core/application/runtime_settings_branding_test.go +++ b/core/application/runtime_settings_branding_test.go @@ -261,4 +261,35 @@ var _ = Describe("loadRuntimeSettingsFromFile", func() { Expect(cfg.AgentPool.AgentHubURL).To(Equal("https://hub.acme.io")) }) }) + + // Backend logging capture. Worker/distributed mode force-enables it + // (core/services/worker.SetBackendLoggingEnabled(true)); single mode used + // to leave it off by default with no CLI flag, so the UI "Backend Logs" + // page was silently empty unless the operator found the Settings toggle. + // It now defaults on in single mode too. Because the default is on, the + // loader must let a persisted enable_backend_logging=false (the UI + // toggle-off) win over the default - the sticky "only flip false->true" + // merge used for env-backed flags would otherwise ignore it and revert + // the toggle on every restart. + Describe("backend logging capture", func() { + It("captures backend logs by default in single mode", func() { + cfg := config.NewApplicationConfig() + Expect(cfg.EnableBackendLogging).To(BeTrue(), + "single mode should capture backend logs out of the box, matching worker mode") + }) + + It("honors a persisted enable_backend_logging=false across restart (toggle-off wins over default-on)", func() { + cfg := config.NewApplicationConfig() // default-on boot state + cfg.DynamicConfigsDir = seedSettings(`{"enable_backend_logging": false}`) + loadRuntimeSettingsFromFile(cfg) + Expect(cfg.EnableBackendLogging).To(BeFalse(), + "a UI toggle-off persisted to runtime_settings.json must survive a restart") + }) + + It("loads a persisted enable_backend_logging=true", func() { + cfg := &config.ApplicationConfig{DynamicConfigsDir: seedSettings(`{"enable_backend_logging": true}`)} + loadRuntimeSettingsFromFile(cfg) + Expect(cfg.EnableBackendLogging).To(BeTrue()) + }) + }) }) diff --git a/core/application/startup.go b/core/application/startup.go index 1cf323d34..47445e117 100644 --- a/core/application/startup.go +++ b/core/application/startup.go @@ -698,10 +698,12 @@ func loadRuntimeSettingsFromFile(options *config.ApplicationConfig) { } } + // Backend logging defaults on in single mode (NewApplicationConfig), so + // the usual "only flip false->true" merge would ignore a persisted false + // and revert the UI toggle-off on every restart. There is no env var/CLI + // flag for it, so an explicit persisted value is authoritative here. if settings.EnableBackendLogging != nil { - if !options.EnableBackendLogging { - options.EnableBackendLogging = *settings.EnableBackendLogging - } + options.EnableBackendLogging = *settings.EnableBackendLogging } // Tracing settings diff --git a/core/config/application_config.go b/core/config/application_config.go index 042c9d826..2c863beb6 100644 --- a/core/config/application_config.go +++ b/core/config/application_config.go @@ -246,9 +246,16 @@ type AppOption func(*ApplicationConfig) func NewApplicationConfig(o ...AppOption) *ApplicationConfig { opt := &ApplicationConfig{ - Context: context.Background(), - UploadLimitMB: 15, - Debug: true, + Context: context.Background(), + UploadLimitMB: 15, + Debug: true, + // Capture backend process stdout/stderr into the per-model + // BackendLogStore by default so the UI "Backend Logs" page works out + // of the box in single mode, matching worker/distributed mode (which + // force-enables it). It's a small in-memory ring buffer; the Settings + // toggle can still turn it off (a persisted false wins - see + // loadRuntimeSettingsFromFile). + EnableBackendLogging: true, AgentJobRetentionDays: 30, // Default: 30 days LRUEvictionMaxRetries: 30, // Default: 30 retries LRUEvictionRetryInterval: 1 * time.Second, // Default: 1 second