Skip to content

Commit 61569f5

Browse files
findleyrgopherbot
authored andcommitted
godev/internal/content: log errors in the content error handler
Generalize the existing error logging for /upload into the content server. This will help debug failures on the worker. Change-Id: I4460d458f3ac9c799ea67e38d4ce9e987306095d Reviewed-on: https://go-review.googlesource.com/c/telemetry/+/600715 Reviewed-by: Hongxiang Jiang <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]> Auto-Submit: Robert Findley <[email protected]>
1 parent af8248e commit 61569f5

File tree

4 files changed

+51
-25
lines changed

4 files changed

+51
-25
lines changed

godev/cmd/telemetrygodev/main.go

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ func newHandler(ctx context.Context, cfg *config.Config) http.Handler {
7676
mux.Handle("/", handleRoot(render, fsys, buckets.Chart, logger))
7777
mux.Handle("/config", handleConfig(fsys, ucfg))
7878
// TODO(rfindley): restrict this routing to POST
79-
mux.Handle("/upload/", handleUpload(ucfg, buckets.Upload, logger))
79+
mux.Handle("/upload/", handleUpload(ucfg, buckets.Upload))
8080
mux.Handle("/charts/", handleCharts(render, buckets.Chart))
8181
mux.Handle("/data/", handleData(render, buckets.Merge))
8282

@@ -267,28 +267,16 @@ func loadCharts(ctx context.Context, date string, bucket storage.BucketHandle) (
267267
return charts, nil
268268
}
269269

270-
func handleUpload(ucfg *tconfig.Config, uploadBucket storage.BucketHandle, log *slog.Logger) content.HandlerFunc {
270+
func handleUpload(ucfg *tconfig.Config, uploadBucket storage.BucketHandle) content.HandlerFunc {
271271
return func(w http.ResponseWriter, r *http.Request) error {
272272
if r.Method == "POST" {
273273
ctx := r.Context()
274-
// Log the error, but only the first 80 characters.
275-
// This prevents excessive logging related to broken payloads.
276-
// The first line should give us a sense of the failure mode.
277-
warn := func(msg string, err error) {
278-
errs := []rune(err.Error())
279-
if len(errs) > 80 {
280-
errs = append(errs[:79], '…')
281-
}
282-
log.WarnContext(ctx, msg+": "+string(errs))
283-
}
284274
var report telemetry.Report
285275
if err := json.NewDecoder(r.Body).Decode(&report); err != nil {
286-
warn("invalid JSON payload", err)
287-
return content.Error(err, http.StatusBadRequest)
276+
return content.Error(fmt.Errorf("invalid JSON payload: %v", err), http.StatusBadRequest)
288277
}
289278
if err := validate(&report, ucfg); err != nil {
290-
warn("invalid report", err)
291-
return content.Error(err, http.StatusBadRequest)
279+
return content.Error(fmt.Errorf("invalid report: %v", err), http.StatusBadRequest)
292280
}
293281
// TODO: capture metrics for collisions.
294282
name := fmt.Sprintf("%s/%g.json", report.Week, report.X)

godev/internal/content/content.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import (
3636
"fmt"
3737
"html/template"
3838
"io/fs"
39+
"log/slog"
3940
"net/http"
4041
"path"
4142
"strconv"
@@ -234,12 +235,20 @@ type contentError struct {
234235
func (e *contentError) Error() string { return e.err.Error() }
235236

236237
// handleErr writes an error as an HTTP response with a status code.
238+
//
239+
// err must be non-nil when calling this function.
237240
func handleErr(w http.ResponseWriter, req *http.Request, err error, code int) {
238-
// TODO(rfindley): should we log here? Do we need to scrub errors before
239-
// logging?
240241
if cerr, ok := err.(*contentError); ok {
241242
code = cerr.Code
242243
}
244+
// Log the error, but only the first 80 characters.
245+
// This prevents excessive logging related to broken payloads.
246+
// The first line should give us a sense of the failure mode.
247+
errs := []rune(err.Error())
248+
if len(errs) > 80 {
249+
errs = append(errs[:79], '…')
250+
}
251+
slog.WarnContext(req.Context(), fmt.Sprintf("request for %q failed with status %d: %s", req.URL.Path, code, string(errs)))
243252
if code == http.StatusInternalServerError {
244253
http.Error(w, http.StatusText(http.StatusInternalServerError), code)
245254
} else {

godev/internal/content/content_test.go

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@
55
package content
66

77
import (
8+
"bytes"
89
"errors"
910
"io/fs"
11+
"log/slog"
1012
"net/http"
1113
"net/http/httptest"
1214
"os"
@@ -21,6 +23,13 @@ import (
2123
func TestServer_ServeHTTP(t *testing.T) {
2224
testenv.NeedsGo1Point(t, 23) // output of some http helpers changed in Go 1.23
2325
fsys := os.DirFS("testdata")
26+
27+
// Reset the default logger after this test.
28+
// The default loggers is mutated in the tests below.
29+
defer func(l *slog.Logger) {
30+
slog.SetDefault(l)
31+
}(slog.Default())
32+
2433
server := Server(fsys,
2534
Handler("/data", handleTemplate(fsys)),
2635
Handler("/json", handleJSON()),
@@ -30,78 +39,95 @@ func TestServer_ServeHTTP(t *testing.T) {
3039
)
3140

3241
tests := []struct {
33-
path string
34-
wantOut string
35-
wantCode int
42+
path string
43+
wantOut string
44+
wantCode int
45+
wantLogContaining string // if empty, expect no logs
3646
}{
3747
{
3848
"/index.html",
3949
"redirect.html.out",
4050
http.StatusMovedPermanently,
51+
"",
4152
},
4253
{
4354
"/index",
4455
"redirect.out",
4556
http.StatusMovedPermanently,
57+
"",
4658
},
4759
{
4860
"/json",
4961
"json.out",
5062
http.StatusOK,
63+
"",
5164
},
5265
{
5366
"/text",
5467
"text.out",
5568
http.StatusOK,
69+
"",
5670
},
5771
{
5872
"/error",
5973
"error.out",
6074
http.StatusBadRequest,
75+
"Oh no",
6176
},
6277
{
6378
"/teapot",
6479
"teapot.out",
6580
http.StatusTeapot,
81+
"418",
6682
},
6783
{
6884
"/style.css",
6985
"style.css.out",
7086
http.StatusOK,
87+
"",
7188
},
7289
{
7390
"/",
7491
"index.html.out",
7592
http.StatusOK,
93+
"",
7694
},
7795
{
7896
"/data",
7997
"data.html.out",
8098
http.StatusOK,
99+
"",
81100
},
82101
{
83102
"/markdown",
84103
"markdown.md.out",
85104
http.StatusOK,
105+
"",
86106
},
87107
{
88108
"/404",
89109
"404.html.out",
90110
http.StatusNotFound,
111+
"404",
91112
},
92113
{
93114
"/subdir",
94115
"subdir/index.html.out",
95116
http.StatusOK,
117+
"",
96118
},
97119
{
98120
"/noindex/",
99121
"noindex/noindex.html.out",
100122
http.StatusOK,
123+
"",
101124
},
102125
}
103126
for _, tt := range tests {
104127
t.Run(tt.path, func(t *testing.T) {
128+
var buf bytes.Buffer
129+
slog.SetDefault(slog.New(slog.NewTextHandler(&buf, nil)))
130+
105131
rr := httptest.NewRecorder()
106132
req, err := http.NewRequest("GET", tt.path, nil)
107133
if err != nil {
@@ -115,10 +141,13 @@ func TestServer_ServeHTTP(t *testing.T) {
115141
}
116142
wantBody := strings.TrimSpace(string(data))
117143
if diff := cmp.Diff(wantBody, got); diff != "" {
118-
t.Errorf("GET %s response body mismatch (-want, +got):\n%s", tt.path, diff)
144+
t.Errorf("response body mismatch (-want, +got):\n%s", diff)
119145
}
120146
if rr.Code != tt.wantCode {
121-
t.Errorf("GET %s response code = %d, want %d", tt.path, rr.Code, tt.wantCode)
147+
t.Errorf("response code = %d, want %d", rr.Code, tt.wantCode)
148+
}
149+
if !strings.Contains(buf.String(), tt.wantLogContaining) {
150+
t.Errorf("no log containing %q", tt.wantLogContaining)
122151
}
123152
})
124153
}
@@ -169,6 +198,6 @@ func handleTeapot() HandlerFunc {
169198

170199
func handleError() HandlerFunc {
171200
return func(w http.ResponseWriter, r *http.Request) error {
172-
return Error(errors.New("Bad Request"), http.StatusBadRequest)
201+
return Error(errors.New("Oh no! Bad Request"), http.StatusBadRequest)
173202
}
174203
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
Bad Request
1+
Oh no! Bad Request

0 commit comments

Comments
 (0)