Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion capture_metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,11 @@ func (m *Metrics) CaptureMetrics(w http.ResponseWriter, fn func(http.ResponseWri
}
)

// defer to ensure duration is updated even if the handler panics
defer func() {
m.Duration += time.Since(start)
}()
Comment thread
anuraaga marked this conversation as resolved.
fn(Wrap(w, hooks))
m.Duration += time.Since(start)
}

// deadliner defines two methods introduced in go 1.20. The standard library
Expand Down
39 changes: 32 additions & 7 deletions capture_metrics_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,20 @@ func TestCaptureMetrics(t *testing.T) {
defer log.SetOutput(os.Stderr)

tests := []struct {
Name string
Handler http.Handler
WantDuration time.Duration
WantWritten int64
WantCode int
WantErr string
}{
{
Name: "simple",
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}),
WantCode: http.StatusOK,
},
{
Name: "headers and body",
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusBadRequest)
w.WriteHeader(http.StatusNotFound)
Expand All @@ -42,13 +45,15 @@ func TestCaptureMetrics(t *testing.T) {
WantDuration: 25 * time.Millisecond,
},
{
Name: "header after body",
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("foo"))
w.WriteHeader(http.StatusNotFound)
}),
WantCode: http.StatusOK,
},
{
Name: "reader",
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
rrf := w.(io.ReaderFrom)
rrf.ReadFrom(strings.NewReader("reader from is ok"))
Expand All @@ -57,29 +62,49 @@ func TestCaptureMetrics(t *testing.T) {
WantCode: http.StatusOK,
},
{
Name: "empty panic",
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
panic("oh no")
}),
WantErr: "EOF",
WantCode: http.StatusOK, // code is not written so is our default
WantDuration: 1, // confirm non-zero
WantErr: "EOF",
},
{
Name: "panic after partial response",
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte("failed to execute"))
panic("oh no")
}),
WantCode: http.StatusInternalServerError,
WantDuration: 1, // confirm non-zero
WantWritten: 17,
WantErr: "EOF",
},
}

for i, test := range tests {
func() {
t.Run(test.Name, func(t *testing.T) {
ch := make(chan Metrics, 1)
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ch <- CaptureMetrics(test.Handler, w, r)
m := Metrics{Code: http.StatusOK}
defer func() {
ch <- m
}()
m.CaptureMetrics(w, func(ww http.ResponseWriter) {
test.Handler.ServeHTTP(ww, r)
})
})
s := httptest.NewServer(h)
defer s.Close()
res, err := http.Get(s.URL)
if !errContains(err, test.WantErr) {
t.Errorf("test %d: got=%s want=%s", i, err, test.WantErr)
}
if err != nil {
return
if res != nil && res.Body != nil {
defer res.Body.Close()
}
Comment thread
anuraaga marked this conversation as resolved.
Outdated
Comment thread
anuraaga marked this conversation as resolved.
Outdated
defer res.Body.Close()
m := <-ch
if m.Code != test.WantCode {
t.Errorf("test %d: got=%d want=%d", i, m.Code, test.WantCode)
Expand All @@ -88,7 +113,7 @@ func TestCaptureMetrics(t *testing.T) {
} else if m.Written < test.WantWritten {
t.Errorf("test %d: got=%d want=%d", i, m.Written, test.WantWritten)
}
}()
})
}
}

Expand Down