Skip to content

Commit 2a1dbe3

Browse files
committed
refactored logging in the logger middleware and added tests for logger
1 parent a772430 commit 2a1dbe3

File tree

2 files changed

+77
-31
lines changed

2 files changed

+77
-31
lines changed

coderd/httpmw/logger.go

Lines changed: 27 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -35,74 +35,70 @@ func Logger(log slog.Logger) func(next http.Handler) http.Handler {
3535
slog.F("start", start),
3636
)
3737

38-
logContext := &RequestLoggerContext{
39-
log: &httplog,
40-
written: false,
41-
message: r.Method,
42-
}
43-
defer func() {
44-
logContext.WriteLog(r.Context(), sw.Status)
45-
}()
38+
logContext := NewRequestLoggerContext(httplog, r.Method, start)
4639

4740
ctx := context.WithValue(r.Context(), logContextKey{}, logContext)
4841

4942
next.ServeHTTP(sw, r.WithContext(ctx))
5043

51-
end := time.Now()
52-
5344
// Don't log successful health check requests.
5445
if r.URL.Path == "/api/v2" && sw.Status == http.StatusOK {
5546
return
5647
}
5748

58-
httplog = httplog.With(
59-
slog.F("took", end.Sub(start)),
60-
slog.F("status_code", sw.Status),
61-
slog.F("latency_ms", float64(end.Sub(start)/time.Millisecond)),
62-
)
63-
64-
// For status codes 400 and higher we
49+
// For status codes 500 and higher we
6550
// want to log the response body.
6651
if sw.Status >= http.StatusInternalServerError {
67-
httplog = httplog.With(
52+
logContext.WithFields(
6853
slog.F("response_body", string(sw.ResponseBody())),
6954
)
7055
}
7156

72-
// We should not log at level ERROR for 5xx status codes because 5xx
73-
// includes proxy errors etc. It also causes slogtest to fail
74-
// instantly without an error message by default.
75-
logLevelFn := httplog.Debug
76-
if sw.Status >= http.StatusInternalServerError {
77-
logLevelFn = httplog.Warn
78-
}
79-
8057
// We already capture most of this information in the span (minus
8158
// the response body which we don't want to capture anyways).
8259
tracing.RunWithoutSpan(r.Context(), func(ctx context.Context) {
83-
logLevelFn(ctx, r.Method)
60+
// logLevelFn(ctx, r.Method)
61+
logContext.WriteLog(r.Context(), sw.Status)
8462
})
8563
})
8664
}
8765
}
8866

8967
type RequestLoggerContext struct {
90-
log *slog.Logger
68+
log slog.Logger
9169
written bool
9270
message string
71+
start time.Time
72+
}
73+
74+
func NewRequestLoggerContext(log slog.Logger, message string, start time.Time) *RequestLoggerContext {
75+
return &RequestLoggerContext{
76+
log: log,
77+
written: false,
78+
message: message,
79+
start: start,
80+
}
9381
}
9482

9583
func (c *RequestLoggerContext) WithFields(fields ...slog.Field) {
96-
newLogger := c.log.With(fields...)
97-
c.log = &newLogger
84+
c.log = c.log.With(fields...)
9885
}
9986

10087
func (c *RequestLoggerContext) WriteLog(ctx context.Context, status int) {
10188
if c.written {
10289
return
10390
}
10491
c.written = true
105-
92+
end := time.Now()
93+
94+
c.WithFields(
95+
slog.F("took", end.Sub(c.start)),
96+
slog.F("status_code", status),
97+
slog.F("latency_ms", float64(end.Sub(c.start)/time.Millisecond)),
98+
)
99+
// We should not log at level ERROR for 5xx status codes because 5xx
100+
// includes proxy errors etc. It also causes slogtest to fail
101+
// instantly without an error message by default.
106102
if status >= http.StatusInternalServerError {
107103
c.log.Error(ctx, c.message, "status_code", status)
108104
} else {

coderd/httpmw/logger_test.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package httpmw
2+
3+
import (
4+
"context"
5+
"net/http"
6+
"testing"
7+
"time"
8+
9+
"cdr.dev/slog"
10+
"cdr.dev/slog/sloggers/slogtest"
11+
)
12+
13+
func TestRequestLoggerContext_WriteLog(t *testing.T) {
14+
t.Parallel()
15+
ctx := context.Background()
16+
17+
testLogger := slogtest.Make(t, nil)
18+
startTime := time.Now()
19+
20+
logCtx := NewRequestLoggerContext(testLogger, "GET", startTime)
21+
22+
// Add custom fields
23+
logCtx.WithFields(
24+
slog.F("custom_field", "custom_value"),
25+
)
26+
27+
// Write log for 200 status
28+
logCtx.WriteLog(ctx, http.StatusOK)
29+
30+
if !logCtxWrittenOnce(t, logCtx) {
31+
t.Error("expected log to be written once")
32+
}
33+
// Attempt to write again (should be skipped).
34+
// If the error log entry gets written,
35+
// slogtest will fail the test.
36+
logCtx.WriteLog(ctx, http.StatusInternalServerError)
37+
}
38+
39+
func logCtxWrittenOnce(t *testing.T, logCtx *RequestLoggerContext) bool {
40+
return logCtxWrittenField(t, logCtx)
41+
}
42+
43+
func logCtxWrittenField(t *testing.T, logCtx *RequestLoggerContext) bool {
44+
logCtxWritten := logCtx != nil && logCtx.written
45+
if !logCtxWritten {
46+
t.Error("log was not marked as written")
47+
return false
48+
}
49+
return true
50+
}

0 commit comments

Comments
 (0)