package middleware import ( "bytes" "encoding/json" "log/slog" "net/http" "net/http/httptest" "sync" "testing" "time" ) // lockedBuffer is a bytes.Buffer safe to write from the server's goroutine // while the test reads it. type lockedBuffer struct { mu sync.Mutex buffer bytes.Buffer } func (b *lockedBuffer) Write(data []byte) (int, error) { b.mu.Lock() defer b.mu.Unlock() return b.buffer.Write(data) } func (b *lockedBuffer) String() string { b.mu.Lock() defer b.mu.Unlock() return b.buffer.String() } // A request is logged at debug level, a hijacked connection at info, a 4xx // response at warn and a 5xx at error. func TestLoggerLevels(t *testing.T) { tests := []struct { name string handler http.HandlerFunc want string }{ {"ok", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) }, "DEBUG"}, {"not found", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNotFound) }, "WARN"}, {"server error", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusInternalServerError) }, "ERROR"}, {"hijacked", func(w http.ResponseWriter, r *http.Request) { conn, readWriter, err := http.NewResponseController(w).Hijack() if err != nil { t.Errorf("hijack: %v", err) return } defer conn.Close() readWriter.WriteString("HTTP/1.1 204 No Content\r\n\r\n") readWriter.Flush() }, "INFO"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var buffer lockedBuffer logger := slog.New(slog.NewJSONHandler(&buffer, &slog.HandlerOptions{ Level: slog.LevelDebug, })) server := httptest.NewServer( Logger(WithLogger(logger))(tt.handler), ) defer server.Close() res, err := http.Get(server.URL) if err != nil { t.Fatal(err) } res.Body.Close() // The line is written when the handler returns, which may be after // the client has its response. deadline := time.Now().Add(time.Second) for buffer.String() == "" && time.Now().Before(deadline) { time.Sleep(5 * time.Millisecond) } var entry struct { Level string `json:"level"` Msg string `json:"msg"` } if err := json.Unmarshal([]byte(buffer.String()), &entry); err != nil { t.Fatalf("log line %q: %v", buffer.String(), err) } if entry.Msg != "request" || entry.Level != tt.want { t.Errorf("logged %+v; want a request at %s", entry, tt.want) } }) } }