Send fx's own events through the service's logger (closes #183)
check / check (push) Successful in 3m39s

fx printed its dependency graph and its start and stop hooks through its console logger on standard error. fx.WithLogger now hands those events to the service's logger: building the graph at DEBUG, the hooks, the start and the stopping signal at INFO, failures at ERROR. fx's own SlogLogger takes one level for everything that is not a failure, so the small FxLogger in internal/logger holds one at each level and picks by event. fx moves from v1.20.1, which has no SlogLogger, to v1.24.0, its current release. The README's logging passage now names only the Go runtime, whose output cannot be redirected.

Model: opus-5-5
This commit is contained in:
2026-10-02 13:50:56 +00:00
parent 88b961c115
commit ccb71948a3
6 changed files with 126 additions and 40 deletions
+54
View File
@@ -1,13 +1,23 @@
package logger_test
import (
"bytes"
"encoding/json"
"errors"
"log/slog"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/fx"
"go.uber.org/fx/fxevent"
"go.uber.org/fx/fxtest"
"sneak.berlin/go/webhooker/internal/globals"
"sneak.berlin/go/webhooker/internal/logger"
)
var errStopHook = errors.New("stop hook failed on purpose")
func testGlobals() *globals.Globals {
return &globals.Globals{
Appname: "test-app",
@@ -57,3 +67,47 @@ func TestEnableDebugLogging(t *testing.T) {
// Test debug logging
l.Get().Debug("debug message", "test", true)
}
// TestFxLogger_Levels starts and stops an fx app that reports its own
// events through NewFxLogger, as cmd/webhooker does, and reads back
// what reached the handler: the graph at DEBUG, the start at INFO and
// a failed stop hook at ERROR, each as a structured record.
func TestFxLogger_Levels(t *testing.T) {
t.Parallel()
var out bytes.Buffer
log := slog.New(slog.NewJSONHandler(
&out, &slog.HandlerOptions{Level: slog.LevelDebug},
))
app := fx.New(
fx.WithLogger(func() fxevent.Logger {
return logger.NewFxLogger(log)
}),
fx.Invoke(func(lc fx.Lifecycle) {
lc.Append(fx.StopHook(func() error { return errStopHook }))
}),
)
require.NoError(t, app.Start(t.Context()))
require.ErrorIs(t, app.Stop(t.Context()), errStopHook)
levels := map[string]string{}
decoder := json.NewDecoder(&out)
for decoder.More() {
var record struct {
Level string `json:"level"`
Msg string `json:"msg"`
}
require.NoError(t, decoder.Decode(&record))
levels[record.Msg] = record.Level
}
assert.Equal(t, "DEBUG", levels["provided"])
assert.Equal(t, "INFO", levels["started"])
assert.Equal(t, "ERROR", levels["OnStop hook failed"])
}