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