handler.go 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. // Copyright The OpenTelemetry 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 otelhttp // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
  15. import (
  16. "io"
  17. "net/http"
  18. "time"
  19. "github.com/felixge/httpsnoop"
  20. "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconvutil"
  21. "go.opentelemetry.io/otel"
  22. "go.opentelemetry.io/otel/attribute"
  23. "go.opentelemetry.io/otel/metric"
  24. "go.opentelemetry.io/otel/propagation"
  25. semconv "go.opentelemetry.io/otel/semconv/v1.20.0"
  26. "go.opentelemetry.io/otel/trace"
  27. )
  28. // middleware is an http middleware which wraps the next handler in a span.
  29. type middleware struct {
  30. operation string
  31. server string
  32. tracer trace.Tracer
  33. meter metric.Meter
  34. propagators propagation.TextMapPropagator
  35. spanStartOptions []trace.SpanStartOption
  36. readEvent bool
  37. writeEvent bool
  38. filters []Filter
  39. spanNameFormatter func(string, *http.Request) string
  40. publicEndpoint bool
  41. publicEndpointFn func(*http.Request) bool
  42. requestBytesCounter metric.Int64Counter
  43. responseBytesCounter metric.Int64Counter
  44. serverLatencyMeasure metric.Float64Histogram
  45. }
  46. func defaultHandlerFormatter(operation string, _ *http.Request) string {
  47. return operation
  48. }
  49. // NewHandler wraps the passed handler in a span named after the operation and
  50. // enriches it with metrics.
  51. func NewHandler(handler http.Handler, operation string, opts ...Option) http.Handler {
  52. return NewMiddleware(operation, opts...)(handler)
  53. }
  54. // NewMiddleware returns a tracing and metrics instrumentation middleware.
  55. // The handler returned by the middleware wraps a handler
  56. // in a span named after the operation and enriches it with metrics.
  57. func NewMiddleware(operation string, opts ...Option) func(http.Handler) http.Handler {
  58. h := middleware{
  59. operation: operation,
  60. }
  61. defaultOpts := []Option{
  62. WithSpanOptions(trace.WithSpanKind(trace.SpanKindServer)),
  63. WithSpanNameFormatter(defaultHandlerFormatter),
  64. }
  65. c := newConfig(append(defaultOpts, opts...)...)
  66. h.configure(c)
  67. h.createMeasures()
  68. return func(next http.Handler) http.Handler {
  69. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  70. h.serveHTTP(w, r, next)
  71. })
  72. }
  73. }
  74. func (h *middleware) configure(c *config) {
  75. h.tracer = c.Tracer
  76. h.meter = c.Meter
  77. h.propagators = c.Propagators
  78. h.spanStartOptions = c.SpanStartOptions
  79. h.readEvent = c.ReadEvent
  80. h.writeEvent = c.WriteEvent
  81. h.filters = c.Filters
  82. h.spanNameFormatter = c.SpanNameFormatter
  83. h.publicEndpoint = c.PublicEndpoint
  84. h.publicEndpointFn = c.PublicEndpointFn
  85. h.server = c.ServerName
  86. }
  87. func handleErr(err error) {
  88. if err != nil {
  89. otel.Handle(err)
  90. }
  91. }
  92. func (h *middleware) createMeasures() {
  93. var err error
  94. h.requestBytesCounter, err = h.meter.Int64Counter(
  95. serverRequestSize,
  96. metric.WithUnit("By"),
  97. metric.WithDescription("Measures the size of HTTP request messages."),
  98. )
  99. handleErr(err)
  100. h.responseBytesCounter, err = h.meter.Int64Counter(
  101. serverResponseSize,
  102. metric.WithUnit("By"),
  103. metric.WithDescription("Measures the size of HTTP response messages."),
  104. )
  105. handleErr(err)
  106. h.serverLatencyMeasure, err = h.meter.Float64Histogram(
  107. serverDuration,
  108. metric.WithUnit("ms"),
  109. metric.WithDescription("Measures the duration of inbound HTTP requests."),
  110. )
  111. handleErr(err)
  112. }
  113. // serveHTTP sets up tracing and calls the given next http.Handler with the span
  114. // context injected into the request context.
  115. func (h *middleware) serveHTTP(w http.ResponseWriter, r *http.Request, next http.Handler) {
  116. requestStartTime := time.Now()
  117. for _, f := range h.filters {
  118. if !f(r) {
  119. // Simply pass through to the handler if a filter rejects the request
  120. next.ServeHTTP(w, r)
  121. return
  122. }
  123. }
  124. ctx := h.propagators.Extract(r.Context(), propagation.HeaderCarrier(r.Header))
  125. opts := []trace.SpanStartOption{
  126. trace.WithAttributes(semconvutil.HTTPServerRequest(h.server, r)...),
  127. }
  128. if h.server != "" {
  129. hostAttr := semconv.NetHostName(h.server)
  130. opts = append(opts, trace.WithAttributes(hostAttr))
  131. }
  132. opts = append(opts, h.spanStartOptions...)
  133. if h.publicEndpoint || (h.publicEndpointFn != nil && h.publicEndpointFn(r.WithContext(ctx))) {
  134. opts = append(opts, trace.WithNewRoot())
  135. // Linking incoming span context if any for public endpoint.
  136. if s := trace.SpanContextFromContext(ctx); s.IsValid() && s.IsRemote() {
  137. opts = append(opts, trace.WithLinks(trace.Link{SpanContext: s}))
  138. }
  139. }
  140. tracer := h.tracer
  141. if tracer == nil {
  142. if span := trace.SpanFromContext(r.Context()); span.SpanContext().IsValid() {
  143. tracer = newTracer(span.TracerProvider())
  144. } else {
  145. tracer = newTracer(otel.GetTracerProvider())
  146. }
  147. }
  148. ctx, span := tracer.Start(ctx, h.spanNameFormatter(h.operation, r), opts...)
  149. defer span.End()
  150. readRecordFunc := func(int64) {}
  151. if h.readEvent {
  152. readRecordFunc = func(n int64) {
  153. span.AddEvent("read", trace.WithAttributes(ReadBytesKey.Int64(n)))
  154. }
  155. }
  156. var bw bodyWrapper
  157. // if request body is nil or NoBody, we don't want to mutate the body as it
  158. // will affect the identity of it in an unforeseeable way because we assert
  159. // ReadCloser fulfills a certain interface and it is indeed nil or NoBody.
  160. if r.Body != nil && r.Body != http.NoBody {
  161. bw.ReadCloser = r.Body
  162. bw.record = readRecordFunc
  163. r.Body = &bw
  164. }
  165. writeRecordFunc := func(int64) {}
  166. if h.writeEvent {
  167. writeRecordFunc = func(n int64) {
  168. span.AddEvent("write", trace.WithAttributes(WroteBytesKey.Int64(n)))
  169. }
  170. }
  171. rww := &respWriterWrapper{
  172. ResponseWriter: w,
  173. record: writeRecordFunc,
  174. ctx: ctx,
  175. props: h.propagators,
  176. statusCode: http.StatusOK, // default status code in case the Handler doesn't write anything
  177. }
  178. // Wrap w to use our ResponseWriter methods while also exposing
  179. // other interfaces that w may implement (http.CloseNotifier,
  180. // http.Flusher, http.Hijacker, http.Pusher, io.ReaderFrom).
  181. w = httpsnoop.Wrap(w, httpsnoop.Hooks{
  182. Header: func(httpsnoop.HeaderFunc) httpsnoop.HeaderFunc {
  183. return rww.Header
  184. },
  185. Write: func(httpsnoop.WriteFunc) httpsnoop.WriteFunc {
  186. return rww.Write
  187. },
  188. WriteHeader: func(httpsnoop.WriteHeaderFunc) httpsnoop.WriteHeaderFunc {
  189. return rww.WriteHeader
  190. },
  191. })
  192. labeler := &Labeler{}
  193. ctx = injectLabeler(ctx, labeler)
  194. next.ServeHTTP(w, r.WithContext(ctx))
  195. setAfterServeAttributes(span, bw.read, rww.written, rww.statusCode, bw.err, rww.err)
  196. // Add metrics
  197. attributes := append(labeler.Get(), semconvutil.HTTPServerRequestMetrics(h.server, r)...)
  198. if rww.statusCode > 0 {
  199. attributes = append(attributes, semconv.HTTPStatusCode(rww.statusCode))
  200. }
  201. o := metric.WithAttributes(attributes...)
  202. h.requestBytesCounter.Add(ctx, bw.read, o)
  203. h.responseBytesCounter.Add(ctx, rww.written, o)
  204. // Use floating point division here for higher precision (instead of Millisecond method).
  205. elapsedTime := float64(time.Since(requestStartTime)) / float64(time.Millisecond)
  206. h.serverLatencyMeasure.Record(ctx, elapsedTime, o)
  207. }
  208. func setAfterServeAttributes(span trace.Span, read, wrote int64, statusCode int, rerr, werr error) {
  209. attributes := []attribute.KeyValue{}
  210. // TODO: Consider adding an event after each read and write, possibly as an
  211. // option (defaulting to off), so as to not create needlessly verbose spans.
  212. if read > 0 {
  213. attributes = append(attributes, ReadBytesKey.Int64(read))
  214. }
  215. if rerr != nil && rerr != io.EOF {
  216. attributes = append(attributes, ReadErrorKey.String(rerr.Error()))
  217. }
  218. if wrote > 0 {
  219. attributes = append(attributes, WroteBytesKey.Int64(wrote))
  220. }
  221. if statusCode > 0 {
  222. attributes = append(attributes, semconv.HTTPStatusCode(statusCode))
  223. }
  224. span.SetStatus(semconvutil.HTTPServerStatus(statusCode))
  225. if werr != nil && werr != io.EOF {
  226. attributes = append(attributes, WriteErrorKey.String(werr.Error()))
  227. }
  228. span.SetAttributes(attributes...)
  229. }
  230. // WithRouteTag annotates spans and metrics with the provided route name
  231. // with HTTP route attribute.
  232. func WithRouteTag(route string, h http.Handler) http.Handler {
  233. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  234. attr := semconv.HTTPRouteKey.String(route)
  235. span := trace.SpanFromContext(r.Context())
  236. span.SetAttributes(attr)
  237. labeler, _ := LabelerFromContext(r.Context())
  238. labeler.Add(attr)
  239. h.ServeHTTP(w, r)
  240. })
  241. }