-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathdoer.go
135 lines (112 loc) · 3.06 KB
/
doer.go
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
package strc
import (
"log/slog"
"net/http"
"strings"
)
// DoerErr is a simple wrapped error without any message. Additional message would
// stack for each request as multiple doers are called leading to:
//
// "error in doer1: error in doer2: error in doer3: something happened"
type DoerErr struct {
Err error
}
func NewDoerErr(err error) *DoerErr {
return &DoerErr{Err: err}
}
func (e *DoerErr) Error() string {
return e.Err.Error()
}
func (e *DoerErr) Unwrap() error {
return e.Err
}
type HttpRequestDoer interface {
Do(req *http.Request) (*http.Response, error)
}
// TracingDoer is a http client doer that adds tracing to the request and response.
type TracingDoer struct {
doer HttpRequestDoer
config TracingDoerConfig
}
type TracingDoerConfig struct {
WithRequestBody bool
WithResponseBody bool
WithRequestHeaders bool
WithResponseHeaders bool
}
// NewTracingDoer returns a new TracingDoer.
func NewTracingDoer(doer HttpRequestDoer) *TracingDoer {
client := TracingDoer{
doer: doer,
}
return &client
}
func NewTracingDoerWithConfig(doer HttpRequestDoer, config TracingDoerConfig) *TracingDoer {
client := TracingDoer{
doer: doer,
config: config,
}
return &client
}
func (td *TracingDoer) Do(req *http.Request) (*http.Response, error) {
span, ctx := Start(req.Context(), "http client request")
defer span.End()
logger := slog.Default().WithGroup("client").With(
slog.String("method", req.Method),
slog.String("url", req.URL.RequestURI()),
)
// add tracing
AddTraceIDHeader(ctx, req)
AddSpanIDHeader(ctx, req)
// dump request body if enabled
if td.config.WithRequestBody && req.Body != nil {
br := newBodyReader(req.Body, RequestBodyMaxSize, td.config.WithRequestBody)
req.Body = br
logger.WithGroup("request").With(
slog.Int64("length", req.ContentLength),
slog.String("body", br.body.String()),
)
}
if td.config.WithRequestHeaders {
kv := []any{}
for k, v := range req.Header {
if _, found := HiddenRequestHeaders[strings.ToLower(k)]; found {
continue
}
kv = append(kv, slog.Any(k, v))
}
logger = logger.WithGroup("request").With(kv...)
}
if td.config.WithRequestBody || td.config.WithRequestHeaders {
logger.DebugContext(ctx, "http client request")
}
// delegate the request
res, err := td.doer.Do(req)
if err != nil {
return nil, NewDoerErr(err)
}
// dump request body if enabled
if td.config.WithResponseBody && res.Body != nil {
br := newBodyReader(req.Body, ResponseBodyMaxSize, td.config.WithRequestBody)
req.Body = br
logger.WithGroup("response").With(
slog.Int64("length", res.ContentLength),
slog.String("body", br.body.String()),
slog.Int("status", res.StatusCode),
)
}
if td.config.WithResponseHeaders {
kv := []any{}
for k, v := range res.Header {
if _, found := HiddenResponseHeaders[strings.ToLower(k)]; found {
continue
}
kv = append(kv, slog.Any(k, v))
}
logger = logger.WithGroup("response").With(kv...)
}
if td.config.WithResponseBody || td.config.WithResponseHeaders {
logger.DebugContext(ctx, "http client response")
}
return res, nil
}