From 488d98d4db6bc8b04146cab8cdd4228416c09fc2 Mon Sep 17 00:00:00 2001 From: Vinay Vennela <52368723+vinayvennela@users.noreply.github.com> Date: Thu, 2 Oct 2025 15:08:09 +0530 Subject: [PATCH 1/6] Adding http.route metric attribute to metrics emitted by otelmux instrumentation library --- .../github.com/gorilla/mux/otelmux/mux.go | 3 + .../gorilla/mux/otelmux/muxtest_test.go | 58 +++++++++++++++++-- 2 files changed, 57 insertions(+), 4 deletions(-) diff --git a/instrumentation/github.com/gorilla/mux/otelmux/mux.go b/instrumentation/github.com/gorilla/mux/otelmux/mux.go index 553c482fea0..ceb6a875f25 100644 --- a/instrumentation/github.com/gorilla/mux/otelmux/mux.go +++ b/instrumentation/github.com/gorilla/mux/otelmux/mux.go @@ -193,6 +193,9 @@ func (tw traceware) ServeHTTP(w http.ResponseWriter, r *http.Request) { AdditionalAttributes: tw.metricAttributesFromRequest(r), } + rAttr := tw.semconv.Route(routeStr) + metricAttributes.AdditionalAttributes = append(metricAttributes.AdditionalAttributes, rAttr) + tw.semconv.RecordMetrics(ctx, semconv.ServerMetricData{ ServerName: tw.service, ResponseSize: rww.BytesWritten(), diff --git a/instrumentation/github.com/gorilla/mux/otelmux/muxtest_test.go b/instrumentation/github.com/gorilla/mux/otelmux/muxtest_test.go index b75c83e89d2..db7ab5f7e02 100644 --- a/instrumentation/github.com/gorilla/mux/otelmux/muxtest_test.go +++ b/instrumentation/github.com/gorilla/mux/otelmux/muxtest_test.go @@ -373,11 +373,58 @@ func TestWithPublicEndpointFn(t *testing.T) { } } +func TestDefaultMetricAttributes(t *testing.T) { + defaultMetricAttributes := []attribute.KeyValue{ + attribute.String("http.route", "/user/{id:[0-9]+}"), + attribute.String("server.address", "foobar"), + } + + reader := sdkmetric.NewManualReader() + meterProvider := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader)) + + router := mux.NewRouter() + router.Use(otelmux.Middleware("foobar", + otelmux.WithMeterProvider(meterProvider), + )) + + router.HandleFunc("/user/{id:[0-9]+}", ok) + r, err := http.NewRequest(http.MethodGet, "http://localhost/user/123", http.NoBody) + require.NoError(t, err) + rr := httptest.NewRecorder() + router.ServeHTTP(rr, r) + + rm := metricdata.ResourceMetrics{} + err = reader.Collect(t.Context(), &rm) + require.NoError(t, err) + require.Len(t, rm.ScopeMetrics, 1) + assert.Len(t, rm.ScopeMetrics[0].Metrics, 3) + + // Verify that the additional attribute is present in the metrics. + for _, m := range rm.ScopeMetrics[0].Metrics { + switch m.Data.(type) { + case metricdata.Histogram[int64]: + d, ok := m.Data.(metricdata.Histogram[int64]) + assert.True(t, ok) + assert.Len(t, d.DataPoints, 1) + containsAttributes(t, d.DataPoints[0].Attributes, defaultMetricAttributes) + case metricdata.Histogram[float64]: + d, ok := m.Data.(metricdata.Histogram[float64]) + assert.True(t, ok) + assert.Len(t, d.DataPoints, 1) + containsAttributes(t, d.DataPoints[0].Attributes, defaultMetricAttributes) + default: + // Intentional failure to keep the test updated with changes in metrics + t.Errorf("Unexpected metric type") + } + } + +} + func TestHandlerWithMetricAttributesFn(t *testing.T) { const ( - serverRequestSize = "http.server.request.size" - serverResponseSize = "http.server.response.size" - serverDuration = "http.server.duration" + serverRequestSize = "http.server.request.body.size" + serverResponseSize = "http.server.response.body.size" + serverDuration = "http.server.request.duration" ) testCases := []struct { name string @@ -430,7 +477,7 @@ func TestHandlerWithMetricAttributesFn(t *testing.T) { for _, m := range rm.ScopeMetrics[0].Metrics { switch m.Name { case serverRequestSize, serverResponseSize: - d, ok := m.Data.(metricdata.Sum[int64]) + d, ok := m.Data.(metricdata.Histogram[int64]) assert.True(t, ok) assert.Len(t, d.DataPoints, 1) containsAttributes(t, d.DataPoints[0].Attributes, testCases[0].wantAdditionalAttribute) @@ -439,6 +486,9 @@ func TestHandlerWithMetricAttributesFn(t *testing.T) { assert.True(t, ok) assert.Len(t, d.DataPoints, 1) containsAttributes(t, d.DataPoints[0].Attributes, testCases[0].wantAdditionalAttribute) + default: + // Intentional failure to keep the test updated with changes in metrics + t.Errorf("Unexpected metric name") } } } From 4c85578f1392e989ea9566fcc0a151a0f849903f Mon Sep 17 00:00:00 2001 From: Vinay Vennela <52368723+vinayvennela@users.noreply.github.com> Date: Thu, 2 Oct 2025 15:21:54 +0530 Subject: [PATCH 2/6] added Changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 272d92d1800..d866b74d2f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - Drop support for [Go 1.23]. (#7831) - Remove deprecated `go.opentelemetry.io/contrib/detectors/aws/ec2` module, please use `go.opentelemetry.io/contrib/detectors/aws/ec2/v2` instead. (#7841) - Remove the deprecated `Extract` and `Inject` functions from `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc`. (#7952) +- Adding `http.route` metric attribute to otelmux instrumentation library. (#7966) From 171c34b1a88c342181bd8250900df82cdc518ba4 Mon Sep 17 00:00:00 2001 From: Vinay Vennela <52368723+vinayvennela@users.noreply.github.com> Date: Thu, 2 Oct 2025 15:43:33 +0530 Subject: [PATCH 3/6] Update CHANGELOG.md Co-authored-by: Damien Mathieu <42@dmathieu.com> --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d866b74d2f7..8c55fedbfdf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - Drop support for [Go 1.23]. (#7831) - Remove deprecated `go.opentelemetry.io/contrib/detectors/aws/ec2` module, please use `go.opentelemetry.io/contrib/detectors/aws/ec2/v2` instead. (#7841) - Remove the deprecated `Extract` and `Inject` functions from `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc`. (#7952) -- Adding `http.route` metric attribute to otelmux instrumentation library. (#7966) +- Add the `http.route` metric attribute to `go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux`. (#7966) From 8f1af5e08dd6dc8a3b01ac29505bf51309dc4336 Mon Sep 17 00:00:00 2001 From: Vinay Vennela <52368723+vinayvennela@users.noreply.github.com> Date: Thu, 2 Oct 2025 16:21:59 +0530 Subject: [PATCH 4/6] Refactored code to add route to metric attributes --- .../mux/otelmux/internal/semconv/server.go | 15 ++++++++++++--- .../mux/otelmux/internal/semconv/server_test.go | 8 ++++++-- .../github.com/gorilla/mux/otelmux/mux.go | 4 +--- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/instrumentation/github.com/gorilla/mux/otelmux/internal/semconv/server.go b/instrumentation/github.com/gorilla/mux/otelmux/internal/semconv/server.go index e80dae1be46..d48bcea23e5 100644 --- a/instrumentation/github.com/gorilla/mux/otelmux/internal/semconv/server.go +++ b/instrumentation/github.com/gorilla/mux/otelmux/internal/semconv/server.go @@ -36,7 +36,7 @@ type ResponseTelemetry struct { WriteError error } -type HTTPServer struct{ +type HTTPServer struct { requestBodySizeHistogram httpconv.ServerRequestBodySize responseBodySizeHistogram httpconv.ServerResponseBodySize requestDurationHistogram httpconv.ServerRequestDuration @@ -240,6 +240,7 @@ type ServerMetricData struct { type MetricAttributes struct { Req *http.Request StatusCode int + Route string AdditionalAttributes []attribute.KeyValue } @@ -265,7 +266,7 @@ var ( ) func (n HTTPServer) RecordMetrics(ctx context.Context, md ServerMetricData) { - attributes := n.MetricAttributes(md.ServerName, md.Req, md.StatusCode, md.AdditionalAttributes) + attributes := n.MetricAttributes(md.ServerName, md.Req, md.StatusCode, md.Route, md.AdditionalAttributes) o := metric.WithAttributeSet(attribute.NewSet(attributes...)) recordOpts := metricRecordOptionPool.Get().(*[]metric.RecordOption) *recordOpts = append(*recordOpts, o) @@ -342,7 +343,7 @@ func (n HTTPServer) Route(route string) attribute.KeyValue { return semconv.HTTPRoute(route) } -func (n HTTPServer) MetricAttributes(server string, req *http.Request, statusCode int, additionalAttributes []attribute.KeyValue) []attribute.KeyValue { +func (n HTTPServer) MetricAttributes(server string, req *http.Request, statusCode int, route string, additionalAttributes []attribute.KeyValue) []attribute.KeyValue { num := len(additionalAttributes) + 3 var host string var p int @@ -371,6 +372,10 @@ func (n HTTPServer) MetricAttributes(server string, req *http.Request, statusCod num++ } + if route != "" { + num++ + } + attributes := slices.Grow(additionalAttributes, num) attributes = append(attributes, semconv.HTTPRequestMethodKey.String(standardizeHTTPMethod(req.Method)), @@ -390,5 +395,9 @@ func (n HTTPServer) MetricAttributes(server string, req *http.Request, statusCod if statusCode > 0 { attributes = append(attributes, semconv.HTTPResponseStatusCode(statusCode)) } + + if route != "" { + attributes = append(attributes, semconv.HTTPRoute(route)) + } return attributes } diff --git a/instrumentation/github.com/gorilla/mux/otelmux/internal/semconv/server_test.go b/instrumentation/github.com/gorilla/mux/otelmux/internal/semconv/server_test.go index 5e6cd3ffa99..4c905e2fa17 100644 --- a/instrumentation/github.com/gorilla/mux/otelmux/internal/semconv/server_test.go +++ b/instrumentation/github.com/gorilla/mux/otelmux/internal/semconv/server_test.go @@ -26,6 +26,7 @@ func TestHTTPServer_MetricAttributes(t *testing.T) { server string req *http.Request statusCode int + route string additionalAttributes []attribute.KeyValue wantFunc func(t *testing.T, attrs []attribute.KeyValue) }{ @@ -34,6 +35,7 @@ func TestHTTPServer_MetricAttributes(t *testing.T) { server: "", req: defaultRequest, statusCode: 200, + route: "", additionalAttributes: []attribute.KeyValue{attribute.String("test", "test")}, wantFunc: func(t *testing.T, attrs []attribute.KeyValue) { require.Len(t, attrs, 7) @@ -53,9 +55,10 @@ func TestHTTPServer_MetricAttributes(t *testing.T) { server: "example.com:9999", req: defaultRequest, statusCode: 200, + route: "/path/${id}", additionalAttributes: nil, wantFunc: func(t *testing.T, attrs []attribute.KeyValue) { - require.Len(t, attrs, 7) + require.Len(t, attrs, 8) assert.ElementsMatch(t, []attribute.KeyValue{ attribute.String("http.request.method", "GET"), attribute.String("url.scheme", "http"), @@ -64,6 +67,7 @@ func TestHTTPServer_MetricAttributes(t *testing.T) { attribute.String("network.protocol.name", "http"), attribute.String("network.protocol.version", "1.1"), attribute.Int64("http.response.status_code", 200), + attribute.String("http.route", "/path/${id}"), }, attrs) }, }, @@ -71,7 +75,7 @@ func TestHTTPServer_MetricAttributes(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := HTTPServer{}.MetricAttributes(tt.server, tt.req, tt.statusCode, tt.additionalAttributes) + got := HTTPServer{}.MetricAttributes(tt.server, tt.req, tt.statusCode, tt.route, tt.additionalAttributes) tt.wantFunc(t, got) }) } diff --git a/instrumentation/github.com/gorilla/mux/otelmux/mux.go b/instrumentation/github.com/gorilla/mux/otelmux/mux.go index ceb6a875f25..b7fd4a2201d 100644 --- a/instrumentation/github.com/gorilla/mux/otelmux/mux.go +++ b/instrumentation/github.com/gorilla/mux/otelmux/mux.go @@ -190,12 +190,10 @@ func (tw traceware) ServeHTTP(w http.ResponseWriter, r *http.Request) { metricAttributes := semconv.MetricAttributes{ Req: r, StatusCode: statusCode, + Route: routeStr, AdditionalAttributes: tw.metricAttributesFromRequest(r), } - rAttr := tw.semconv.Route(routeStr) - metricAttributes.AdditionalAttributes = append(metricAttributes.AdditionalAttributes, rAttr) - tw.semconv.RecordMetrics(ctx, semconv.ServerMetricData{ ServerName: tw.service, ResponseSize: rww.BytesWritten(), From ca035598aeef9092a38d3b3bd92732d9af9b3428 Mon Sep 17 00:00:00 2001 From: Vinay Vennela <52368723+vinayvennela@users.noreply.github.com> Date: Thu, 2 Oct 2025 18:37:02 +0530 Subject: [PATCH 5/6] Updated templates with tests --- .../otelrestful/internal/semconv/server.go | 13 +++++++++++-- .../otelrestful/internal/semconv/server_test.go | 8 ++++++-- .../gin/otelgin/internal/semconv/server.go | 13 +++++++++++-- .../gin/otelgin/internal/semconv/server_test.go | 8 ++++++-- .../gorilla/mux/otelmux/internal/semconv/server.go | 10 +++++----- .../echo/otelecho/internal/semconv/server.go | 13 +++++++++++-- .../echo/otelecho/internal/semconv/server_test.go | 8 ++++++-- .../otelhttptrace/internal/semconv/server.go | 13 +++++++++++-- .../otelhttptrace/internal/semconv/server_test.go | 8 ++++++-- .../net/http/otelhttp/internal/semconv/server.go | 13 +++++++++++-- .../http/otelhttp/internal/semconv/server_test.go | 8 ++++++-- internal/shared/semconv/server.go.tmpl | 13 +++++++++++-- internal/shared/semconv/server_test.go.tmpl | 8 ++++++-- 13 files changed, 107 insertions(+), 29 deletions(-) diff --git a/instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconv/server.go b/instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconv/server.go index 2214ead9ea7..95028febb32 100644 --- a/instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconv/server.go +++ b/instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconv/server.go @@ -240,6 +240,7 @@ type ServerMetricData struct { type MetricAttributes struct { Req *http.Request StatusCode int + Route string AdditionalAttributes []attribute.KeyValue } @@ -265,7 +266,7 @@ var ( ) func (n HTTPServer) RecordMetrics(ctx context.Context, md ServerMetricData) { - attributes := n.MetricAttributes(md.ServerName, md.Req, md.StatusCode, md.AdditionalAttributes) + attributes := n.MetricAttributes(md.ServerName, md.Req, md.StatusCode, md.Route, md.AdditionalAttributes) o := metric.WithAttributeSet(attribute.NewSet(attributes...)) recordOpts := metricRecordOptionPool.Get().(*[]metric.RecordOption) *recordOpts = append(*recordOpts, o) @@ -342,7 +343,7 @@ func (n HTTPServer) Route(route string) attribute.KeyValue { return semconv.HTTPRoute(route) } -func (n HTTPServer) MetricAttributes(server string, req *http.Request, statusCode int, additionalAttributes []attribute.KeyValue) []attribute.KeyValue { +func (n HTTPServer) MetricAttributes(server string, req *http.Request, statusCode int, route string, additionalAttributes []attribute.KeyValue) []attribute.KeyValue { num := len(additionalAttributes) + 3 var host string var p int @@ -371,6 +372,10 @@ func (n HTTPServer) MetricAttributes(server string, req *http.Request, statusCod num++ } + if route != "" { + num++ + } + attributes := slices.Grow(additionalAttributes, num) attributes = append(attributes, semconv.HTTPRequestMethodKey.String(standardizeHTTPMethod(req.Method)), @@ -390,5 +395,9 @@ func (n HTTPServer) MetricAttributes(server string, req *http.Request, statusCod if statusCode > 0 { attributes = append(attributes, semconv.HTTPResponseStatusCode(statusCode)) } + + if route != "" { + attributes = append(attributes, semconv.HTTPRoute(route)) + } return attributes } diff --git a/instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconv/server_test.go b/instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconv/server_test.go index 5e6cd3ffa99..4c905e2fa17 100644 --- a/instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconv/server_test.go +++ b/instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconv/server_test.go @@ -26,6 +26,7 @@ func TestHTTPServer_MetricAttributes(t *testing.T) { server string req *http.Request statusCode int + route string additionalAttributes []attribute.KeyValue wantFunc func(t *testing.T, attrs []attribute.KeyValue) }{ @@ -34,6 +35,7 @@ func TestHTTPServer_MetricAttributes(t *testing.T) { server: "", req: defaultRequest, statusCode: 200, + route: "", additionalAttributes: []attribute.KeyValue{attribute.String("test", "test")}, wantFunc: func(t *testing.T, attrs []attribute.KeyValue) { require.Len(t, attrs, 7) @@ -53,9 +55,10 @@ func TestHTTPServer_MetricAttributes(t *testing.T) { server: "example.com:9999", req: defaultRequest, statusCode: 200, + route: "/path/${id}", additionalAttributes: nil, wantFunc: func(t *testing.T, attrs []attribute.KeyValue) { - require.Len(t, attrs, 7) + require.Len(t, attrs, 8) assert.ElementsMatch(t, []attribute.KeyValue{ attribute.String("http.request.method", "GET"), attribute.String("url.scheme", "http"), @@ -64,6 +67,7 @@ func TestHTTPServer_MetricAttributes(t *testing.T) { attribute.String("network.protocol.name", "http"), attribute.String("network.protocol.version", "1.1"), attribute.Int64("http.response.status_code", 200), + attribute.String("http.route", "/path/${id}"), }, attrs) }, }, @@ -71,7 +75,7 @@ func TestHTTPServer_MetricAttributes(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := HTTPServer{}.MetricAttributes(tt.server, tt.req, tt.statusCode, tt.additionalAttributes) + got := HTTPServer{}.MetricAttributes(tt.server, tt.req, tt.statusCode, tt.route, tt.additionalAttributes) tt.wantFunc(t, got) }) } diff --git a/instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconv/server.go b/instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconv/server.go index 000741086fb..5f1e64d9c38 100644 --- a/instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconv/server.go +++ b/instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconv/server.go @@ -240,6 +240,7 @@ type ServerMetricData struct { type MetricAttributes struct { Req *http.Request StatusCode int + Route string AdditionalAttributes []attribute.KeyValue } @@ -265,7 +266,7 @@ var ( ) func (n HTTPServer) RecordMetrics(ctx context.Context, md ServerMetricData) { - attributes := n.MetricAttributes(md.ServerName, md.Req, md.StatusCode, md.AdditionalAttributes) + attributes := n.MetricAttributes(md.ServerName, md.Req, md.StatusCode, md.Route, md.AdditionalAttributes) o := metric.WithAttributeSet(attribute.NewSet(attributes...)) recordOpts := metricRecordOptionPool.Get().(*[]metric.RecordOption) *recordOpts = append(*recordOpts, o) @@ -342,7 +343,7 @@ func (n HTTPServer) Route(route string) attribute.KeyValue { return semconv.HTTPRoute(route) } -func (n HTTPServer) MetricAttributes(server string, req *http.Request, statusCode int, additionalAttributes []attribute.KeyValue) []attribute.KeyValue { +func (n HTTPServer) MetricAttributes(server string, req *http.Request, statusCode int, route string, additionalAttributes []attribute.KeyValue) []attribute.KeyValue { num := len(additionalAttributes) + 3 var host string var p int @@ -371,6 +372,10 @@ func (n HTTPServer) MetricAttributes(server string, req *http.Request, statusCod num++ } + if route != "" { + num++ + } + attributes := slices.Grow(additionalAttributes, num) attributes = append(attributes, semconv.HTTPRequestMethodKey.String(standardizeHTTPMethod(req.Method)), @@ -390,5 +395,9 @@ func (n HTTPServer) MetricAttributes(server string, req *http.Request, statusCod if statusCode > 0 { attributes = append(attributes, semconv.HTTPResponseStatusCode(statusCode)) } + + if route != "" { + attributes = append(attributes, semconv.HTTPRoute(route)) + } return attributes } diff --git a/instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconv/server_test.go b/instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconv/server_test.go index 5e6cd3ffa99..4c905e2fa17 100644 --- a/instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconv/server_test.go +++ b/instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconv/server_test.go @@ -26,6 +26,7 @@ func TestHTTPServer_MetricAttributes(t *testing.T) { server string req *http.Request statusCode int + route string additionalAttributes []attribute.KeyValue wantFunc func(t *testing.T, attrs []attribute.KeyValue) }{ @@ -34,6 +35,7 @@ func TestHTTPServer_MetricAttributes(t *testing.T) { server: "", req: defaultRequest, statusCode: 200, + route: "", additionalAttributes: []attribute.KeyValue{attribute.String("test", "test")}, wantFunc: func(t *testing.T, attrs []attribute.KeyValue) { require.Len(t, attrs, 7) @@ -53,9 +55,10 @@ func TestHTTPServer_MetricAttributes(t *testing.T) { server: "example.com:9999", req: defaultRequest, statusCode: 200, + route: "/path/${id}", additionalAttributes: nil, wantFunc: func(t *testing.T, attrs []attribute.KeyValue) { - require.Len(t, attrs, 7) + require.Len(t, attrs, 8) assert.ElementsMatch(t, []attribute.KeyValue{ attribute.String("http.request.method", "GET"), attribute.String("url.scheme", "http"), @@ -64,6 +67,7 @@ func TestHTTPServer_MetricAttributes(t *testing.T) { attribute.String("network.protocol.name", "http"), attribute.String("network.protocol.version", "1.1"), attribute.Int64("http.response.status_code", 200), + attribute.String("http.route", "/path/${id}"), }, attrs) }, }, @@ -71,7 +75,7 @@ func TestHTTPServer_MetricAttributes(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := HTTPServer{}.MetricAttributes(tt.server, tt.req, tt.statusCode, tt.additionalAttributes) + got := HTTPServer{}.MetricAttributes(tt.server, tt.req, tt.statusCode, tt.route, tt.additionalAttributes) tt.wantFunc(t, got) }) } diff --git a/instrumentation/github.com/gorilla/mux/otelmux/internal/semconv/server.go b/instrumentation/github.com/gorilla/mux/otelmux/internal/semconv/server.go index d48bcea23e5..7364390aaf0 100644 --- a/instrumentation/github.com/gorilla/mux/otelmux/internal/semconv/server.go +++ b/instrumentation/github.com/gorilla/mux/otelmux/internal/semconv/server.go @@ -36,7 +36,7 @@ type ResponseTelemetry struct { WriteError error } -type HTTPServer struct { +type HTTPServer struct{ requestBodySizeHistogram httpconv.ServerRequestBodySize responseBodySizeHistogram httpconv.ServerResponseBodySize requestDurationHistogram httpconv.ServerRequestDuration @@ -373,8 +373,8 @@ func (n HTTPServer) MetricAttributes(server string, req *http.Request, statusCod } if route != "" { - num++ - } + num++ + } attributes := slices.Grow(additionalAttributes, num) attributes = append(attributes, @@ -397,7 +397,7 @@ func (n HTTPServer) MetricAttributes(server string, req *http.Request, statusCod } if route != "" { - attributes = append(attributes, semconv.HTTPRoute(route)) - } + attributes = append(attributes, semconv.HTTPRoute(route)) + } return attributes } diff --git a/instrumentation/github.com/labstack/echo/otelecho/internal/semconv/server.go b/instrumentation/github.com/labstack/echo/otelecho/internal/semconv/server.go index ea0fd63ad06..c860e29c225 100644 --- a/instrumentation/github.com/labstack/echo/otelecho/internal/semconv/server.go +++ b/instrumentation/github.com/labstack/echo/otelecho/internal/semconv/server.go @@ -240,6 +240,7 @@ type ServerMetricData struct { type MetricAttributes struct { Req *http.Request StatusCode int + Route string AdditionalAttributes []attribute.KeyValue } @@ -265,7 +266,7 @@ var ( ) func (n HTTPServer) RecordMetrics(ctx context.Context, md ServerMetricData) { - attributes := n.MetricAttributes(md.ServerName, md.Req, md.StatusCode, md.AdditionalAttributes) + attributes := n.MetricAttributes(md.ServerName, md.Req, md.StatusCode, md.Route, md.AdditionalAttributes) o := metric.WithAttributeSet(attribute.NewSet(attributes...)) recordOpts := metricRecordOptionPool.Get().(*[]metric.RecordOption) *recordOpts = append(*recordOpts, o) @@ -342,7 +343,7 @@ func (n HTTPServer) Route(route string) attribute.KeyValue { return semconv.HTTPRoute(route) } -func (n HTTPServer) MetricAttributes(server string, req *http.Request, statusCode int, additionalAttributes []attribute.KeyValue) []attribute.KeyValue { +func (n HTTPServer) MetricAttributes(server string, req *http.Request, statusCode int, route string, additionalAttributes []attribute.KeyValue) []attribute.KeyValue { num := len(additionalAttributes) + 3 var host string var p int @@ -371,6 +372,10 @@ func (n HTTPServer) MetricAttributes(server string, req *http.Request, statusCod num++ } + if route != "" { + num++ + } + attributes := slices.Grow(additionalAttributes, num) attributes = append(attributes, semconv.HTTPRequestMethodKey.String(standardizeHTTPMethod(req.Method)), @@ -390,5 +395,9 @@ func (n HTTPServer) MetricAttributes(server string, req *http.Request, statusCod if statusCode > 0 { attributes = append(attributes, semconv.HTTPResponseStatusCode(statusCode)) } + + if route != "" { + attributes = append(attributes, semconv.HTTPRoute(route)) + } return attributes } diff --git a/instrumentation/github.com/labstack/echo/otelecho/internal/semconv/server_test.go b/instrumentation/github.com/labstack/echo/otelecho/internal/semconv/server_test.go index 5e6cd3ffa99..4c905e2fa17 100644 --- a/instrumentation/github.com/labstack/echo/otelecho/internal/semconv/server_test.go +++ b/instrumentation/github.com/labstack/echo/otelecho/internal/semconv/server_test.go @@ -26,6 +26,7 @@ func TestHTTPServer_MetricAttributes(t *testing.T) { server string req *http.Request statusCode int + route string additionalAttributes []attribute.KeyValue wantFunc func(t *testing.T, attrs []attribute.KeyValue) }{ @@ -34,6 +35,7 @@ func TestHTTPServer_MetricAttributes(t *testing.T) { server: "", req: defaultRequest, statusCode: 200, + route: "", additionalAttributes: []attribute.KeyValue{attribute.String("test", "test")}, wantFunc: func(t *testing.T, attrs []attribute.KeyValue) { require.Len(t, attrs, 7) @@ -53,9 +55,10 @@ func TestHTTPServer_MetricAttributes(t *testing.T) { server: "example.com:9999", req: defaultRequest, statusCode: 200, + route: "/path/${id}", additionalAttributes: nil, wantFunc: func(t *testing.T, attrs []attribute.KeyValue) { - require.Len(t, attrs, 7) + require.Len(t, attrs, 8) assert.ElementsMatch(t, []attribute.KeyValue{ attribute.String("http.request.method", "GET"), attribute.String("url.scheme", "http"), @@ -64,6 +67,7 @@ func TestHTTPServer_MetricAttributes(t *testing.T) { attribute.String("network.protocol.name", "http"), attribute.String("network.protocol.version", "1.1"), attribute.Int64("http.response.status_code", 200), + attribute.String("http.route", "/path/${id}"), }, attrs) }, }, @@ -71,7 +75,7 @@ func TestHTTPServer_MetricAttributes(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := HTTPServer{}.MetricAttributes(tt.server, tt.req, tt.statusCode, tt.additionalAttributes) + got := HTTPServer{}.MetricAttributes(tt.server, tt.req, tt.statusCode, tt.route, tt.additionalAttributes) tt.wantFunc(t, got) }) } diff --git a/instrumentation/net/http/httptrace/otelhttptrace/internal/semconv/server.go b/instrumentation/net/http/httptrace/otelhttptrace/internal/semconv/server.go index cd72dc47fc2..5e770b74ad6 100644 --- a/instrumentation/net/http/httptrace/otelhttptrace/internal/semconv/server.go +++ b/instrumentation/net/http/httptrace/otelhttptrace/internal/semconv/server.go @@ -240,6 +240,7 @@ type ServerMetricData struct { type MetricAttributes struct { Req *http.Request StatusCode int + Route string AdditionalAttributes []attribute.KeyValue } @@ -265,7 +266,7 @@ var ( ) func (n HTTPServer) RecordMetrics(ctx context.Context, md ServerMetricData) { - attributes := n.MetricAttributes(md.ServerName, md.Req, md.StatusCode, md.AdditionalAttributes) + attributes := n.MetricAttributes(md.ServerName, md.Req, md.StatusCode, md.Route, md.AdditionalAttributes) o := metric.WithAttributeSet(attribute.NewSet(attributes...)) recordOpts := metricRecordOptionPool.Get().(*[]metric.RecordOption) *recordOpts = append(*recordOpts, o) @@ -342,7 +343,7 @@ func (n HTTPServer) Route(route string) attribute.KeyValue { return semconv.HTTPRoute(route) } -func (n HTTPServer) MetricAttributes(server string, req *http.Request, statusCode int, additionalAttributes []attribute.KeyValue) []attribute.KeyValue { +func (n HTTPServer) MetricAttributes(server string, req *http.Request, statusCode int, route string, additionalAttributes []attribute.KeyValue) []attribute.KeyValue { num := len(additionalAttributes) + 3 var host string var p int @@ -371,6 +372,10 @@ func (n HTTPServer) MetricAttributes(server string, req *http.Request, statusCod num++ } + if route != "" { + num++ + } + attributes := slices.Grow(additionalAttributes, num) attributes = append(attributes, semconv.HTTPRequestMethodKey.String(standardizeHTTPMethod(req.Method)), @@ -390,5 +395,9 @@ func (n HTTPServer) MetricAttributes(server string, req *http.Request, statusCod if statusCode > 0 { attributes = append(attributes, semconv.HTTPResponseStatusCode(statusCode)) } + + if route != "" { + attributes = append(attributes, semconv.HTTPRoute(route)) + } return attributes } diff --git a/instrumentation/net/http/httptrace/otelhttptrace/internal/semconv/server_test.go b/instrumentation/net/http/httptrace/otelhttptrace/internal/semconv/server_test.go index 5e6cd3ffa99..4c905e2fa17 100644 --- a/instrumentation/net/http/httptrace/otelhttptrace/internal/semconv/server_test.go +++ b/instrumentation/net/http/httptrace/otelhttptrace/internal/semconv/server_test.go @@ -26,6 +26,7 @@ func TestHTTPServer_MetricAttributes(t *testing.T) { server string req *http.Request statusCode int + route string additionalAttributes []attribute.KeyValue wantFunc func(t *testing.T, attrs []attribute.KeyValue) }{ @@ -34,6 +35,7 @@ func TestHTTPServer_MetricAttributes(t *testing.T) { server: "", req: defaultRequest, statusCode: 200, + route: "", additionalAttributes: []attribute.KeyValue{attribute.String("test", "test")}, wantFunc: func(t *testing.T, attrs []attribute.KeyValue) { require.Len(t, attrs, 7) @@ -53,9 +55,10 @@ func TestHTTPServer_MetricAttributes(t *testing.T) { server: "example.com:9999", req: defaultRequest, statusCode: 200, + route: "/path/${id}", additionalAttributes: nil, wantFunc: func(t *testing.T, attrs []attribute.KeyValue) { - require.Len(t, attrs, 7) + require.Len(t, attrs, 8) assert.ElementsMatch(t, []attribute.KeyValue{ attribute.String("http.request.method", "GET"), attribute.String("url.scheme", "http"), @@ -64,6 +67,7 @@ func TestHTTPServer_MetricAttributes(t *testing.T) { attribute.String("network.protocol.name", "http"), attribute.String("network.protocol.version", "1.1"), attribute.Int64("http.response.status_code", 200), + attribute.String("http.route", "/path/${id}"), }, attrs) }, }, @@ -71,7 +75,7 @@ func TestHTTPServer_MetricAttributes(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := HTTPServer{}.MetricAttributes(tt.server, tt.req, tt.statusCode, tt.additionalAttributes) + got := HTTPServer{}.MetricAttributes(tt.server, tt.req, tt.statusCode, tt.route, tt.additionalAttributes) tt.wantFunc(t, got) }) } diff --git a/instrumentation/net/http/otelhttp/internal/semconv/server.go b/instrumentation/net/http/otelhttp/internal/semconv/server.go index c497eadea01..5ae6a07386b 100644 --- a/instrumentation/net/http/otelhttp/internal/semconv/server.go +++ b/instrumentation/net/http/otelhttp/internal/semconv/server.go @@ -240,6 +240,7 @@ type ServerMetricData struct { type MetricAttributes struct { Req *http.Request StatusCode int + Route string AdditionalAttributes []attribute.KeyValue } @@ -265,7 +266,7 @@ var ( ) func (n HTTPServer) RecordMetrics(ctx context.Context, md ServerMetricData) { - attributes := n.MetricAttributes(md.ServerName, md.Req, md.StatusCode, md.AdditionalAttributes) + attributes := n.MetricAttributes(md.ServerName, md.Req, md.StatusCode, md.Route, md.AdditionalAttributes) o := metric.WithAttributeSet(attribute.NewSet(attributes...)) recordOpts := metricRecordOptionPool.Get().(*[]metric.RecordOption) *recordOpts = append(*recordOpts, o) @@ -342,7 +343,7 @@ func (n HTTPServer) Route(route string) attribute.KeyValue { return semconv.HTTPRoute(route) } -func (n HTTPServer) MetricAttributes(server string, req *http.Request, statusCode int, additionalAttributes []attribute.KeyValue) []attribute.KeyValue { +func (n HTTPServer) MetricAttributes(server string, req *http.Request, statusCode int, route string, additionalAttributes []attribute.KeyValue) []attribute.KeyValue { num := len(additionalAttributes) + 3 var host string var p int @@ -371,6 +372,10 @@ func (n HTTPServer) MetricAttributes(server string, req *http.Request, statusCod num++ } + if route != "" { + num++ + } + attributes := slices.Grow(additionalAttributes, num) attributes = append(attributes, semconv.HTTPRequestMethodKey.String(standardizeHTTPMethod(req.Method)), @@ -390,5 +395,9 @@ func (n HTTPServer) MetricAttributes(server string, req *http.Request, statusCod if statusCode > 0 { attributes = append(attributes, semconv.HTTPResponseStatusCode(statusCode)) } + + if route != "" { + attributes = append(attributes, semconv.HTTPRoute(route)) + } return attributes } diff --git a/instrumentation/net/http/otelhttp/internal/semconv/server_test.go b/instrumentation/net/http/otelhttp/internal/semconv/server_test.go index 5e6cd3ffa99..4c905e2fa17 100644 --- a/instrumentation/net/http/otelhttp/internal/semconv/server_test.go +++ b/instrumentation/net/http/otelhttp/internal/semconv/server_test.go @@ -26,6 +26,7 @@ func TestHTTPServer_MetricAttributes(t *testing.T) { server string req *http.Request statusCode int + route string additionalAttributes []attribute.KeyValue wantFunc func(t *testing.T, attrs []attribute.KeyValue) }{ @@ -34,6 +35,7 @@ func TestHTTPServer_MetricAttributes(t *testing.T) { server: "", req: defaultRequest, statusCode: 200, + route: "", additionalAttributes: []attribute.KeyValue{attribute.String("test", "test")}, wantFunc: func(t *testing.T, attrs []attribute.KeyValue) { require.Len(t, attrs, 7) @@ -53,9 +55,10 @@ func TestHTTPServer_MetricAttributes(t *testing.T) { server: "example.com:9999", req: defaultRequest, statusCode: 200, + route: "/path/${id}", additionalAttributes: nil, wantFunc: func(t *testing.T, attrs []attribute.KeyValue) { - require.Len(t, attrs, 7) + require.Len(t, attrs, 8) assert.ElementsMatch(t, []attribute.KeyValue{ attribute.String("http.request.method", "GET"), attribute.String("url.scheme", "http"), @@ -64,6 +67,7 @@ func TestHTTPServer_MetricAttributes(t *testing.T) { attribute.String("network.protocol.name", "http"), attribute.String("network.protocol.version", "1.1"), attribute.Int64("http.response.status_code", 200), + attribute.String("http.route", "/path/${id}"), }, attrs) }, }, @@ -71,7 +75,7 @@ func TestHTTPServer_MetricAttributes(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := HTTPServer{}.MetricAttributes(tt.server, tt.req, tt.statusCode, tt.additionalAttributes) + got := HTTPServer{}.MetricAttributes(tt.server, tt.req, tt.statusCode, tt.route, tt.additionalAttributes) tt.wantFunc(t, got) }) } diff --git a/internal/shared/semconv/server.go.tmpl b/internal/shared/semconv/server.go.tmpl index 81b21616f7f..a8ce6987dcc 100644 --- a/internal/shared/semconv/server.go.tmpl +++ b/internal/shared/semconv/server.go.tmpl @@ -240,6 +240,7 @@ type ServerMetricData struct { type MetricAttributes struct { Req *http.Request StatusCode int + Route string AdditionalAttributes []attribute.KeyValue } @@ -265,7 +266,7 @@ var ( ) func (n HTTPServer) RecordMetrics(ctx context.Context, md ServerMetricData) { - attributes := n.MetricAttributes(md.ServerName, md.Req, md.StatusCode, md.AdditionalAttributes) + attributes := n.MetricAttributes(md.ServerName, md.Req, md.StatusCode, md.Route, md.AdditionalAttributes) o := metric.WithAttributeSet(attribute.NewSet(attributes...)) recordOpts := metricRecordOptionPool.Get().(*[]metric.RecordOption) *recordOpts = append(*recordOpts, o) @@ -342,7 +343,7 @@ func (n HTTPServer) Route(route string) attribute.KeyValue { return semconv.HTTPRoute(route) } -func (n HTTPServer) MetricAttributes(server string, req *http.Request, statusCode int, additionalAttributes []attribute.KeyValue) []attribute.KeyValue { +func (n HTTPServer) MetricAttributes(server string, req *http.Request, statusCode int, route string, additionalAttributes []attribute.KeyValue) []attribute.KeyValue { num := len(additionalAttributes) + 3 var host string var p int @@ -371,6 +372,10 @@ func (n HTTPServer) MetricAttributes(server string, req *http.Request, statusCod num++ } + if route != "" { + num++ + } + attributes := slices.Grow(additionalAttributes, num) attributes = append(attributes, semconv.HTTPRequestMethodKey.String(standardizeHTTPMethod(req.Method)), @@ -390,5 +395,9 @@ func (n HTTPServer) MetricAttributes(server string, req *http.Request, statusCod if statusCode > 0 { attributes = append(attributes, semconv.HTTPResponseStatusCode(statusCode)) } + + if route != "" { + attributes = append(attributes, semconv.HTTPRoute(route)) + } return attributes } diff --git a/internal/shared/semconv/server_test.go.tmpl b/internal/shared/semconv/server_test.go.tmpl index 5e6cd3ffa99..4c905e2fa17 100644 --- a/internal/shared/semconv/server_test.go.tmpl +++ b/internal/shared/semconv/server_test.go.tmpl @@ -26,6 +26,7 @@ func TestHTTPServer_MetricAttributes(t *testing.T) { server string req *http.Request statusCode int + route string additionalAttributes []attribute.KeyValue wantFunc func(t *testing.T, attrs []attribute.KeyValue) }{ @@ -34,6 +35,7 @@ func TestHTTPServer_MetricAttributes(t *testing.T) { server: "", req: defaultRequest, statusCode: 200, + route: "", additionalAttributes: []attribute.KeyValue{attribute.String("test", "test")}, wantFunc: func(t *testing.T, attrs []attribute.KeyValue) { require.Len(t, attrs, 7) @@ -53,9 +55,10 @@ func TestHTTPServer_MetricAttributes(t *testing.T) { server: "example.com:9999", req: defaultRequest, statusCode: 200, + route: "/path/${id}", additionalAttributes: nil, wantFunc: func(t *testing.T, attrs []attribute.KeyValue) { - require.Len(t, attrs, 7) + require.Len(t, attrs, 8) assert.ElementsMatch(t, []attribute.KeyValue{ attribute.String("http.request.method", "GET"), attribute.String("url.scheme", "http"), @@ -64,6 +67,7 @@ func TestHTTPServer_MetricAttributes(t *testing.T) { attribute.String("network.protocol.name", "http"), attribute.String("network.protocol.version", "1.1"), attribute.Int64("http.response.status_code", 200), + attribute.String("http.route", "/path/${id}"), }, attrs) }, }, @@ -71,7 +75,7 @@ func TestHTTPServer_MetricAttributes(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := HTTPServer{}.MetricAttributes(tt.server, tt.req, tt.statusCode, tt.additionalAttributes) + got := HTTPServer{}.MetricAttributes(tt.server, tt.req, tt.statusCode, tt.route, tt.additionalAttributes) tt.wantFunc(t, got) }) } From c301545f4533675a49c52d8baf50d3090a3b91d9 Mon Sep 17 00:00:00 2001 From: Vinay Vennela <52368723+vinayvennela@users.noreply.github.com> Date: Thu, 2 Oct 2025 19:08:53 +0530 Subject: [PATCH 6/6] fix lint issues --- .../github.com/gorilla/mux/otelmux/muxtest_test.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/instrumentation/github.com/gorilla/mux/otelmux/muxtest_test.go b/instrumentation/github.com/gorilla/mux/otelmux/muxtest_test.go index db7ab5f7e02..d755d8ce74b 100644 --- a/instrumentation/github.com/gorilla/mux/otelmux/muxtest_test.go +++ b/instrumentation/github.com/gorilla/mux/otelmux/muxtest_test.go @@ -401,15 +401,11 @@ func TestDefaultMetricAttributes(t *testing.T) { // Verify that the additional attribute is present in the metrics. for _, m := range rm.ScopeMetrics[0].Metrics { - switch m.Data.(type) { + switch d := m.Data.(type) { case metricdata.Histogram[int64]: - d, ok := m.Data.(metricdata.Histogram[int64]) - assert.True(t, ok) assert.Len(t, d.DataPoints, 1) containsAttributes(t, d.DataPoints[0].Attributes, defaultMetricAttributes) case metricdata.Histogram[float64]: - d, ok := m.Data.(metricdata.Histogram[float64]) - assert.True(t, ok) assert.Len(t, d.DataPoints, 1) containsAttributes(t, d.DataPoints[0].Attributes, defaultMetricAttributes) default: @@ -417,7 +413,6 @@ func TestDefaultMetricAttributes(t *testing.T) { t.Errorf("Unexpected metric type") } } - } func TestHandlerWithMetricAttributesFn(t *testing.T) {