66 lines
2.2 KiB
Go
66 lines
2.2 KiB
Go
package reqid
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"log/slog"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// The id is ours. An id echoed from the caller lets them stamp their own value on our lines and
|
|
// correlate — or collide with — someone else's. Mutation caught: reading the header from the
|
|
// request when it is present.
|
|
func TestRequestIDIsNeverTakenFromTheCaller(t *testing.T) {
|
|
var seen string
|
|
h := Middleware(http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) {
|
|
seen = FromContext(r.Context())
|
|
}))
|
|
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
|
req.Header.Set(Header, "attacker-supplied")
|
|
rec := httptest.NewRecorder()
|
|
h.ServeHTTP(rec, req)
|
|
|
|
if seen == "" {
|
|
t.Fatal("no id was stamped")
|
|
}
|
|
if seen == "attacker-supplied" || rec.Header().Get(Header) == "attacker-supplied" {
|
|
t.Fatal("the caller's id was adopted")
|
|
}
|
|
if rec.Header().Get(Header) != seen {
|
|
t.Fatalf("the echoed id %q is not the one in the context %q", rec.Header().Get(Header), seen)
|
|
}
|
|
}
|
|
|
|
// Every *Context log call inside a request carries the id without the call site saying so — which
|
|
// is the point: the error paths that need correlating are the ones nobody remembers to annotate.
|
|
func TestLogRecordsCarryTheRequestID(t *testing.T) {
|
|
var buf bytes.Buffer
|
|
log := slog.New(WithContext(slog.NewJSONHandler(&buf, nil)))
|
|
|
|
h := Middleware(http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) {
|
|
log.With("layer", "test").ErrorContext(r.Context(), "something failed")
|
|
}))
|
|
h.ServeHTTP(httptest.NewRecorder(), httptest.NewRequest(http.MethodGet, "/", nil))
|
|
|
|
var rec map[string]any
|
|
if err := json.Unmarshal(bytes.TrimSpace(buf.Bytes()), &rec); err != nil {
|
|
t.Fatalf("log line: %v (%s)", err, buf.String())
|
|
}
|
|
if id, _ := rec[Key].(string); id == "" {
|
|
t.Fatalf("no %s on the record: %s", Key, buf.String())
|
|
}
|
|
if rec["layer"] != "test" {
|
|
t.Fatalf("the wrapper dropped attributes added with With: %s", buf.String())
|
|
}
|
|
|
|
// Outside a request there is nothing to add, and the handler must not invent one.
|
|
buf.Reset()
|
|
log.ErrorContext(context.Background(), "background failure")
|
|
if strings.Contains(buf.String(), Key) {
|
|
t.Fatalf("an id appeared outside a request: %s", buf.String())
|
|
}
|
|
}
|