Non puoi selezionare più di 25 argomenti Gli argomenti devono iniziare con una lettera o un numero, possono includere trattini ('-') e possono essere lunghi fino a 35 caratteri.
 
 
 

229 righe
7.0 KiB

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