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 26, 2023
1 parent 830138c commit cd6ab17
Show file tree
Hide file tree
Showing 2 changed files with 24 additions and 19 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 compatibility 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
42 changes: 23 additions & 19 deletions instrumentation/net/http/otelhttp/handler.go
Expand Up @@ -31,16 +31,10 @@ import (
"go.opentelemetry.io/otel/trace"
)

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.
type Handler struct {
// 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 +54,16 @@ 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 NewMiddleware(operation, opts...)(handler)
}

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

Expand All @@ -77,10 +76,14 @@ func NewHandler(handler http.Handler, operation string, opts ...Option) http.Han
h.configure(c)
h.createMeasures()

return &h
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
h.serveHTTP(w, r, next)
})
}
}

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 +103,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 +121,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 +214,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 cd6ab17

Please sign in to comment.