]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blob - vendor/go.opencensus.io/plugin/ochttp/trace.go
Upgrade to 0.12
[github/fretlink/terraform-provider-statuscake.git] / vendor / go.opencensus.io / plugin / ochttp / trace.go
1 // Copyright 2018, OpenCensus Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
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 // TODO(jbd): Add godoc examples.
28
29 var defaultFormat propagation.HTTPFormat = &b3.HTTPFormat{}
30
31 // Attributes recorded on the span for the requests.
32 // Only trace exporters will need them.
33 const (
34 HostAttribute = "http.host"
35 MethodAttribute = "http.method"
36 PathAttribute = "http.path"
37 UserAgentAttribute = "http.user_agent"
38 StatusCodeAttribute = "http.status_code"
39 )
40
41 type traceTransport struct {
42 base http.RoundTripper
43 startOptions trace.StartOptions
44 format propagation.HTTPFormat
45 formatSpanName func(*http.Request) string
46 newClientTrace func(*http.Request, *trace.Span) *httptrace.ClientTrace
47 }
48
49 // TODO(jbd): Add message events for request and response size.
50
51 // RoundTrip creates a trace.Span and inserts it into the outgoing request's headers.
52 // The created span can follow a parent span, if a parent is presented in
53 // the request's context.
54 func (t *traceTransport) RoundTrip(req *http.Request) (*http.Response, error) {
55 name := t.formatSpanName(req)
56 // TODO(jbd): Discuss whether we want to prefix
57 // outgoing requests with Sent.
58 ctx, span := trace.StartSpan(req.Context(), name,
59 trace.WithSampler(t.startOptions.Sampler),
60 trace.WithSpanKind(trace.SpanKindClient))
61
62 if t.newClientTrace != nil {
63 req = req.WithContext(httptrace.WithClientTrace(ctx, t.newClientTrace(req, span)))
64 } else {
65 req = req.WithContext(ctx)
66 }
67
68 if t.format != nil {
69 // SpanContextToRequest will modify its Request argument, which is
70 // contrary to the contract for http.RoundTripper, so we need to
71 // pass it a copy of the Request.
72 // However, the Request struct itself was already copied by
73 // the WithContext calls above and so we just need to copy the header.
74 header := make(http.Header)
75 for k, v := range req.Header {
76 header[k] = v
77 }
78 req.Header = header
79 t.format.SpanContextToRequest(span.SpanContext(), req)
80 }
81
82 span.AddAttributes(requestAttrs(req)...)
83 resp, err := t.base.RoundTrip(req)
84 if err != nil {
85 span.SetStatus(trace.Status{Code: trace.StatusCodeUnknown, Message: err.Error()})
86 span.End()
87 return resp, err
88 }
89
90 span.AddAttributes(responseAttrs(resp)...)
91 span.SetStatus(TraceStatus(resp.StatusCode, resp.Status))
92
93 // span.End() will be invoked after
94 // a read from resp.Body returns io.EOF or when
95 // resp.Body.Close() is invoked.
96 resp.Body = &bodyTracker{rc: resp.Body, span: span}
97 return resp, err
98 }
99
100 // bodyTracker wraps a response.Body and invokes
101 // trace.EndSpan on encountering io.EOF on reading
102 // the body of the original response.
103 type bodyTracker struct {
104 rc io.ReadCloser
105 span *trace.Span
106 }
107
108 var _ io.ReadCloser = (*bodyTracker)(nil)
109
110 func (bt *bodyTracker) Read(b []byte) (int, error) {
111 n, err := bt.rc.Read(b)
112
113 switch err {
114 case nil:
115 return n, nil
116 case io.EOF:
117 bt.span.End()
118 default:
119 // For all other errors, set the span status
120 bt.span.SetStatus(trace.Status{
121 // Code 2 is the error code for Internal server error.
122 Code: 2,
123 Message: err.Error(),
124 })
125 }
126 return n, err
127 }
128
129 func (bt *bodyTracker) Close() error {
130 // Invoking endSpan on Close will help catch the cases
131 // in which a read returned a non-nil error, we set the
132 // span status but didn't end the span.
133 bt.span.End()
134 return bt.rc.Close()
135 }
136
137 // CancelRequest cancels an in-flight request by closing its connection.
138 func (t *traceTransport) CancelRequest(req *http.Request) {
139 type canceler interface {
140 CancelRequest(*http.Request)
141 }
142 if cr, ok := t.base.(canceler); ok {
143 cr.CancelRequest(req)
144 }
145 }
146
147 func spanNameFromURL(req *http.Request) string {
148 return req.URL.Path
149 }
150
151 func requestAttrs(r *http.Request) []trace.Attribute {
152 return []trace.Attribute{
153 trace.StringAttribute(PathAttribute, r.URL.Path),
154 trace.StringAttribute(HostAttribute, r.URL.Host),
155 trace.StringAttribute(MethodAttribute, r.Method),
156 trace.StringAttribute(UserAgentAttribute, r.UserAgent()),
157 }
158 }
159
160 func responseAttrs(resp *http.Response) []trace.Attribute {
161 return []trace.Attribute{
162 trace.Int64Attribute(StatusCodeAttribute, int64(resp.StatusCode)),
163 }
164 }
165
166 // TraceStatus is a utility to convert the HTTP status code to a trace.Status that
167 // represents the outcome as closely as possible.
168 func TraceStatus(httpStatusCode int, statusLine string) trace.Status {
169 var code int32
170 if httpStatusCode < 200 || httpStatusCode >= 400 {
171 code = trace.StatusCodeUnknown
172 }
173 switch httpStatusCode {
174 case 499:
175 code = trace.StatusCodeCancelled
176 case http.StatusBadRequest:
177 code = trace.StatusCodeInvalidArgument
178 case http.StatusGatewayTimeout:
179 code = trace.StatusCodeDeadlineExceeded
180 case http.StatusNotFound:
181 code = trace.StatusCodeNotFound
182 case http.StatusForbidden:
183 code = trace.StatusCodePermissionDenied
184 case http.StatusUnauthorized: // 401 is actually unauthenticated.
185 code = trace.StatusCodeUnauthenticated
186 case http.StatusTooManyRequests:
187 code = trace.StatusCodeResourceExhausted
188 case http.StatusNotImplemented:
189 code = trace.StatusCodeUnimplemented
190 case http.StatusServiceUnavailable:
191 code = trace.StatusCodeUnavailable
192 case http.StatusOK:
193 code = trace.StatusCodeOK
194 }
195 return trace.Status{Code: code, Message: codeToStr[code]}
196 }
197
198 var codeToStr = map[int32]string{
199 trace.StatusCodeOK: `OK`,
200 trace.StatusCodeCancelled: `CANCELLED`,
201 trace.StatusCodeUnknown: `UNKNOWN`,
202 trace.StatusCodeInvalidArgument: `INVALID_ARGUMENT`,
203 trace.StatusCodeDeadlineExceeded: `DEADLINE_EXCEEDED`,
204 trace.StatusCodeNotFound: `NOT_FOUND`,
205 trace.StatusCodeAlreadyExists: `ALREADY_EXISTS`,
206 trace.StatusCodePermissionDenied: `PERMISSION_DENIED`,
207 trace.StatusCodeResourceExhausted: `RESOURCE_EXHAUSTED`,
208 trace.StatusCodeFailedPrecondition: `FAILED_PRECONDITION`,
209 trace.StatusCodeAborted: `ABORTED`,
210 trace.StatusCodeOutOfRange: `OUT_OF_RANGE`,
211 trace.StatusCodeUnimplemented: `UNIMPLEMENTED`,
212 trace.StatusCodeInternal: `INTERNAL`,
213 trace.StatusCodeUnavailable: `UNAVAILABLE`,
214 trace.StatusCodeDataLoss: `DATA_LOSS`,
215 trace.StatusCodeUnauthenticated: `UNAUTHENTICATED`,
216 }
217
218 func isHealthEndpoint(path string) bool {
219 // Health checking is pretty frequent and
220 // traces collected for health endpoints
221 // can be extremely noisy and expensive.
222 // Disable canonical health checking endpoints
223 // like /healthz and /_ah/health for now.
224 if path == "/healthz" || path == "/_ah/health" {
225 return true
226 }
227 return false
228 }