Skip to content

Commit

Permalink
otelmux: Add new WithSpanNameFormatter option
Browse files Browse the repository at this point in the history
Signed-off-by: Dave Henderson <dhenderson@gmail.com>
  • Loading branch information
hairyhenderson committed Nov 28, 2022
1 parent 01b39e7 commit 9e2f09a
Show file tree
Hide file tree
Showing 5 changed files with 85 additions and 17 deletions.
17 changes: 15 additions & 2 deletions instrumentation/github.com/gorilla/mux/otelmux/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,17 @@
package otelmux // import "go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux"

import (
"net/http"

"go.opentelemetry.io/otel/propagation"
oteltrace "go.opentelemetry.io/otel/trace"
)

// config is used to configure the mux middleware.
type config struct {
TracerProvider oteltrace.TracerProvider
Propagators propagation.TextMapPropagator
TracerProvider oteltrace.TracerProvider
Propagators propagation.TextMapPropagator
spanNameFormatter func(string, *http.Request) string
}

// Option specifies instrumentation configuration options.
Expand Down Expand Up @@ -56,3 +59,13 @@ func WithTracerProvider(provider oteltrace.TracerProvider) Option {
}
})
}

// WithSpanNameFormatter specifies a function to use for generating a custom span
// name. By default, the route name (path template or regexp) is used. The route
// name is provided so you can use it in the span name without needing to
// duplicate the logic for extracting it from the request.
func WithSpanNameFormatter(fn func(routeName string, r *http.Request) string) Option {
return optionFunc(func(cfg *config) {
cfg.spanNameFormatter = fn
})
}
2 changes: 2 additions & 0 deletions instrumentation/github.com/gorilla/mux/otelmux/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ require (
github.com/gorilla/mux v1.8.0
github.com/stretchr/testify v1.8.1
go.opentelemetry.io/otel v1.11.1
go.opentelemetry.io/otel/sdk v1.11.1
go.opentelemetry.io/otel/trace v1.11.1
)

