...

Source file src/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/handler.go

Documentation: go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp

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

View as plain text