Skip to content

Commit ff0aa8d

Browse files
authored
feat: add unique ids to all HTTP requests (#3845)
1 parent de219d9 commit ff0aa8d

File tree

4 files changed

+70
-0
lines changed

4 files changed

+70
-0
lines changed

coderd/audit/request.go

+2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"cdr.dev/slog"
1313
"github.com/coder/coder/coderd/database"
1414
"github.com/coder/coder/coderd/httpapi"
15+
"github.com/coder/coder/coderd/httpmw"
1516
)
1617

1718
type RequestParams struct {
@@ -69,6 +70,7 @@ func InitRequest[T Auditable](w http.ResponseWriter, p *RequestParams) (*Request
6970
Action: p.Action,
7071
Diff: diffRaw,
7172
StatusCode: int32(sw.Status),
73+
RequestID: httpmw.RequestID(p.Request),
7274
})
7375
if err != nil {
7476
p.Log.Error(ctx, "export audit log", slog.Error(err))

coderd/coderd.go

+1
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ func New(options *Options) *API {
155155
apiKeyMiddleware := httpmw.ExtractAPIKey(options.Database, oauthConfigs, false)
156156

157157
r.Use(
158+
httpmw.AttachRequestID,
158159
httpmw.Recover(api.Logger),
159160
httpmw.Logger(api.Logger),
160161
httpmw.Prometheus(options.PrometheusRegistry),

coderd/httpmw/requestid.go

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package httpmw
2+
3+
import (
4+
"context"
5+
"net/http"
6+
7+
"github.com/google/uuid"
8+
9+
"cdr.dev/slog"
10+
)
11+
12+
type requestIDContextKey struct{}
13+
14+
// RequestID returns the ID of the request.
15+
func RequestID(r *http.Request) uuid.UUID {
16+
rid, ok := r.Context().Value(requestIDContextKey{}).(uuid.UUID)
17+
if !ok {
18+
panic("developer error: request id middleware not provided")
19+
}
20+
return rid
21+
}
22+
23+
// AttachRequestID adds a request ID to each HTTP request.
24+
func AttachRequestID(next http.Handler) http.Handler {
25+
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
26+
rid := uuid.New()
27+
28+
ctx := context.WithValue(r.Context(), requestIDContextKey{}, rid)
29+
ctx = slog.With(ctx, slog.F("request_id", rid))
30+
31+
rw.Header().Set("X-Coder-Request-Id", rid.String())
32+
next.ServeHTTP(rw, r.WithContext(ctx))
33+
})
34+
}

coderd/httpmw/requestid_test.go

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package httpmw_test
2+
3+
import (
4+
"net/http"
5+
"net/http/httptest"
6+
"testing"
7+
8+
"github.com/go-chi/chi/v5"
9+
"github.com/stretchr/testify/require"
10+
11+
"github.com/coder/coder/coderd/httpmw"
12+
)
13+
14+
func TestRequestID(t *testing.T) {
15+
t.Parallel()
16+
17+
rtr := chi.NewRouter()
18+
rtr.Use(httpmw.AttachRequestID)
19+
rtr.Get("/", func(w http.ResponseWriter, r *http.Request) {
20+
rid := httpmw.RequestID(r)
21+
w.WriteHeader(http.StatusOK)
22+
w.Write([]byte(rid.String()))
23+
})
24+
r := httptest.NewRequest("GET", "/", nil)
25+
rw := httptest.NewRecorder()
26+
rtr.ServeHTTP(rw, r)
27+
28+
res := rw.Result()
29+
defer res.Body.Close()
30+
require.Equal(t, http.StatusOK, res.StatusCode)
31+
require.NotEmpty(t, res.Header.Get("X-Coder-Request-ID"))
32+
require.NotEmpty(t, rw.Body.Bytes())
33+
}

0 commit comments

Comments
 (0)