From 4cea479d426017db3f8f56311d822d01edc06342 Mon Sep 17 00:00:00 2001 From: Jan Tytgat Date: Tue, 29 Apr 2025 14:46:04 +0200 Subject: [PATCH] Refactoring - logging interface - log levels - application interface Signed-off-by: Jan Tytgat --- application/application.go | 88 ++++++++++++++++++++++---------------- application/config.go | 15 +++---- application/globals.go | 2 +- application/logging.go | 29 ++++++++++--- go.mod | 2 +- go.sum | 2 + 6 files changed, 85 insertions(+), 53 deletions(-) diff --git a/application/application.go b/application/application.go index 7e5c3a9..de76de1 100644 --- a/application/application.go +++ b/application/application.go @@ -2,15 +2,17 @@ package application import ( "context" + "errors" "fmt" + "log/slog" "os/signal" + "git.flexabyte.io/flexabyte/go-slogd/slogd" "github.com/spf13/cobra" ) type Application interface { - Start(ctx context.Context) error - Shutdown() error + ExecuteContext(ctx context.Context) error } func New(c Config) (Application, error) { @@ -31,53 +33,67 @@ type application struct { config Config } -func (a *application) Start(ctx context.Context) error { +func (a *application) ExecuteContext(ctx context.Context) error { + a.config.Logger.LogAttrs(ctx, slogd.LevelTrace, "configuring application shutdown signals", slog.Any("signals", a.config.ShutdownSignals)) - sigCtx, sigStop := signal.NotifyContext(ctx, a.config.ShutdownSignals...) - defer sigStop() // Ensure that this gets called. + sigCtx, sigCancel := signal.NotifyContext(ctx, a.config.ShutdownSignals...) + defer sigCancel() // Ensure that this gets called. - // Result channel for command + // Result channel for command output chExe := make(chan error) - go a.execute(sigCtx, chExe) + // Run the application command + a.config.Logger.LogAttrs(ctx, slogd.LevelTrace, "executing application context") + go func(ctx context.Context, chErr chan error) { + chErr <- a.cmd.ExecuteContext(ctx) + }(sigCtx, chExe) + + // Wait for command output or a shutdown signal + var err error select { - // sigCtx.Done() returns a channel that will have a message - // when the context is cancelled. We wait for that signal, which means - // we received the signal, or our context was cancelled for some other reason. + // sigCtx.Done() returns a channel that will have a message when the context is canceled. + // Alternatively, chExe will receive the response from the execution context if the application finishes. case <-sigCtx.Done(): - sigStop() - return a.Shutdown() - case err := <-chExe: + sigCancel() + + // Adapt the shutdown scenario if a graceful shutdown period is configured + switch a.config.EnableGracefulShutdown && a.config.ShutdownTimeout > 0 { + case true: + a.config.Logger.LogAttrs(ctx, slogd.LevelTrace, "gracefully shutting down application") + if err = a.gracefulShutdown(ctx); !errors.Is(err, context.DeadlineExceeded) { + a.config.Logger.LogAttrs(ctx, slogd.LevelTrace, "graceful shutdown completed with error", slog.Any("error", err)) + return err + } + a.config.Logger.LogAttrs(ctx, slogd.LevelTrace, "graceful shutdown completed") + return nil + case false: + a.config.Logger.LogAttrs(ctx, slogd.LevelTrace, "immediately shutting down application") + return nil + } + case err = <-chExe: + a.config.Logger.LogAttrs(ctx, slogd.LevelTrace, "application terminated successfully") return err } -} -func (a *application) Shutdown() error { - switch a.config.EnableGracefulShutdown { - case true: - return a.gracefulShutdown() - case false: - return a.shutdown() - // default: - // return a.shutdown() - } + a.config.Logger.LogAttrs(ctx, slogd.LevelTrace, "application terminated unexpectedly") return nil } -func (a *application) execute(ctx context.Context, chErr chan error) { - chErr <- a.cmd.ExecuteContext(ctx) -} +func (a *application) gracefulShutdown(ctx context.Context) error { + fmt.Printf("waiting %s for graceful application shutdown... PRESS CTRL+C again to quit now!\n", a.config.ShutdownTimeout) -func (a *application) gracefulShutdown() error { - fmt.Println("graceful shutdown") - - shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), a.config.ShutdownTimeout) + shutdownCtx, shutdownCancel := context.WithTimeout(ctx, a.config.ShutdownTimeout) defer shutdownCancel() - <-shutdownCtx.Done() - return nil -} -func (a *application) shutdown() error { - fmt.Println("shutdown") - return nil + // Wait for the shutdown timeout or a hard exit signal + sigCtx, sigCancel := signal.NotifyContext(shutdownCtx, a.config.ShutdownSignals...) + defer sigCancel() // Ensure that this gets called. + + select { + case <-shutdownCtx.Done(): + return shutdownCtx.Err() + case <-sigCtx.Done(): + sigCancel() + return nil + } } diff --git a/application/config.go b/application/config.go index 4014e15..1939697 100644 --- a/application/config.go +++ b/application/config.go @@ -3,6 +3,7 @@ package application import ( "errors" "fmt" + "log/slog" "os" "time" @@ -15,8 +16,9 @@ type Config struct { Name string Title string Banner string - Version string + Version Version EnableGracefulShutdown bool + Logger *slog.Logger OverrideRunE func(cmd *cobra.Command, args []string) error PersistentPreRunE []func(cmd *cobra.Command, args []string) error // collection of PreRunE functions PersistentPostRunE []func(cmd *cobra.Command, args []string) error // collection of PostRunE functions @@ -59,12 +61,7 @@ func (c Config) getRootCommand() (*cobra.Command, error) { cmd.AddCommand(subcommand.Initialize(c.SubCommandInitializeFunc)) } - var v semver.Version - if v, err = c.ParseVersion(); err != nil { - return nil, err - } - - configureVersionFlag(cmd, v) // Configure app for version information + configureVersionFlag(cmd, c.Version) // Configure app for version information configureOutputFlags(cmd) // Configure verbosity configureLoggingFlags(cmd) // Configure logging cmd.PersistentFlags().SetNormalizeFunc(normalizeFunc) // normalize persistent flags @@ -73,7 +70,7 @@ func (c Config) getRootCommand() (*cobra.Command, error) { } func (c Config) ParseVersion() (semver.Version, error) { - return semver.Parse(c.Version) + return semver.Parse(c.Version.Full) } func (c Config) RegisterCommand(cmd Commander, f func(*cobra.Command)) { @@ -103,7 +100,7 @@ func (c Config) Validate() error { } var err error - if _, err = semver.Parse(c.Version); err != nil { + if _, err = semver.Parse(c.Version.Full); err != nil { return fmt.Errorf("invalid version: %s", c.Version) } return nil diff --git a/application/globals.go b/application/globals.go index b930255..4c2ff0d 100644 --- a/application/globals.go +++ b/application/globals.go @@ -66,7 +66,7 @@ func persistentPreRunFuncE(cmd *cobra.Command, args []string) error { // TODO move to front?? if quietFlag { - slogd.FromContext(cmd.Context()).LogAttrs(cmd.Context(), slogd.LevelDebug, "activating quiet mode") + slogd.FromContext(cmd.Context()).LogAttrs(cmd.Context(), slogd.LevelTrace, "activating quiet mode") outWriter = io.Discard } diff --git a/application/logging.go b/application/logging.go index af7680b..341ff63 100644 --- a/application/logging.go +++ b/application/logging.go @@ -1,6 +1,9 @@ package application import ( + "log/slog" + + "git.flexabyte.io/flexabyte/go-slogd/slogd" "github.com/spf13/cobra" ) @@ -10,20 +13,25 @@ const ( LogOutputFile = "file" ) -var logLevelFlag string -var logOutputFlag string -var logTypeFlag string +var ( + logLevelFlagName string = "log-level" + logLevelFlag string + logOutputFlagName string = "log-output" + logOutputFlag string + logTypeFlagName = "log-type" + logTypeFlag string +) func addLogLevelFlag(cmd *cobra.Command) { - cmd.PersistentFlags().StringVarP(&logLevelFlag, "log-level", "", "info", "Set log level (trace, debug, info, warn, error, fatal)") + cmd.PersistentFlags().StringVarP(&logLevelFlag, logLevelFlagName, "", "info", "Set log level (trace, debug, info, warn, error, fatal)") } func addLogOutputFlag(cmd *cobra.Command) { - cmd.PersistentFlags().StringVarP(&logOutputFlag, "log-outWriter", "", "stderr", "Set log outWriter (stdout, stderr, file)") + cmd.PersistentFlags().StringVarP(&logOutputFlag, logOutputFlagName, "", "stderr", "Set log output (stdout, stderr, file)") } func addLogTypeFlag(cmd *cobra.Command) { - cmd.PersistentFlags().StringVarP(&logTypeFlag, "log-type", "", "text", "Set log type (text, json, color)") + cmd.PersistentFlags().StringVarP(&logTypeFlag, logTypeFlagName, "", "text", "Set log type (text, json, color)") } func configureLoggingFlags(cmd *cobra.Command) { @@ -33,3 +41,12 @@ func configureLoggingFlags(cmd *cobra.Command) { cmd.MarkFlagsMutuallyExclusive("no-color", "log-type") } + +func GetLogLevelFromArgs(args []string) slog.Level { + for i, arg := range args { + if arg == "--log-level" && i+1 < len(args) { + return slogd.Level(args[i+1]) + } + } + return slogd.LevelDefault +} diff --git a/go.mod b/go.mod index 54bc049..0d141a3 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module git.flexabyte.io/flexabyte/go-kit go 1.24.2 require ( - git.flexabyte.io/flexabyte/go-slogd v0.0.0-20250422113213-08cde7c3729c + git.flexabyte.io/flexabyte/go-slogd v0.0.0-20250428200220-8e65f81d9450 github.com/spf13/cobra v1.9.1 github.com/spf13/pflag v1.0.6 ) diff --git a/go.sum b/go.sum index ae6f969..bea345e 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,8 @@ git.flexabyte.io/flexabyte/go-slogd v0.0.0-20250416190113-64c1cac4d274 h1:aR9nV8 git.flexabyte.io/flexabyte/go-slogd v0.0.0-20250416190113-64c1cac4d274/go.mod h1:rL08OHw4aycfjkZOS8pBfLapeG3IZHxIInW29hVVSrI= git.flexabyte.io/flexabyte/go-slogd v0.0.0-20250422113213-08cde7c3729c h1:8ZVNJJ/LNbu4aWozJ6wj0ZOZFeuqRh1xSyQRh+Cq1yM= git.flexabyte.io/flexabyte/go-slogd v0.0.0-20250422113213-08cde7c3729c/go.mod h1:rL08OHw4aycfjkZOS8pBfLapeG3IZHxIInW29hVVSrI= +git.flexabyte.io/flexabyte/go-slogd v0.0.0-20250428200220-8e65f81d9450 h1:VCstITW9pMgR5EnSLU83UiK7llLOguFLAo26VOOnzrI= +git.flexabyte.io/flexabyte/go-slogd v0.0.0-20250428200220-8e65f81d9450/go.mod h1:rL08OHw4aycfjkZOS8pBfLapeG3IZHxIInW29hVVSrI= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=