Skip to content

Commit

Permalink
WithRouteTag adds HTTP route attribute to metrics (#615)
Browse files Browse the repository at this point in the history
  • Loading branch information
charleskorn committed Jul 31, 2023
1 parent bf2ae27 commit 525d6c0
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 3 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -13,6 +13,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- Add `NewMiddleware` function in `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp`. (#2964)
- Add the new `go.opentelemetry.io/contrib/instrgen` package to provide auto-generated source code instrumentation. (#3068, #3108)
- The `go.opentelemetry.io/contrib/exporters/autoexport` package to provide configuration of trace exporters with useful defaults and envar support. (#2753, #4100)
- `WithRouteTag` in `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp` adds HTTP route attribute to metrics. (#615)

### Fixed

Expand Down
12 changes: 9 additions & 3 deletions instrumentation/net/http/otelhttp/handler.go
Expand Up @@ -258,12 +258,18 @@ func setAfterServeAttributes(span trace.Span, read, wrote int64, statusCode int,
span.SetAttributes(attributes...)
}

// WithRouteTag annotates a span with the provided route name using the
// RouteKey Tag.
// WithRouteTag annotates spans and metrics with the provided route name
// with HTTP route attribute.
func WithRouteTag(route string, h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
attr := semconv.HTTPRouteKey.String(route)

span := trace.SpanFromContext(r.Context())
span.SetAttributes(semconv.HTTPRoute(route))
span.SetAttributes(attr)

labeler, _ := LabelerFromContext(r.Context())
labeler.Add(attr)

h.ServeHTTP(w, r)
})
}
67 changes: 67 additions & 0 deletions instrumentation/net/http/otelhttp/test/handler_test.go
Expand Up @@ -468,3 +468,70 @@ func TestSpanStatus(t *testing.T) {
})
}
}

func TestWithRouteTag(t *testing.T) {
route := "/some/route"

spanRecorder := tracetest.NewSpanRecorder()
tracerProvider := sdktrace.NewTracerProvider()
tracerProvider.RegisterSpanProcessor(spanRecorder)

metricReader := metric.NewManualReader()
meterProvider := metric.NewMeterProvider(metric.WithReader(metricReader))

h := otelhttp.NewHandler(
otelhttp.WithRouteTag(
route,
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusTeapot)
}),
),
"test_handler",
otelhttp.WithTracerProvider(tracerProvider),
otelhttp.WithMeterProvider(meterProvider),
)

h.ServeHTTP(httptest.NewRecorder(), httptest.NewRequest(http.MethodGet, "/", nil))
want := semconv.HTTPRouteKey.String(route)

require.Len(t, spanRecorder.Ended(), 1, "should emit a span")
gotSpan := spanRecorder.Ended()[0]
require.Contains(t, gotSpan.Attributes(), want, "should add route to span attributes")

rm := metricdata.ResourceMetrics{}
err := metricReader.Collect(context.Background(), &rm)
require.NoError(t, err)
require.Len(t, rm.ScopeMetrics, 1, "should emit metrics for one scope")
gotMetrics := rm.ScopeMetrics[0].Metrics

for _, m := range gotMetrics {
switch d := m.Data.(type) {
case metricdata.Sum[int64]:
require.Len(t, d.DataPoints, 1, "metric '%v' should have exactly one data point", m.Name)
require.Contains(t, d.DataPoints[0].Attributes.ToSlice(), want, "should add route to attributes for metric '%v'", m.Name)

case metricdata.Sum[float64]:
require.Len(t, d.DataPoints, 1, "metric '%v' should have exactly one data point", m.Name)
require.Contains(t, d.DataPoints[0].Attributes.ToSlice(), want, "should add route to attributes for metric '%v'", m.Name)

case metricdata.Histogram[int64]:
require.Len(t, d.DataPoints, 1, "metric '%v' should have exactly one data point", m.Name)
require.Contains(t, d.DataPoints[0].Attributes.ToSlice(), want, "should add route to attributes for metric '%v'", m.Name)

case metricdata.Histogram[float64]:
require.Len(t, d.DataPoints, 1, "metric '%v' should have exactly one data point", m.Name)
require.Contains(t, d.DataPoints[0].Attributes.ToSlice(), want, "should add route to attributes for metric '%v'", m.Name)

case metricdata.Gauge[int64]:
require.Len(t, d.DataPoints, 1, "metric '%v' should have exactly one data point", m.Name)
require.Contains(t, d.DataPoints[0].Attributes.ToSlice(), want, "should add route to attributes for metric '%v'", m.Name)

case metricdata.Gauge[float64]:
require.Len(t, d.DataPoints, 1, "metric '%v' should have exactly one data point", m.Name)
require.Contains(t, d.DataPoints[0].Attributes.ToSlice(), want, "should add route to attributes for metric '%v'", m.Name)

default:
require.Fail(t, "metric has unexpected data type", "metric '%v' has unexpected data type %T", m.Name, m.Data)
}
}
}

0 comments on commit 525d6c0

Please sign in to comment.