Expand All @@ -15,5 +16,6 @@ require (
github.com/go-logr/logr v1.2.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
4 changes: 4 additions & 0 deletions instrumentation/github.com/gorilla/mux/otelmux/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,12 @@ github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKs
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
go.opentelemetry.io/otel v1.11.1 h1:4WLLAmcfkmDk2ukNXJyq3/kiz/3UzCaYq6PskJsaou4=
go.opentelemetry.io/otel v1.11.1/go.mod h1:1nNhXBbWSD0nsL38H6btgnFN2k4i0sNLHNNMZMSbUGE=
go.opentelemetry.io/otel/sdk v1.11.1 h1:F7KmQgoHljhUuJyA+9BiU+EkJfyX5nVVF4wyzWZpKxs=
go.opentelemetry.io/otel/sdk v1.11.1/go.mod h1:/l3FE4SupHJ12TduVjUkZtlfFqDCQJlOlithYrdktys=
go.opentelemetry.io/otel/trace v1.11.1 h1:ofxdnzsNrGBYXbP7t7zpUK281+go5rF7dvdIZXF8gdQ=
go.opentelemetry.io/otel/trace v1.11.1/go.mod h1:f/Q9G7vzk5u91PhbmKbg1Qn0rzH1LJ4vbPHFGkTPtOk=
golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8 h1:h+EGohizhe9XlX18rfpa8k8RAc5XyaeamM+0VHRd4lc=
golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
Expand Down
38 changes: 23 additions & 15 deletions instrumentation/github.com/gorilla/mux/otelmux/mux.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,21 +51,26 @@ func Middleware(service string, opts ...Option) mux.MiddlewareFunc {
if cfg.Propagators == nil {
cfg.Propagators = otel.GetTextMapPropagator()
}
if cfg.spanNameFormatter == nil {
cfg.spanNameFormatter = defaultSpanNameFunc
}
return func(handler http.Handler) http.Handler {
return traceware{
service: service,
tracer: tracer,
propagators: cfg.Propagators,
handler: handler,
service: service,
tracer: tracer,
propagators: cfg.Propagators,
handler: handler,
spanNameFormatter: cfg.spanNameFormatter,
}
}
}

type traceware struct {
service string
tracer oteltrace.Tracer
propagators propagation.TextMapPropagator
handler http.Handler
service string
tracer oteltrace.Tracer
propagators propagation.TextMapPropagator
handler http.Handler
spanNameFormatter func(string, *http.Request) string
}

type recordingResponseWriter struct {
Expand Down Expand Up @@ -111,32 +116,35 @@ func putRRW(rrw *recordingResponseWriter) {
rrwPool.Put(rrw)
}

// defaultSpanNameFunc just reuses the route name as the span name.
func defaultSpanNameFunc(routeName string, _ *http.Request) string { return routeName }

// ServeHTTP implements the http.Handler interface. It does the actual
// tracing of the request.
func (tw traceware) ServeHTTP(w http.ResponseWriter, r *http.Request) {
ctx := tw.propagators.Extract(r.Context(), propagation.HeaderCarrier(r.Header))
spanName := ""
routeStr := ""
route := mux.CurrentRoute(r)
if route != nil {
var err error
spanName, err = route.GetPathTemplate()
routeStr, err = route.GetPathTemplate()
if err != nil {
spanName, err = route.GetPathRegexp()
routeStr, err = route.GetPathRegexp()
if err != nil {
spanName = ""
routeStr = ""
}
}
}
routeStr := spanName
if spanName == "" {
spanName = fmt.Sprintf("HTTP %s route not found", r.Method)
if routeStr == "" {
routeStr = fmt.Sprintf("HTTP %s route not found", r.Method)
}
opts := []oteltrace.SpanStartOption{
oteltrace.WithAttributes(semconv.NetAttributesFromHTTPRequest("tcp", r)...),
oteltrace.WithAttributes(semconv.EndUserAttributesFromHTTPRequest(r)...),
oteltrace.WithAttributes(semconv.HTTPServerAttributesFromHTTPRequest(tw.service, routeStr, r)...),
oteltrace.WithSpanKind(oteltrace.SpanKindServer),
}
spanName := tw.spanNameFormatter(routeStr, r)
ctx, span := tw.tracer.Start(ctx, spanName, opts...)
defer span.End()
r2 := r.WithContext(ctx)
Expand Down
41 changes: 41 additions & 0 deletions instrumentation/github.com/gorilla/mux/otelmux/mux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package otelmux
import (
"bufio"
"context"
"fmt"
"io"
"net"
"net/http"
Expand All @@ -25,9 +26,12 @@ import (

"github.com/gorilla/mux"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/propagation"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
"go.opentelemetry.io/otel/sdk/trace/tracetest"
"go.opentelemetry.io/otel/trace"
)

Expand Down Expand Up @@ -162,3 +166,40 @@ func TestResponseWriterInterfaces(t *testing.T) {

router.ServeHTTP(w, r)
}

func TestCustomSpanNameFormatter(t *testing.T) {
exporter := tracetest.NewInMemoryExporter()
t.Cleanup(exporter.Reset)

tp := sdktrace.NewTracerProvider(sdktrace.WithSyncer(exporter))

routeTpl := "/user/{id}"

testdata := []struct {
spanNameFormatter func(string, *http.Request) string
expected string
}{
{nil, routeTpl},
{func(string, *http.Request) string { return "custom" }, "custom"},
{func(name string, r *http.Request) string {
return fmt.Sprintf("%s %s", r.Method, name)
}, "GET " + routeTpl},
}

for _, d := range testdata {
router := mux.NewRouter()
router.Use(Middleware("foobar", WithTracerProvider(tp), WithSpanNameFormatter(d.spanNameFormatter)))
router.HandleFunc(routeTpl, func(w http.ResponseWriter, r *http.Request) {})

r := httptest.NewRequest("GET", "/user/123", nil)
w := httptest.NewRecorder()

router.ServeHTTP(w, r)

spans := exporter.GetSpans()
require.Len(t, spans, 1)
assert.Equal(t, d.expected, spans[0].Name)

exporter.Reset()
}
}

0 comments on commit 9e2f09a

Please sign in to comment.