97 lines
2.4 KiB
Go
97 lines
2.4 KiB
Go
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)
|
|
}
|
|
})
|
|
}
|
|
}
|