Skip to content

Commit

Permalink
Add otelhttp.Middleware for better compatiblity with third-party HTTP…
Browse files Browse the repository at this point in the history
… routers and middleware chainers

The key difference between the existing otelhttp.Handler and the new
otelhttp.Middleware is that `Middleware` takes the `next` handler as an
argument after construction, wheres the existing `Handler` works by
wrapping one specific http.Handler at construction time.
  • Loading branch information
alnr committed Apr 24, 2023
1 parent 6682591 commit 3c5a1c1
Show file tree
Hide file tree
Showing 2 changed files with 35 additions and 15 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -10,6 +10,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm

### Added

- An HTTP middleware in addition to the existing HTTP handler. The middleware is constructed once and afterwards can take different handlers as arguments, whereas the handler wraps a single http.Handler during construction. This allows better compatiblity with packages like github.com/urfave/negroni and github.com/go-chi/chi.
- AWS SDK add `rpc.system` attribute in `go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws`. (#3582, #3617)
- Add the new `go.opentelemetry.io/contrib/instrgen` package to provide auto-generated source code instrumentation. (#3068, #3108)

Expand Down
49 changes: 34 additions & 15 deletions instrumentation/net/http/otelhttp/handler.go
Expand Up @@ -33,14 +33,19 @@ import (

var _ http.Handler = &Handler{}

// Handler is http middleware that corresponds to the http.Handler interface and
// is designed to wrap a http.Mux (or equivalent), while individual routes on
// the mux are wrapped with WithRouteTag. A Handler will add various attributes
// to the span using the attribute.Keys defined in this package.
// Handler is an http.Handler which wraps a http.Mux (or equivalent), while
// individual routes on the mux are wrapped with WithRouteTag. A Handler will
// add various attributes to the span using the attribute.Keys defined in this
// package.
type Handler struct {
next http.Handler
middleware *Middleware
}

// Middleware is an http middleware which wraps the next handler in a span.
type Middleware struct {
operation string
server string
handler http.Handler

tracer trace.Tracer
meter metric.Meter
Expand All @@ -60,11 +65,24 @@ func defaultHandlerFormatter(operation string, _ *http.Request) string {
return operation
}

// NewHandler wraps the passed handler, functioning like middleware, in a span
// named after the operation and with any provided Options.
// NewHandler wraps the passed handler in a span named after the operation and
// with any provided Options.
func NewHandler(handler http.Handler, operation string, opts ...Option) http.Handler {
h := Handler{
handler: handler,
return &Handler{
next: handler,
middleware: NewMiddleware(operation, opts...),
}
}

// ServeHTTP serves HTTP requests (implements http.Handler).
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
h.middleware.ServeHTTP(w, r, h.next)
}

// / NewMiddleware returns a tracing middleware from the given operation name and
// options.
func NewMiddleware(operation string, opts ...Option) *Middleware {
h := Middleware{
operation: operation,
}

Expand All @@ -80,7 +98,7 @@ func NewHandler(handler http.Handler, operation string, opts ...Option) http.Han
return &h
}

func (h *Handler) configure(c *config) {
func (h *Middleware) configure(c *config) {
h.tracer = c.Tracer
h.meter = c.Meter
h.propagators = c.Propagators
Expand All @@ -100,7 +118,7 @@ func handleErr(err error) {
}
}

func (h *Handler) createMeasures() {
func (h *Middleware) createMeasures() {
h.counters = make(map[string]instrument.Int64Counter)
h.valueRecorders = make(map[string]instrument.Float64Histogram)

Expand All @@ -118,13 +136,14 @@ func (h *Handler) createMeasures() {
h.valueRecorders[ServerLatency] = serverLatencyMeasure
}

// ServeHTTP serves HTTP requests (http.Handler).
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// ServeHTTP sets up tracing and calls the given next http.Handler with the span
// context injected into the request context.
func (h *Middleware) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.Handler) {
requestStartTime := time.Now()
for _, f := range h.filters {
if !f(r) {
// Simply pass through to the handler if a filter rejects the request
h.handler.ServeHTTP(w, r)
next.ServeHTTP(w, r)
return
}
}
Expand Down Expand Up @@ -210,7 +229,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
labeler := &Labeler{}
ctx = injectLabeler(ctx, labeler)

h.handler.ServeHTTP(w, r.WithContext(ctx))
next.ServeHTTP(w, r.WithContext(ctx))

setAfterServeAttributes(span, bw.read, rww.written, rww.statusCode, bw.err, rww.err)

Expand Down

0 comments on commit 3c5a1c1

Please sign in to comment.