Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WithRouteTag adds HTTP route attribute to metrics #615

Merged
merged 12 commits into from Jul 31, 2023
4 changes: 4 additions & 0 deletions CHANGELOG.md
Expand Up @@ -14,6 +14,10 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- 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)

### Changed

- Add route tag to metrics as well as traces when using `otelhttp.WithRouteTag()`. (#615)

### Fixed

- AWS XRay Remote Sampling to cap quotaBalance to 1x quota in `go.opentelemetry.io/contrib/samplers/aws/xray`. (#3651, #3652)
Expand Down
8 changes: 7 additions & 1 deletion instrumentation/net/http/otelhttp/handler.go
Expand Up @@ -262,8 +262,14 @@ func setAfterServeAttributes(span trace.Span, read, wrote int64, statusCode int,
// RouteKey Tag.
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)
pellared marked this conversation as resolved.
Show resolved Hide resolved
}
}
}