Skip to content

Commit 9e2f09a

Browse files
otelmux: Add new WithSpanNameFormatter option
Signed-off-by: Dave Henderson <[email protected]>
1 parent 01b39e7 commit 9e2f09a

File tree

5 files changed

+85
-17
lines changed

5 files changed

+85
-17
lines changed

instrumentation/github.com/gorilla/mux/otelmux/config.go

+15-2
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,17 @@
1515
package otelmux // import "go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux"
1616

1717
import (
18+
"net/http"
19+
1820
"go.opentelemetry.io/otel/propagation"
1921
oteltrace "go.opentelemetry.io/otel/trace"
2022
)
2123

2224
// config is used to configure the mux middleware.
2325
type config struct {
24-
TracerProvider oteltrace.TracerProvider
25-
Propagators propagation.TextMapPropagator
26+
TracerProvider oteltrace.TracerProvider
27+
Propagators propagation.TextMapPropagator
28+
spanNameFormatter func(string, *http.Request) string
2629
}
2730

2831
// Option specifies instrumentation configuration options.
@@ -56,3 +59,13 @@ func WithTracerProvider(provider oteltrace.TracerProvider) Option {
5659
}
5760
})
5861
}
62+
63+
// WithSpanNameFormatter specifies a function to use for generating a custom span
64+
// name. By default, the route name (path template or regexp) is used. The route
65+
// name is provided so you can use it in the span name without needing to
66+
// duplicate the logic for extracting it from the request.
67+
func WithSpanNameFormatter(fn func(routeName string, r *http.Request) string) Option {
68+
return optionFunc(func(cfg *config) {
69+
cfg.spanNameFormatter = fn
70+
})
71+
}

instrumentation/github.com/gorilla/mux/otelmux/go.mod

+2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ require (
77
github.com/gorilla/mux v1.8.0
88
github.com/stretchr/testify v1.8.1
99
go.opentelemetry.io/otel v1.11.1
10+
go.opentelemetry.io/otel/sdk v1.11.1
1011
go.opentelemetry.io/otel/trace v1.11.1
1112
)
1213

@@ -15,5 +16,6 @@ require (
1516
github.com/go-logr/logr v1.2.3 // indirect
1617
github.com/go-logr/stdr v1.2.2 // indirect
1718
github.com/pmezard/go-difflib v1.0.0 // indirect
19+
golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8 // indirect
1820
gopkg.in/yaml.v3 v3.0.1 // indirect
1921
)

instrumentation/github.com/gorilla/mux/otelmux/go.sum

+4
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,12 @@ github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKs
2222
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
2323
go.opentelemetry.io/otel v1.11.1 h1:4WLLAmcfkmDk2ukNXJyq3/kiz/3UzCaYq6PskJsaou4=
2424
go.opentelemetry.io/otel v1.11.1/go.mod h1:1nNhXBbWSD0nsL38H6btgnFN2k4i0sNLHNNMZMSbUGE=
25+
go.opentelemetry.io/otel/sdk v1.11.1 h1:F7KmQgoHljhUuJyA+9BiU+EkJfyX5nVVF4wyzWZpKxs=
26+
go.opentelemetry.io/otel/sdk v1.11.1/go.mod h1:/l3FE4SupHJ12TduVjUkZtlfFqDCQJlOlithYrdktys=
2527
go.opentelemetry.io/otel/trace v1.11.1 h1:ofxdnzsNrGBYXbP7t7zpUK281+go5rF7dvdIZXF8gdQ=
2628
go.opentelemetry.io/otel/trace v1.11.1/go.mod h1:f/Q9G7vzk5u91PhbmKbg1Qn0rzH1LJ4vbPHFGkTPtOk=
29+
golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8 h1:h+EGohizhe9XlX18rfpa8k8RAc5XyaeamM+0VHRd4lc=
30+
golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
2731
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
2832
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
2933
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

instrumentation/github.com/gorilla/mux/otelmux/mux.go

+23-15
Original file line numberDiff line numberDiff line change
@@ -51,21 +51,26 @@ func Middleware(service string, opts ...Option) mux.MiddlewareFunc {
5151
if cfg.Propagators == nil {
5252
cfg.Propagators = otel.GetTextMapPropagator()
5353
}
54+
if cfg.spanNameFormatter == nil {
55+
cfg.spanNameFormatter = defaultSpanNameFunc
56+
}
5457
return func(handler http.Handler) http.Handler {
5558
return traceware{
56-
service: service,
57-
tracer: tracer,
58-
propagators: cfg.Propagators,
59-
handler: handler,
59+
service: service,
60+
tracer: tracer,
61+
propagators: cfg.Propagators,
62+
handler: handler,
63+
spanNameFormatter: cfg.spanNameFormatter,
6064
}
6165
}
6266
}
6367

