handler.go raw

   1  // Copyright The OpenTelemetry Authors
   2  // SPDX-License-Identifier: Apache-2.0
   3  
   4  package otelhttp // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
   5  
   6  import (
   7  	"net/http"
   8  	"time"
   9  
  10  	"github.com/felixge/httpsnoop"
  11  
  12  	"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/request"
  13  	"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconv"
  14  	"go.opentelemetry.io/otel"
  15  	"go.opentelemetry.io/otel/attribute"
  16  	"go.opentelemetry.io/otel/propagation"
  17  	"go.opentelemetry.io/otel/trace"
  18  )
  19  
  20  // middleware is an http middleware which wraps the next handler in a span.
  21  type middleware struct {
  22  	operation string
  23  	server    string
  24  
  25  	tracer             trace.Tracer
  26  	propagators        propagation.TextMapPropagator
  27  	spanStartOptions   []trace.SpanStartOption
  28  	readEvent          bool
  29  	writeEvent         bool
  30  	filters            []Filter
  31  	spanNameFormatter  func(string, *http.Request) string
  32  	publicEndpoint     bool
  33  	publicEndpointFn   func(*http.Request) bool
  34  	metricAttributesFn func(*http.Request) []attribute.KeyValue
  35  
  36  	semconv semconv.HTTPServer
  37  }
  38  
  39  func defaultHandlerFormatter(operation string, _ *http.Request) string {
  40  	return operation
  41  }
  42  
  43  // NewHandler wraps the passed handler in a span named after the operation and
  44  // enriches it with metrics.
  45  func NewHandler(handler http.Handler, operation string, opts ...Option) http.Handler {
  46  	return NewMiddleware(operation, opts...)(handler)
  47  }
  48  
  49  // NewMiddleware returns a tracing and metrics instrumentation middleware.
  50  // The handler returned by the middleware wraps a handler
  51  // in a span named after the operation and enriches it with metrics.
  52  func NewMiddleware(operation string, opts ...Option) func(http.Handler) http.Handler {
  53  	h := middleware{
  54  		operation: operation,
  55  	}
  56  
  57  	defaultOpts := []Option{
  58  		WithSpanOptions(trace.WithSpanKind(trace.SpanKindServer)),
  59  		WithSpanNameFormatter(defaultHandlerFormatter),
  60  	}
  61  
  62  	c := newConfig(append(defaultOpts, opts...)...)
  63  	h.configure(c)
  64  
  65  	return func(next http.Handler) http.Handler {
  66  		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  67  			h.serveHTTP(w, r, next)
  68  		})
  69  	}
  70  }
  71  
  72  func (h *middleware) configure(c *config) {
  73  	h.tracer = c.Tracer
  74  	h.propagators = c.Propagators
  75  	h.spanStartOptions = c.SpanStartOptions
  76  	h.readEvent = c.ReadEvent
  77  	h.writeEvent = c.WriteEvent
  78  	h.filters = c.Filters
  79  	h.spanNameFormatter = c.SpanNameFormatter
  80  	h.publicEndpoint = c.PublicEndpoint
  81  	h.publicEndpointFn = c.PublicEndpointFn
  82  	h.server = c.ServerName
  83  	h.semconv = semconv.NewHTTPServer(c.Meter)
  84  	h.metricAttributesFn = c.MetricAttributesFn
  85  }
  86  
  87  // serveHTTP sets up tracing and calls the given next http.Handler with the span
  88  // context injected into the request context.
  89  func (h *middleware) serveHTTP(w http.ResponseWriter, r *http.Request, next http.Handler) {
  90  	requestStartTime := time.Now()
  91  	for _, f := range h.filters {
  92  		if !f(r) {
  93  			// Simply pass through to the handler if a filter rejects the request
  94  			next.ServeHTTP(w, r)
  95  			return
  96  		}
  97  	}
  98  
  99  	ctx := h.propagators.Extract(r.Context(), propagation.HeaderCarrier(r.Header))
 100  	opts := []trace.SpanStartOption{
 101  		trace.WithAttributes(h.semconv.RequestTraceAttrs(h.server, r, semconv.RequestTraceAttrsOpts{})...),
 102  	}
 103  
 104  	opts = append(opts, h.spanStartOptions...)
 105  	if h.publicEndpoint || (h.publicEndpointFn != nil && h.publicEndpointFn(r.WithContext(ctx))) {
 106  		opts = append(opts, trace.WithNewRoot())
 107  		// Linking incoming span context if any for public endpoint.
 108  		if s := trace.SpanContextFromContext(ctx); s.IsValid() && s.IsRemote() {
 109  			opts = append(opts, trace.WithLinks(trace.Link{SpanContext: s}))
 110  		}
 111  	}
 112  
 113  	tracer := h.tracer
 114  
 115  	if tracer == nil {
 116  		if span := trace.SpanFromContext(r.Context()); span.SpanContext().IsValid() {
 117  			tracer = newTracer(span.TracerProvider())
 118  		} else {
 119  			tracer = newTracer(otel.GetTracerProvider())
 120  		}
 121  	}
 122  
 123  	if startTime := StartTimeFromContext(ctx); !startTime.IsZero() {
 124  		opts = append(opts, trace.WithTimestamp(startTime))
 125  		requestStartTime = startTime
 126  	}
 127  
 128  	ctx, span := tracer.Start(ctx, h.spanNameFormatter(h.operation, r), opts...)
 129  	defer span.End()
 130  
 131  	readRecordFunc := func(int64) {}
 132  	if h.readEvent {
 133  		readRecordFunc = func(n int64) {
 134  			span.AddEvent("read", trace.WithAttributes(ReadBytesKey.Int64(n)))
 135  		}
 136  	}
 137  
 138  	// if request body is nil or NoBody, we don't want to mutate the body as it
 139  	// will affect the identity of it in an unforeseeable way because we assert
 140  	// ReadCloser fulfills a certain interface and it is indeed nil or NoBody.
 141  	bw := request.NewBodyWrapper(r.Body, readRecordFunc)
 142  	if r.Body != nil && r.Body != http.NoBody {
 143  		r.Body = bw
 144  	}
 145  
 146  	writeRecordFunc := func(int64) {}
 147  	if h.writeEvent {
 148  		writeRecordFunc = func(n int64) {
 149  			span.AddEvent("write", trace.WithAttributes(WroteBytesKey.Int64(n)))
 150  		}
 151  	}
 152  
 153  	rww := request.NewRespWriterWrapper(w, writeRecordFunc)
 154  
 155  	// Wrap w to use our ResponseWriter methods while also exposing
 156  	// other interfaces that w may implement (http.CloseNotifier,
 157  	// http.Flusher, http.Hijacker, http.Pusher, io.ReaderFrom).
 158  
 159  	w = httpsnoop.Wrap(w, httpsnoop.Hooks{
 160  		Header: func(httpsnoop.HeaderFunc) httpsnoop.HeaderFunc {
 161  			return rww.Header
 162  		},
 163  		Write: func(httpsnoop.WriteFunc) httpsnoop.WriteFunc {
 164  			return rww.Write
 165  		},
 166  		WriteHeader: func(httpsnoop.WriteHeaderFunc) httpsnoop.WriteHeaderFunc {
 167  			return rww.WriteHeader
 168  		},
 169  		Flush: func(httpsnoop.FlushFunc) httpsnoop.FlushFunc {
 170  			return rww.Flush
 171  		},
 172  	})
 173  
 174  	labeler, found := LabelerFromContext(ctx)
 175  	if !found {
 176  		ctx = ContextWithLabeler(ctx, labeler)
 177  	}
 178  
 179  	r = r.WithContext(ctx)
 180  	next.ServeHTTP(w, r)
 181  
 182  	if r.Pattern != "" {
 183  		span.SetName(h.spanNameFormatter(h.operation, r))
 184  	}
 185  
 186  	statusCode := rww.StatusCode()
 187  	bytesWritten := rww.BytesWritten()
 188  	span.SetStatus(h.semconv.Status(statusCode))
 189  	span.SetAttributes(h.semconv.ResponseTraceAttrs(semconv.ResponseTelemetry{
 190  		StatusCode: statusCode,
 191  		ReadBytes:  bw.BytesRead(),
 192  		ReadError:  bw.Error(),
 193  		WriteBytes: bytesWritten,
 194  		WriteError: rww.Error(),
 195  	})...)
 196  
 197  	// Use floating point division here for higher precision (instead of Millisecond method).
 198  	elapsedTime := float64(time.Since(requestStartTime)) / float64(time.Millisecond)
 199  
 200  	metricAttributes := semconv.MetricAttributes{
 201  		Req:                  r,
 202  		StatusCode:           statusCode,
 203  		AdditionalAttributes: append(labeler.Get(), h.metricAttributesFromRequest(r)...),
 204  	}
 205  
 206  	h.semconv.RecordMetrics(ctx, semconv.ServerMetricData{
 207  		ServerName:       h.server,
 208  		ResponseSize:     bytesWritten,
 209  		MetricAttributes: metricAttributes,
 210  		MetricData: semconv.MetricData{
 211  			RequestSize: bw.BytesRead(),
 212  			ElapsedTime: elapsedTime,
 213  		},
 214  	})
 215  }
 216  
 217  func (h *middleware) metricAttributesFromRequest(r *http.Request) []attribute.KeyValue {
 218  	var attributeForRequest []attribute.KeyValue
 219  	if h.metricAttributesFn != nil {
 220  		attributeForRequest = h.metricAttributesFn(r)
 221  	}
 222  	return attributeForRequest
 223  }
 224  
 225  // WithRouteTag annotates spans and metrics with the provided route name
 226  // with HTTP route attribute.
 227  func WithRouteTag(route string, h http.Handler) http.Handler {
 228  	attr := semconv.NewHTTPServer(nil).Route(route)
 229  	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 230  		span := trace.SpanFromContext(r.Context())
 231  		span.SetAttributes(attr)
 232  
 233  		labeler, _ := LabelerFromContext(r.Context())
 234  		labeler.Add(attr)
 235  
 236  		h.ServeHTTP(w, r)
 237  	})
 238  }
 239