Skip to content
Merged
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- Add `http.route` metric attribute in `go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin`. (#7275)
- Add the `WithSpanStartOptions` option to add custom options to new spans `go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin`. (#7261)
- Add instrumentation support for `go.mongodb.org/mongo-driver/v2` in `go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/v2/mongo/otelmongo`. (#6539)
- Rerun the span name formatter after the request ran if a `req.Pattern` is set, so the span name can include it in `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp`. (#7192)

### Changed

Expand Down
4 changes: 4 additions & 0 deletions instrumentation/net/http/otelhttp/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,10 @@ func WithMessageEvents(events ...event) Option {

// WithSpanNameFormatter takes a function that will be called on every
// request and the returned string will become the Span Name.
//
// When using [http.ServeMux] (or any middleware that sets the Pattern of [http.Request]),
// the span name formatter will run twice. Once when the span is created, and
// second time after the middleware, so the pattern can be used.
func WithSpanNameFormatter(f func(operation string, r *http.Request) string) Option {
return optionFunc(func(c *config) {
c.SpanNameFormatter = f
Expand Down
7 changes: 6 additions & 1 deletion instrumentation/net/http/otelhttp/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,12 @@ func (h *middleware) serveHTTP(w http.ResponseWriter, r *http.Request, next http
ctx = ContextWithLabeler(ctx, labeler)
}

next.ServeHTTP(w, r.WithContext(ctx))
r = r.WithContext(ctx)
next.ServeHTTP(w, r)

if r.Pattern != "" {
span.SetName(h.spanNameFormatter(h.operation, r))
}

statusCode := rww.StatusCode()
bytesWritten := rww.BytesWritten()
Expand Down
60 changes: 60 additions & 0 deletions instrumentation/net/http/otelhttp/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,66 @@ func TestHandlerRequestWithTraceContext(t *testing.T) {
assert.Equal(t, spans[1].SpanContext().SpanID(), spans[0].Parent().SpanID())
}

func TestWithSpanNameFormatter(t *testing.T) {
for _, tt := range []struct {
name string

formatter func(operation string, r *http.Request) string
wantSpanName string
}{
{
name: "with the default span name formatter",
wantSpanName: "test_handler",
},
{
name: "with a custom span name formatter",
formatter: func(op string, r *http.Request) string {
return fmt.Sprintf("%s %s", r.Method, r.URL.Path)
},
wantSpanName: "GET /foo/123",
},
{
name: "with a custom span name formatter using the pattern",
formatter: func(op string, r *http.Request) string {
return fmt.Sprintf("%s %s", r.Method, r.Pattern)
},
wantSpanName: "GET /foo/{id}",
},
} {
t.Run(tt.name, func(t *testing.T) {
spanRecorder := tracetest.NewSpanRecorder()
provider := sdktrace.NewTracerProvider(
sdktrace.WithSpanProcessor(spanRecorder),
)

opts := []Option{
WithTracerProvider(provider),
}
if tt.formatter != nil {
opts = append(opts, WithSpanNameFormatter(tt.formatter))
}

mux := http.NewServeMux()
mux.Handle("/foo/{id}", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Nothing to do here
}))
h := NewHandler(mux, "test_handler", opts...)

r, err := http.NewRequest(http.MethodGet, "http://localhost/foo/123", nil)
require.NoError(t, err)

rr := httptest.NewRecorder()
h.ServeHTTP(rr, r)
assert.Equal(t, http.StatusOK, rr.Result().StatusCode)

assert.NoError(t, spanRecorder.ForceFlush(context.Background()))
spans := spanRecorder.Ended()
assert.Len(t, spans, 1)
assert.Equal(t, tt.wantSpanName, spans[0].Name())
})
}
}

func TestWithPublicEndpoint(t *testing.T) {
spanRecorder := tracetest.NewSpanRecorder()
provider := sdktrace.NewTracerProvider(
Expand Down
Loading