diff --git a/CHANGELOG.md b/CHANGELOG.md index 272d92d1800..8c55fedbfdf 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) +- Add the `http.route` metric attribute to `go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux`. (#7966) 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 e80dae1be46..7364390aaf0 100644 --- a/instrumentation/github.com/gorilla/mux/otelmux/internal/semconv/server.go +++ b/instrumentation/github.com/gorilla/mux/otelmux/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/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 553c482fea0..b7fd4a2201d 100644 --- a/instrumentation/github.com/gorilla/mux/otelmux/mux.go +++ b/instrumentation/github.com/gorilla/mux/otelmux/mux.go @@ -190,6 +190,7 @@ func (tw traceware) ServeHTTP(w http.ResponseWriter, r *http.Request) { metricAttributes := semconv.MetricAttributes{ Req: r, StatusCode: statusCode, + Route: routeStr, AdditionalAttributes: tw.metricAttributesFromRequest(r), } diff --git a/instrumentation/github.com/gorilla/mux/otelmux/muxtest_test.go b/instrumentation/github.com/gorilla/mux/otelmux/muxtest_test.go index b75c83e89d2..d755d8ce74b 100644 --- a/instrumentation/github.com/gorilla/mux/otelmux/muxtest_test.go +++ b/instrumentation/github.com/gorilla/mux/otelmux/muxtest_test.go @@ -373,11 +373,53 @@ 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 d := m.Data.(type) { + case metricdata.Histogram[int64]: + assert.Len(t, d.DataPoints, 1) + containsAttributes(t, d.DataPoints[0].Attributes, defaultMetricAttributes) + case metricdata.Histogram[float64]: + 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 +472,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 +481,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") } } } 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) }) }