6468
type traceware struct {
65-
service string
66-
tracer oteltrace.Tracer
67-
propagators propagation.TextMapPropagator
68-
handler http.Handler
69+
service string
70+
tracer oteltrace.Tracer
71+
propagators propagation.TextMapPropagator
72+
handler http.Handler
73+
spanNameFormatter func(string, *http.Request) string
6974
}
7075

7176
type recordingResponseWriter struct {
@@ -111,32 +116,35 @@ func putRRW(rrw *recordingResponseWriter) {
111116
rrwPool.Put(rrw)
112117
}
113118

119+
// defaultSpanNameFunc just reuses the route name as the span name.
120+
func defaultSpanNameFunc(routeName string, _ *http.Request) string { return routeName }
121+
114122
// ServeHTTP implements the http.Handler interface. It does the actual
115123
// tracing of the request.
116124
func (tw traceware) ServeHTTP(w http.ResponseWriter, r *http.Request) {
117125
ctx := tw.propagators.Extract(r.Context(), propagation.HeaderCarrier(r.Header))
118-
spanName := ""
126+
routeStr := ""
119127
route := mux.CurrentRoute(r)
120128
if route != nil {
121129
var err error
122-
spanName, err = route.GetPathTemplate()
130+
routeStr, err = route.GetPathTemplate()
123131
if err != nil {
124-
spanName, err = route.GetPathRegexp()
132+
routeStr, err = route.GetPathRegexp()
125133
if err != nil {
126-
spanName = ""
134+
routeStr = ""
127135
}
128136
}
129137
}
130-
routeStr := spanName
131-
if spanName == "" {
132-
spanName = fmt.Sprintf("HTTP %s route not found", r.Method)
138+
if routeStr == "" {
139+
routeStr = fmt.Sprintf("HTTP %s route not found", r.Method)
133140
}
134141
opts := []oteltrace.SpanStartOption{
135142
oteltrace.WithAttributes(semconv.NetAttributesFromHTTPRequest("tcp", r)...),
136143
oteltrace.WithAttributes(semconv.EndUserAttributesFromHTTPRequest(r)...),
137144
oteltrace.WithAttributes(semconv.HTTPServerAttributesFromHTTPRequest(tw.service, routeStr, r)...),
138145
oteltrace.WithSpanKind(oteltrace.SpanKindServer),
139146
}
147+
spanName := tw.spanNameFormatter(routeStr, r)
140148
ctx, span := tw.tracer.Start(ctx, spanName, opts...)
141149
defer span.End()
142150
r2 := r.WithContext(ctx)

instrumentation/github.com/gorilla/mux/otelmux/mux_test.go

+41
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ package otelmux
1717
import (
1818
"bufio"
1919
"context"
20+
"fmt"
2021
"io"
2122
"net"
2223
"net/http"
@@ -25,9 +26,12 @@ import (
2526

2627
"github.com/gorilla/mux"
2728
"github.com/stretchr/testify/assert"
29+
"github.com/stretchr/testify/require"
2830

2931
"go.opentelemetry.io/otel"
3032
"go.opentelemetry.io/otel/propagation"
33+
sdktrace "go.opentelemetry.io/otel/sdk/trace"
34+
"go.opentelemetry.io/otel/sdk/trace/tracetest"
3135
"go.opentelemetry.io/otel/trace"
3236
)
3337

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

163167
router.ServeHTTP(w, r)
164168
}
169+
170+
func TestCustomSpanNameFormatter(t *testing.T) {
171+
exporter := tracetest.NewInMemoryExporter()
172+
t.Cleanup(exporter.Reset)
173+
174+
tp := sdktrace.NewTracerProvider(sdktrace.WithSyncer(exporter))
175+
176+
routeTpl := "/user/{id}"
177+
178+
testdata := []struct {
179+
spanNameFormatter func(string, *http.Request) string
180+
expected string
181+
}{
182+
{nil, routeTpl},
183+
{func(string, *http.Request) string { return "custom" }, "custom"},
184+
{func(name string, r *http.Request) string {
185+
return fmt.Sprintf("%s %s", r.Method, name)
186+
}, "GET " + routeTpl},
187+
}
188+
189+
for _, d := range testdata {
190+
router := mux.NewRouter()
191+
router.Use(Middleware("foobar", WithTracerProvider(tp), WithSpanNameFormatter(d.spanNameFormatter)))
192+
router.HandleFunc(routeTpl, func(w http.ResponseWriter, r *http.Request) {})
193+
194+
r := httptest.NewRequest("GET", "/user/123", nil)
195+
w := httptest.NewRecorder()
196+
197+
router.ServeHTTP(w, r)
198+
199+
spans := exporter.GetSpans()
200+
require.Len(t, spans, 1)
201+
assert.Equal(t, d.expected, spans[0].Name)
202+
203+
exporter.Reset()
204+
}
205+
}

0 commit comments

Comments
 (0)