check / check (push) Waiting to run
fx printed its dependency graph and lifecycle hooks through its own console logger on standard error, so an operator shipping the JSON log to a collector got a second shape on a second stream for every start. The production app now passes fx.WithLogger with a small FxLogger in internal/logger that writes fx's events through the service's logger: graph events at debug, lifecycle at info, failures at error. Its constructor takes the configuration, so DEBUG=true applies before fx replays the events it held back. go.uber.org/fx moves from v1.20.1 to v1.24.0. Tests keep fx.NopLogger. The README says a failure before the logger exists, and the Go runtime's own output, still go to standard error as plain text. Model: opus-5-5
114 lines
2.4 KiB
Go
114 lines
2.4 KiB
Go
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",
|
|
Version: "1.0.0",
|
|
}
|
|
}
|
|
|
|
func TestNew(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
lc := fxtest.NewLifecycle(t)
|
|
|
|
params := logger.LoggerParams{
|
|
Globals: testGlobals(),
|
|
}
|
|
|
|
l, err := logger.New(lc, params)
|
|
if err != nil {
|
|
t.Fatalf("New() error = %v", err)
|
|
}
|
|
|
|
if l.Get() == nil {
|
|
t.Error("Get() returned nil logger")
|
|
}
|
|
|
|
// Test that we can log without panic
|
|
l.Get().Info("test message", "key", "value")
|
|
}
|
|
|
|
func TestEnableDebugLogging(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
lc := fxtest.NewLifecycle(t)
|
|
|
|
params := logger.LoggerParams{
|
|
Globals: testGlobals(),
|
|
}
|
|
|
|
l, err := logger.New(lc, params)
|
|
if err != nil {
|
|
t.Fatalf("New() error = %v", err)
|
|
}
|
|
|
|
// Enable debug logging should not panic
|
|
l.EnableDebugLogging()
|
|
|
|
// 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"])
|
|
}
|