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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
|
package logging
import (
"context"
"crypto/sha256"
"encoding/hex"
"errors"
"io"
"net"
"time"
"tildegit.org/tjp/sliderule/internal/types"
)
func LogRequests(logger Logger) types.Middleware {
return func(inner types.Handler) types.Handler {
return types.HandlerFunc(func(ctx context.Context, request *types.Request) *types.Response {
start := time.Now()
var clientip string
if request.RemoteAddr != nil {
clientip, _, _ = net.SplitHostPort(request.RemoteAddr.String())
}
response := inner.Handle(ctx, request)
if response != nil {
response.Body = loggingBody(logger, request, response, start, clientip)
} else {
end := time.Now()
params := []any{
"msg", "request",
"ts", end.UTC(),
"dur", end.Sub(start),
"url", request.URL,
"status", "(not found)",
"clientip", clientip,
}
if fingerprint, ok := clientFingerprint(request); ok {
params = append(params, "client_ident", fingerprint)
}
_ = logger.Log(params...)
}
return response
})
}
}
func clientFingerprint(request *types.Request) (string, bool) {
if request.TLSState == nil || len(request.TLSState.PeerCertificates) == 0 {
return "", false
}
digest := sha256.Sum256(request.TLSState.PeerCertificates[0].Raw)
return hex.EncodeToString(digest[:]), true
}
type loggedResponseBody struct {
request *types.Request
response *types.Response
body io.Reader
start time.Time
clientip string
written int
logger Logger
}
func (lr *loggedResponseBody) log() {
end := time.Now()
params := []any{
"msg", "request",
"ts", end.UTC(),
"dur", end.Sub(lr.start),
"url", lr.request.URL,
"status", lr.response.Status,
"clientip", lr.clientip,
"bodylen", lr.written,
}
if fingerprint, ok := clientFingerprint(lr.request); ok {
params = append(params, "client_ident", fingerprint)
}
_ = lr.logger.Log(params...)
}
func (lr *loggedResponseBody) Read(b []byte) (int, error) {
if lr.body == nil {
lr.log()
return 0, io.EOF
}
wr, err := lr.body.Read(b)
lr.written += wr
if errors.Is(err, io.EOF) {
lr.log()
}
return wr, err
}
func (lr *loggedResponseBody) Close() error {
if cl, ok := lr.body.(io.Closer); ok {
return cl.Close()
}
return nil
}
type loggedWriteToResponseBody struct {
*loggedResponseBody
}
func (lwtr loggedWriteToResponseBody) WriteTo(dst io.Writer) (int64, error) {
n, err := lwtr.body.(io.WriterTo).WriteTo(dst)
if err == nil {
lwtr.written += int(n)
lwtr.log()
}
return n, err
}
func loggingBody(logger Logger, request *types.Request, response *types.Response, start time.Time, clientip string) io.Reader {
body := &loggedResponseBody{
request: request,
response: response,
body: response.Body,
start: start,
written: 0,
logger: logger,
clientip: clientip,
}
if _, ok := response.Body.(io.WriterTo); ok {
return loggedWriteToResponseBody{body}
}
return body
}
|