From 00dd4f1b7ff4dbf9addfc0bafb87d552c0859355 Mon Sep 17 00:00:00 2001 From: Dominik Schmidt Date: Wed, 15 Jul 2026 18:32:04 +0200 Subject: [PATCH 1/2] fix(runtime): log service startup errors instead of printing them beside the log --- opencloud/pkg/runtime/service/service.go | 13 ++++++++++--- pkg/clihelper/app.go | 6 ++++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/opencloud/pkg/runtime/service/service.go b/opencloud/pkg/runtime/service/service.go index 13b0af8076..c360a623f2 100644 --- a/opencloud/pkg/runtime/service/service.go +++ b/opencloud/pkg/runtime/service/service.go @@ -389,9 +389,16 @@ func Start(ctx context.Context, o ...Option) error { if ev.Restarting { l = s.Log.Error() } - l.Str("event", e.String()).Str("service", ev.ServiceName).Str("supervisor", ev.SupervisorName). - Bool("restarting", ev.Restarting).Float64("failures", ev.CurrentFailures).Float64("threshold", ev.FailureThreshold). - Interface("error", ev.Err).Msg("service terminated") + l = l.Str("event", e.String()).Str("service", ev.ServiceName).Str("supervisor", ev.SupervisorName). + Bool("restarting", ev.Restarting).Float64("failures", ev.CurrentFailures).Float64("threshold", ev.FailureThreshold) + // ev.Err is an interface{}: marshaling an error yields {} because + // its fields are unexported, so the message has to go through Err + if err, ok := ev.Err.(error); ok { + l = l.Err(err) + } else { + l = l.Interface("error", ev.Err) + } + l.Msg("service terminated") case suture.EventBackoff: s.Log.Warn().Str("event", e.String()).Str("supervisor", ev.SupervisorName).Msg("service backoff") case suture.EventResume: diff --git a/pkg/clihelper/app.go b/pkg/clihelper/app.go index e2543faff0..2643391671 100644 --- a/pkg/clihelper/app.go +++ b/pkg/clihelper/app.go @@ -14,5 +14,11 @@ func DefaultApp(app *cobra.Command) *cobra.Command { // version info app.Version = fmt.Sprintf("%s (%s <%s>) (%s)", version.String, "OpenCloud GmbH", "support@opencloud.eu", version.Compiled()) + // a failing RunE is a runtime error, not a usage error: printing it here + // would put unstructured text next to the JSON log records, and the usage + // block on top of it is noise. main() reports what reaches it. + app.SilenceErrors = true + app.SilenceUsage = true + return app } From fec92d2f22ae63025eb1fa5d44cb8a1faf3053ad Mon Sep 17 00:00:00 2001 From: Dominik Schmidt Date: Wed, 15 Jul 2026 19:01:03 +0200 Subject: [PATCH 2/2] fix(runtime): keep the usage block for flag errors only --- pkg/clihelper/app.go | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/pkg/clihelper/app.go b/pkg/clihelper/app.go index 2643391671..e654165d45 100644 --- a/pkg/clihelper/app.go +++ b/pkg/clihelper/app.go @@ -14,11 +14,17 @@ func DefaultApp(app *cobra.Command) *cobra.Command { // version info app.Version = fmt.Sprintf("%s (%s <%s>) (%s)", version.String, "OpenCloud GmbH", "support@opencloud.eu", version.Compiled()) - // a failing RunE is a runtime error, not a usage error: printing it here - // would put unstructured text next to the JSON log records, and the usage - // block on top of it is noise. main() reports what reaches it. + // cobra would print the error on top of what main() already prints app.SilenceErrors = true - app.SilenceUsage = true + + // keep the usage block for flag parse errors, drop it once RunE runs. + // Traversing runs the hook even below a subcommand that brings its own, + // e.g. every service below ServiceCommand. + cobra.EnableTraverseRunHooks = true + app.PersistentPreRunE = func(cmd *cobra.Command, _ []string) error { + cmd.SilenceUsage = true + return nil + } return app }