diff --git a/go.mod b/go.mod index 618e2e9..2707998 100644 --- a/go.mod +++ b/go.mod @@ -17,6 +17,7 @@ require ( github.com/prometheus/client_golang v1.23.2 github.com/slok/go-http-metrics v0.13.0 github.com/spf13/cobra v1.10.2 + go.uber.org/dig v1.19.0 go.uber.org/fx v1.24.0 golang.org/x/crypto v0.41.0 golang.org/x/sync v0.19.0 @@ -126,7 +127,6 @@ require ( go.opentelemetry.io/otel v1.36.0 // indirect go.opentelemetry.io/otel/metric v1.36.0 // indirect go.opentelemetry.io/otel/trace v1.36.0 // indirect - go.uber.org/dig v1.19.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect diff --git a/internal/app/app.go b/internal/app/app.go index 0d9daf9..5b492f9 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -2,6 +2,7 @@ package app import ( + "context" "fmt" "os" "os/signal" @@ -9,6 +10,7 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/spf13/cobra" + "go.uber.org/dig" "go.uber.org/fx" "sneak.berlin/go/pixa/internal/config" "sneak.berlin/go/pixa/internal/database" @@ -53,7 +55,10 @@ func run(_ *cobra.Command, _ []string) { // A write to a closed stdout or stderr must not end the process. signal.Ignore(syscall.SIGPIPE) - fx.New( + app := fx.New( + // pixad logs with slog only; runApp prints a startup or shutdown + // error itself. + fx.NopLogger, fx.Provide( config.New, database.New, @@ -71,5 +76,40 @@ func run(_ *cobra.Command, _ []string) { func(log *logger.Logger) { log.Identify() }, func(*server.Server) {}, ), - ).Run() + ) + + exitCode := runApp(app) + if exitCode != 0 { + os.Exit(exitCode) + } +} + +// runApp starts app, waits for a signal or a shutdown request, stops app, +// and returns the status pixad exits with, as fx.App.Run does. It prints +// a start or stop error to stderr, once, as the error that caused it: +// dig.RootCause drops the frames fx and dig wrap around it. +func runApp(app *fx.App) int { + startCtx, cancelStart := context.WithTimeout(context.Background(), app.StartTimeout()) + defer cancelStart() + + err := app.Start(startCtx) + if err != nil { + fmt.Fprintln(os.Stderr, dig.RootCause(err)) + + return 1 + } + + shutdown := <-app.Wait() + + stopCtx, cancelStop := context.WithTimeout(context.Background(), app.StopTimeout()) + defer cancelStop() + + err = app.Stop(stopCtx) + if err != nil { + fmt.Fprintln(os.Stderr, dig.RootCause(err)) + + return 1 + } + + return shutdown.ExitCode }