Print a startup or shutdown error once, without fx's event log (closes #359)
check / check (push) Successful in 1m51s

pixad builds its fx app with fx.NopLogger and runs start, wait and stop
itself, so that it can print a start or stop error to stderr once.
dig.RootCause strips the frames fx and dig wrap around the error, which
leaves the line that names the setting. The exit status is as before.

Two tests run runApp on a small fx app. A stop hook that fails has its
error printed once, with exit status 1. A shutdown request's exit code
is returned.

Model: opus-5-5
This commit is contained in:
sneak committed 2026-11-11 21:02:37 +00:00
1 parent 8aaf6dc794
commit 2679139492
3 files changed
+109 -3

No files matched your search

+43 -2
View File
@@ -2,13 +2,16 @@
package app
import (
"context"
"fmt"
"io"
"os"
"os/signal"
"syscall"
"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 +56,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 +77,40 @@ func run(_ *cobra.Command, _ []string) {
func(log *logger.Logger) { log.Identify() },
func(*server.Server) {},
),
).Run()
)
exitCode := runApp(app, os.Stderr)
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, stderr io.Writer) int {
startCtx, cancelStart := context.WithTimeout(context.Background(), app.StartTimeout())
defer cancelStart()
err := app.Start(startCtx)
if err != nil {
_, _ = fmt.Fprintln(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(stderr, dig.RootCause(err))
return 1
}
return shutdown.ExitCode
}