Skip to content

Commit 9a9390b

Browse files
anuraagaccoVeille
andauthored
fix: populate duration when handler panics (#31)
Co-authored-by: ccoVeille <[email protected]>
1 parent d3fc968 commit 9a9390b

File tree

2 files changed

+37
-9
lines changed

2 files changed

+37
-9
lines changed

capture_metrics.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,11 @@ func (m *Metrics) CaptureMetrics(w http.ResponseWriter, fn func(http.ResponseWri
8181
}
8282
)
8383

84+
// defer to ensure duration is updated even if the handler panics
85+
defer func() {
86+
m.Duration += time.Since(start)
87+
}()
8488
fn(Wrap(w, hooks))
85-
m.Duration += time.Since(start)
8689
}
8790

8891
// deadliner defines two methods introduced in go 1.20. The standard library

capture_metrics_test.go

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,20 @@ func TestCaptureMetrics(t *testing.T) {
1919
defer log.SetOutput(os.Stderr)
2020

2121
tests := []struct {
22+
Name string
2223
Handler http.Handler
2324
WantDuration time.Duration
2425
WantWritten int64
2526
WantCode int
2627
WantErr string
2728
}{
2829
{
30+
Name: "simple",
2931
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}),
3032
WantCode: http.StatusOK,
3133
},
3234
{
35+
Name: "headers and body",
3336
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
3437
w.WriteHeader(http.StatusBadRequest)
3538
w.WriteHeader(http.StatusNotFound)
@@ -42,13 +45,15 @@ func TestCaptureMetrics(t *testing.T) {
4245
WantDuration: 25 * time.Millisecond,
4346
},
4447
{
48+
Name: "header after body",
4549
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
4650
w.Write([]byte("foo"))
4751
w.WriteHeader(http.StatusNotFound)
4852
}),
4953
WantCode: http.StatusOK,
5054
},
5155
{
56+
Name: "reader",
5257
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
5358
rrf := w.(io.ReaderFrom)
5459
rrf.ReadFrom(strings.NewReader("reader from is ok"))
@@ -57,29 +62,49 @@ func TestCaptureMetrics(t *testing.T) {
5762
WantCode: http.StatusOK,
5863
},
5964
{
65+
Name: "empty panic",
6066
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
6167
panic("oh no")
6268
}),
63-
WantErr: "EOF",
69+
WantCode: http.StatusOK, // code is not written so is our default
70+
WantDuration: 1, // confirm non-zero
71+
WantErr: "EOF",
72+
},
73+
{
74+
Name: "panic after partial response",
75+
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
76+
w.WriteHeader(http.StatusInternalServerError)
77+
w.Write([]byte("failed to execute"))
78+
panic("oh no")
79+
}),
80+
WantCode: http.StatusInternalServerError,
81+
WantDuration: 1, // confirm non-zero
82+
WantWritten: 17,
83+
WantErr: "EOF",
6484
},
6585
}
6686

6787
for i, test := range tests {
68-
func() {
88+
t.Run(test.Name, func(t *testing.T) {
6989
ch := make(chan Metrics, 1)
7090
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
71-
ch <- CaptureMetrics(test.Handler, w, r)
91+
m := Metrics{Code: http.StatusOK}
92+
defer func() {
93+
ch <- m
94+
}()
95+
m.CaptureMetrics(w, func(ww http.ResponseWriter) {
96+
test.Handler.ServeHTTP(ww, r)
97+
})
7298
})
7399
s := httptest.NewServer(h)
74100
defer s.Close()
75101
res, err := http.Get(s.URL)
76102
if !errContains(err, test.WantErr) {
77103
t.Errorf("test %d: got=%s want=%s", i, err, test.WantErr)
78104
}
79-
if err != nil {
80-
return
81-
}
82-
defer res.Body.Close()
105+
if err == nil {
106+
defer res.Body.Close()
107+
}
83108
m := <-ch
84109
if m.Code != test.WantCode {
85110
t.Errorf("test %d: got=%d want=%d", i, m.Code, test.WantCode)
@@ -88,7 +113,7 @@ func TestCaptureMetrics(t *testing.T) {
88113
} else if m.Written < test.WantWritten {
89114
t.Errorf("test %d: got=%d want=%d", i, m.Written, test.WantWritten)
90115
}
91-
}()
116+
})
92117
}
93118
}
94119

0 commit comments

Comments
 (0)