diff --git a/backend/internal/bootstrap/router_bootstrap.go b/backend/internal/bootstrap/router_bootstrap.go index d92f7174..bdc98842 100644 --- a/backend/internal/bootstrap/router_bootstrap.go +++ b/backend/internal/bootstrap/router_bootstrap.go @@ -404,7 +404,9 @@ func initLogger(r *gin.Engine) { r.Use(sloggin.SetLogger( sloggin.WithLogger(func(_ *gin.Context, _ *slog.Logger) *slog.Logger { - return slog.Default() + // gin-contrib/slog calls Handler.Handle directly instead of Logger.LogAttrs + // Wrapping the default handler in MultiHandler restores the Enabled check that enforces LOG_LEVEL + return slog.New(slog.NewMultiHandler(slog.Default().Handler())) }), sloggin.WithClientErrorLevel(slog.LevelInfo), sloggin.WithSpecificLogLevelByStatusCode(map[int]slog.Level{ diff --git a/backend/internal/bootstrap/router_bootstrap_test.go b/backend/internal/bootstrap/router_bootstrap_test.go index 7561aaf9..288e7525 100644 --- a/backend/internal/bootstrap/router_bootstrap_test.go +++ b/backend/internal/bootstrap/router_bootstrap_test.go @@ -78,3 +78,55 @@ func TestRequestLoggerKeepsRateLimitsAtWarningLevel(t *testing.T) { require.Contains(t, output.String(), "level=WARN") require.Contains(t, output.String(), "error_code=rate_limited") } + +func TestRequestLoggerRespectsConfiguredMinimumLevel(t *testing.T) { + gin.SetMode(gin.TestMode) + + var output bytes.Buffer + previousLogger := slog.Default() + slog.SetDefault(slog.New(slog.NewTextHandler(&output, &slog.HandlerOptions{ + Level: slog.LevelWarn, + }))) + t.Cleanup(func() { + slog.SetDefault(previousLogger) + }) + + router := gin.New() + initLogger(router) + router.GET("/api/status", func(c *gin.Context) { + c.Status(http.StatusNoContent) + }) + + recorder := httptest.NewRecorder() + request := httptest.NewRequestWithContext(t.Context(), http.MethodGet, "/api/status", nil) + router.ServeHTTP(recorder, request) + + require.Equal(t, http.StatusNoContent, recorder.Code) + require.Empty(t, output.String()) +} + +func TestRequestLoggerLogsAtConfiguredMinimumLevel(t *testing.T) { + gin.SetMode(gin.TestMode) + + var output bytes.Buffer + previousLogger := slog.Default() + slog.SetDefault(slog.New(slog.NewTextHandler(&output, &slog.HandlerOptions{ + Level: slog.LevelInfo, + }))) + t.Cleanup(func() { + slog.SetDefault(previousLogger) + }) + + router := gin.New() + initLogger(router) + router.GET("/api/status", func(c *gin.Context) { + c.Status(http.StatusNoContent) + }) + + recorder := httptest.NewRecorder() + request := httptest.NewRequestWithContext(t.Context(), http.MethodGet, "/api/status", nil) + router.ServeHTTP(recorder, request) + + require.Equal(t, http.StatusNoContent, recorder.Code) + require.Contains(t, output.String(), "level=INFO") +}