Skip to content

Commit 171330f

Browse files
committed
Implement transport.LogRequests()
- Remove external dependencies (implement our own CURL printer) - Switch to slog.Logger (requires Go 1.21) - Require new opts param (this is technically breaking change, but we're under v0.0.0-, so it's fine).
1 parent 75ad232 commit 171330f

File tree

4 files changed

+93
-52
lines changed

4 files changed

+93
-52
lines changed

curl.go

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package transport
2+
3+
import (
4+
"bytes"
5+
"fmt"
6+
"net/http"
7+
"strings"
8+
)
9+
10+
func curl(r *http.Request, body *bytes.Buffer) string {
11+
var b strings.Builder
12+
13+
fmt.Fprintf(&b, "curl")
14+
if r.Method != "GET" && r.Method != "POST" {
15+
fmt.Fprintf(&b, " -X %s", r.Method)
16+
}
17+
18+
fmt.Fprintf(&b, " %s", singleQuoted(r.URL.String()))
19+
20+
if r.Method == "POST" {
21+
fmt.Fprintf(&b, " --data-raw %s", singleQuoted(body.String()))
22+
}
23+
24+
for name, vals := range r.Header {
25+
for _, val := range vals {
26+
fmt.Fprintf(&b, " -H %s", singleQuoted(fmt.Sprintf("%s: %s", name, val)))
27+
}
28+
}
29+
30+
return b.String()
31+
}
32+
33+
func scheme(r *http.Request) string {
34+
if r.TLS != nil {
35+
return "https"
36+
}
37+
return "http"
38+
}
39+
40+
func singleQuoted(v string) string {
41+
return fmt.Sprintf("'%s'", strings.ReplaceAll(v, "'", `'\''`))
42+
}

go.mod

-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,4 @@ module github.com/go-chi/transport
22

33
go 1.14
44

5-
require moul.io/http2curl/v2 v2.3.0
6-
75
require golang.org/x/sync v0.4.0

go.sum

-33
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,2 @@
1-
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
2-
github.com/pkg/diff v0.0.0-20200914180035-5b29258ca4f7/go.mod h1:zO8QMzTeZd5cpnIkz/Gn6iK0jDfGicM1nynOkkPIl28=
3-
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
4-
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
5-
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
6-
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
7-
github.com/tailscale/depaware v0.0.0-20210622194025-720c4b409502/go.mod h1:p9lPsd+cx33L3H9nNoecRRxPssFKUwwI50I3pZ0yT+8=
8-
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
9-
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
10-
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
11-
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
12-
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
13-
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
14-
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
15-
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
16-
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
17-
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
18-
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
191
golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ=
202
golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
21-
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
22-
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
23-
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
24-
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
25-
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
26-
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
27-
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
28-
golang.org/x/tools v0.0.0-20201211185031-d93e913c1a58/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
29-
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
30-
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
31-
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
32-
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
33-
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
34-
moul.io/http2curl/v2 v2.3.0 h1:9r3JfDzWPcbIklMOs2TnIFzDYvfAZvjeavG6EzP7jYs=
35-
moul.io/http2curl/v2 v2.3.0/go.mod h1:RW4hyBjTWSYDOxapodpNEtX0g5Eb16sxklBqmd2RHcE=

logRequests.go

+51-17
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,64 @@
1+
//go:build go1.21
2+
13
package transport
24

35
import (
4-
"log"
6+
"bytes"
7+
"fmt"
8+
"io"
59
"net/http"
610
"time"
711

8-
"moul.io/http2curl/v2"
12+
"log/slog"
913
)
1014

11-
func LogRequests(next http.RoundTripper) http.RoundTripper {
12-
return RoundTripFunc(func(req *http.Request) (resp *http.Response, err error) {
13-
r := CloneRequest(req)
15+
type LogOptions struct {
16+
Concise bool
17+
CURL bool
18+
}
1419

15-
curlCommand, _ := http2curl.GetCurlCommand(r)
16-
log.Printf("%v", curlCommand)
17-
log.Printf("request: %s %s", r.Method, r.URL)
20+
func LogRequests(opts LogOptions) func(next http.RoundTripper) http.RoundTripper {
21+
return func(next http.RoundTripper) http.RoundTripper {
22+
return RoundTripFunc(func(req *http.Request) (resp *http.Response, err error) {
23+
ctx := req.Context()
24+
r := CloneRequest(req)
1825

19-
startTime := time.Now()
20-
defer func() {
21-
if resp != nil {
22-
log.Printf("response (HTTP %v): %v %s", time.Since(startTime), resp.Status, r.URL)
23-
} else {
24-
log.Printf("response (<nil>): %v %s", time.Since(startTime), r.URL)
26+
var buf bytes.Buffer
27+
if opts.CURL && r.Body != nil {
28+
r.Body = io.NopCloser(io.TeeReader(r.Body, &buf))
2529
}
26-
}()
2730

28-
return next.RoundTrip(r)
29-
})
31+
slog.LogAttrs(ctx, slog.LevelDebug, fmt.Sprintf("Request: %v %s", r.Method, r.URL.String()))
32+
33+
startTime := time.Now()
34+
defer func() {
35+
level := slog.LevelError
36+
var statusCode int
37+
if resp != nil {
38+
statusCode = resp.StatusCode
39+
if statusCode >= 200 && statusCode < 400 {
40+
level = slog.LevelInfo
41+
}
42+
}
43+
44+
attrs := []slog.Attr{}
45+
if opts.CURL {
46+
attrs = append(attrs, slog.String("curl", curl(r, &buf)))
47+
}
48+
49+
if opts.Concise {
50+
slog.LogAttrs(ctx, level, fmt.Sprintf("Request: %v %s => HTTP %v (%v)", r.Method, r.URL.String(), statusCode, time.Since(startTime)), attrs...)
51+
} else {
52+
attrs = append(attrs,
53+
slog.String("url", r.URL.String()),
54+
slog.Duration("duration", time.Since(startTime)),
55+
slog.Int("status", statusCode),
56+
)
57+
slog.LogAttrs(ctx, level, fmt.Sprintf("Request"), attrs...)
58+
}
59+
}()
60+
61+
return next.RoundTrip(r)
62+
})
63+
}
3064
}

0 commit comments

Comments
 (0)