summaryrefslogtreecommitdiff
path: root/pkg/logger/middleware.go
blob: 54cca96bb22c5f0f02c5d6f3ea9e374ce27ab9a5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
package logger

import (
	"log/slog"
	"net/http"
	"runtime/debug"
	"strconv"
	"time"
)

func Middleware(next http.Handler, recordBody bool) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method == "GET" && r.URL.Path == "/healthz" {
			next.ServeHTTP(w, r)
			return
		}

		defer func() {
			if err := recover(); err != nil {
				w.WriteHeader(http.StatusInternalServerError)
				slog.Error(
					"panic",
					"err", err,
					"trace", string(debug.Stack()),
				)
			}
		}()
		start := time.Now()
		path := r.URL.Path
		query := r.URL.RawQuery

		bw := newBodyWriter(w, recordBody)

		next.ServeHTTP(bw, r)

		end := time.Now()
		requestAttributes := []slog.Attr{
			slog.Time("time", start.UTC()),
			slog.String("method", r.Method),
			slog.String("host", r.Host),
			slog.String("path", path),
			slog.String("query", query),
			slog.String("ip", r.RemoteAddr),
		}
		responseAttributes := []slog.Attr{
			slog.Time("time", end.UTC()),
			slog.Duration("latency", end.Sub(start)),
			slog.Int("length", bw.bytes),
			slog.Int("status", bw.status),
		}
		if recordBody {
			responseAttributes = append(responseAttributes, slog.String("body", bw.body.String()))
		}
		attributes := []slog.Attr{
			{
				Key:   "request",
				Value: slog.GroupValue(requestAttributes...),
			},
			{
				Key:   "response",
				Value: slog.GroupValue(responseAttributes...),
			},
		}
		level := slog.LevelInfo
		if bw.status >= http.StatusInternalServerError {
			level = slog.LevelError
		} else if bw.status >= http.StatusBadRequest && bw.status < http.StatusInternalServerError {
			level = slog.LevelWarn
		}
		slog.LogAttrs(r.Context(), level, strconv.Itoa(bw.status)+": "+http.StatusText(bw.status), attributes...)
	})
}