1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package ochttp
16
17 import (
18 "io"
19 "net/http"
20 "net/http/httptrace"
21
22 "go.opencensus.io/plugin/ochttp/propagation/b3"
23 "go.opencensus.io/trace"
24 "go.opencensus.io/trace/propagation"
25 )
26
27
28
29 var defaultFormat propagation.HTTPFormat = &b3.HTTPFormat{}
30
31
32
33 const (
34 HostAttribute = "http.host"
35 MethodAttribute = "http.method"
36 PathAttribute = "http.path"
37 URLAttribute = "http.url"
38 UserAgentAttribute = "http.user_agent"
39 StatusCodeAttribute = "http.status_code"
40 )
41
42 type traceTransport struct {
43 base http.RoundTripper
44 startOptions trace.StartOptions
45 format propagation.HTTPFormat
46 formatSpanName func(*http.Request) string
47 newClientTrace func(*http.Request, *trace.Span) *httptrace.ClientTrace
48 }
49
50
51
52
53
54
55 func (t *traceTransport) RoundTrip(req *http.Request) (*http.Response, error) {
56 name := t.formatSpanName(req)
57
58
59 ctx, span := trace.StartSpan(req.Context(), name,
60 trace.WithSampler(t.startOptions.Sampler),
61 trace.WithSpanKind(trace.SpanKindClient))
62
63 if t.newClientTrace != nil {
64 req = req.WithContext(httptrace.WithClientTrace(ctx, t.newClientTrace(req, span)))
65 } else {
66 req = req.WithContext(ctx)
67 }
68
69 if t.format != nil {
70
71
72
73
74
75 header := make(http.Header)
76 for k, v := range req.Header {
77 header[k] = v
78 }
79 req.Header = header
80 t.format.SpanContextToRequest(span.SpanContext(), req)
81 }
82
83 span.AddAttributes(requestAttrs(req)...)
84 resp, err := t.base.RoundTrip(req)
85 if err != nil {
86 span.SetStatus(trace.Status{Code: trace.StatusCodeUnknown, Message: err.Error()})
87 span.End()
88 return resp, err
89 }
90
91 span.AddAttributes(responseAttrs(resp)...)
92 span.SetStatus(TraceStatus(resp.StatusCode, resp.Status))
93
94
95
96
97 bt := &bodyTracker{rc: resp.Body, span: span}
98 resp.Body = wrappedBody(bt, resp.Body)
99 return resp, err
100 }
101
102
103
104
105 type bodyTracker struct {
106 rc io.ReadCloser
107 span *trace.Span
108 }
109
110 var _ io.ReadCloser = (*bodyTracker)(nil)
111
112 func (bt *bodyTracker) Read(b []byte) (int, error) {
113 n, err := bt.rc.Read(b)
114
115 switch err {
116 case nil:
117 return n, nil
118 case io.EOF:
119 bt.span.End()
120 default:
121
122 bt.span.SetStatus(trace.Status{
123
124 Code: 2,
125 Message: err.Error(),
126 })
127 }
128 return n, err
129 }
130
131 func (bt *bodyTracker) Close() error {
132
133
134
135 bt.span.End()
136 return bt.rc.Close()
137 }
138
139
140 func (t *traceTransport) CancelRequest(req *http.Request) {
141 type canceler interface {
142 CancelRequest(*http.Request)
143 }
144 if cr, ok := t.base.(canceler); ok {
145 cr.CancelRequest(req)
146 }
147 }
148
149 func spanNameFromURL(req *http.Request) string {
150 return req.URL.Path
151 }
152
153 func requestAttrs(r *http.Request) []trace.Attribute {
154 userAgent := r.UserAgent()
155
156 attrs := make([]trace.Attribute, 0, 5)
157 attrs = append(attrs,
158 trace.StringAttribute(PathAttribute, r.URL.Path),
159 trace.StringAttribute(URLAttribute, r.URL.String()),
160 trace.StringAttribute(HostAttribute, r.Host),
161 trace.StringAttribute(MethodAttribute, r.Method),
162 )
163
164 if userAgent != "" {
165 attrs = append(attrs, trace.StringAttribute(UserAgentAttribute, userAgent))
166 }
167
168 return attrs
169 }
170
171 func responseAttrs(resp *http.Response) []trace.Attribute {
172 return []trace.Attribute{
173 trace.Int64Attribute(StatusCodeAttribute, int64(resp.StatusCode)),
174 }
175 }
176
177
178
179 func TraceStatus(httpStatusCode int, statusLine string) trace.Status {
180 var code int32
181 if httpStatusCode < 200 || httpStatusCode >= 400 {
182 code = trace.StatusCodeUnknown
183 }
184 switch httpStatusCode {
185 case 499:
186 code = trace.StatusCodeCancelled
187 case http.StatusBadRequest:
188 code = trace.StatusCodeInvalidArgument
189 case http.StatusUnprocessableEntity:
190 code = trace.StatusCodeInvalidArgument
191 case http.StatusGatewayTimeout:
192 code = trace.StatusCodeDeadlineExceeded
193 case http.StatusNotFound:
194 code = trace.StatusCodeNotFound
195 case http.StatusForbidden:
196 code = trace.StatusCodePermissionDenied
197 case http.StatusUnauthorized:
198 code = trace.StatusCodeUnauthenticated
199 case http.StatusTooManyRequests:
200 code = trace.StatusCodeResourceExhausted
201 case http.StatusNotImplemented:
202 code = trace.StatusCodeUnimplemented
203 case http.StatusServiceUnavailable:
204 code = trace.StatusCodeUnavailable
205 case http.StatusOK:
206 code = trace.StatusCodeOK
207 case http.StatusConflict:
208 code = trace.StatusCodeAlreadyExists
209 }
210
211 return trace.Status{Code: code, Message: codeToStr[code]}
212 }
213
214 var codeToStr = map[int32]string{
215 trace.StatusCodeOK: `OK`,
216 trace.StatusCodeCancelled: `CANCELLED`,
217 trace.StatusCodeUnknown: `UNKNOWN`,
218 trace.StatusCodeInvalidArgument: `INVALID_ARGUMENT`,
219 trace.StatusCodeDeadlineExceeded: `DEADLINE_EXCEEDED`,
220 trace.StatusCodeNotFound: `NOT_FOUND`,
221 trace.StatusCodeAlreadyExists: `ALREADY_EXISTS`,
222 trace.StatusCodePermissionDenied: `PERMISSION_DENIED`,
223 trace.StatusCodeResourceExhausted: `RESOURCE_EXHAUSTED`,
224 trace.StatusCodeFailedPrecondition: `FAILED_PRECONDITION`,
225 trace.StatusCodeAborted: `ABORTED`,
226 trace.StatusCodeOutOfRange: `OUT_OF_RANGE`,
227 trace.StatusCodeUnimplemented: `UNIMPLEMENTED`,
228 trace.StatusCodeInternal: `INTERNAL`,
229 trace.StatusCodeUnavailable: `UNAVAILABLE`,
230 trace.StatusCodeDataLoss: `DATA_LOSS`,
231 trace.StatusCodeUnauthenticated: `UNAUTHENTICATED`,
232 }
233
234 func isHealthEndpoint(path string) bool {
235
236
237
238
239
240 if path == "/healthz" || path == "/_ah/health" {
241 return true
242 }
243 return false
244 }
245
View as plain text