trace.go 7.3 KB

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