diff --git a/CHANGELOG.md b/CHANGELOG.md index 6439b6333eb..bd817ab8e9f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ## [Unreleased] +### Added + +- Generate server metrics with semantic conventions v1.26 in `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp` when `OTEL_SEMCONV_STABILITY_OPT_IN` is set to `http/dup`. (#6411) + ## [1.33.0/0.58.0/0.27.0/0.13.0/0.8.0/0.6.0/0.5.0] - 2024-12-12 ### Added diff --git a/instrumentation/net/http/otelhttp/internal/semconv/env.go b/instrumentation/net/http/otelhttp/internal/semconv/env.go index 3b036f8a37b..cab3cf812ab 100644 --- a/instrumentation/net/http/otelhttp/internal/semconv/env.go +++ b/instrumentation/net/http/otelhttp/internal/semconv/env.go @@ -16,6 +16,10 @@ import ( "go.opentelemetry.io/otel/metric" ) +// OTelSemConvStabilityOptIn is an environment variable. +// That can be set to "old" or "http/dup" to opt into the new HTTP semantic conventions. +const OTelSemConvStabilityOptIn = "OTEL_SEMCONV_STABILITY_OPT_IN" + type ResponseTelemetry struct { StatusCode int ReadBytes int64 @@ -31,6 +35,11 @@ type HTTPServer struct { requestBytesCounter metric.Int64Counter responseBytesCounter metric.Int64Counter serverLatencyMeasure metric.Float64Histogram + + // New metrics + requestBodySizeHistogram metric.Int64Histogram + responseBodySizeHistogram metric.Int64Histogram + requestDurationHistogram metric.Float64Histogram } // RequestTraceAttrs returns trace attributes for an HTTP request received by a @@ -103,38 +112,56 @@ type MetricData struct { ElapsedTime float64 } -var metricAddOptionPool = &sync.Pool{ - New: func() interface{} { - return &[]metric.AddOption{} - }, -} +var ( + metricAddOptionPool = &sync.Pool{ + New: func() interface{} { + return &[]metric.AddOption{} + }, + } -func (s HTTPServer) RecordMetrics(ctx context.Context, md ServerMetricData) { - if s.requestBytesCounter == nil || s.responseBytesCounter == nil || s.serverLatencyMeasure == nil { - // This will happen if an HTTPServer{} is used instead of NewHTTPServer. - return + metricRecordOptionPool = &sync.Pool{ + New: func() interface{} { + return &[]metric.RecordOption{} + }, } +) - attributes := OldHTTPServer{}.MetricAttributes(md.ServerName, md.Req, md.StatusCode, md.AdditionalAttributes) - o := metric.WithAttributeSet(attribute.NewSet(attributes...)) - addOpts := metricAddOptionPool.Get().(*[]metric.AddOption) - *addOpts = append(*addOpts, o) - s.requestBytesCounter.Add(ctx, md.RequestSize, *addOpts...) - s.responseBytesCounter.Add(ctx, md.ResponseSize, *addOpts...) - s.serverLatencyMeasure.Record(ctx, md.ElapsedTime, o) - *addOpts = (*addOpts)[:0] - metricAddOptionPool.Put(addOpts) +func (s HTTPServer) RecordMetrics(ctx context.Context, md ServerMetricData) { + if s.requestBytesCounter != nil && s.responseBytesCounter != nil && s.serverLatencyMeasure != nil { + attributes := OldHTTPServer{}.MetricAttributes(md.ServerName, md.Req, md.StatusCode, md.AdditionalAttributes) + o := metric.WithAttributeSet(attribute.NewSet(attributes...)) + addOpts := metricAddOptionPool.Get().(*[]metric.AddOption) + *addOpts = append(*addOpts, o) + s.requestBytesCounter.Add(ctx, md.RequestSize, *addOpts...) + s.responseBytesCounter.Add(ctx, md.ResponseSize, *addOpts...) + s.serverLatencyMeasure.Record(ctx, md.ElapsedTime, o) + *addOpts = (*addOpts)[:0] + metricAddOptionPool.Put(addOpts) + } - // TODO: Duplicate Metrics + if s.duplicate && s.requestDurationHistogram != nil && s.requestBodySizeHistogram != nil && s.responseBodySizeHistogram != nil { + attributes := CurrentHTTPServer{}.MetricAttributes(md.ServerName, md.Req, md.StatusCode, md.AdditionalAttributes) + o := metric.WithAttributeSet(attribute.NewSet(attributes...)) + recordOpts := metricRecordOptionPool.Get().(*[]metric.RecordOption) + *recordOpts = append(*recordOpts, o) + s.requestBodySizeHistogram.Record(ctx, md.RequestSize, *recordOpts...) + s.responseBodySizeHistogram.Record(ctx, md.ResponseSize, *recordOpts...) + s.requestDurationHistogram.Record(ctx, md.ElapsedTime, o) + *recordOpts = (*recordOpts)[:0] + metricRecordOptionPool.Put(recordOpts) + } } func NewHTTPServer(meter metric.Meter) HTTPServer { - env := strings.ToLower(os.Getenv("OTEL_SEMCONV_STABILITY_OPT_IN")) + env := strings.ToLower(os.Getenv(OTelSemConvStabilityOptIn)) duplicate := env == "http/dup" server := HTTPServer{ duplicate: duplicate, } server.requestBytesCounter, server.responseBytesCounter, server.serverLatencyMeasure = OldHTTPServer{}.createMeasures(meter) + if duplicate { + server.requestBodySizeHistogram, server.responseBodySizeHistogram, server.requestDurationHistogram = CurrentHTTPServer{}.createMeasures(meter) + } return server } @@ -148,7 +175,7 @@ type HTTPClient struct { } func NewHTTPClient(meter metric.Meter) HTTPClient { - env := strings.ToLower(os.Getenv("OTEL_SEMCONV_STABILITY_OPT_IN")) + env := strings.ToLower(os.Getenv(OTelSemConvStabilityOptIn)) client := HTTPClient{ duplicate: env == "http/dup", } diff --git a/instrumentation/net/http/otelhttp/internal/semconv/httpconv.go b/instrumentation/net/http/otelhttp/internal/semconv/httpconv.go index dc9ec7bc39e..3c6c6a9b93b 100644 --- a/instrumentation/net/http/otelhttp/internal/semconv/httpconv.go +++ b/instrumentation/net/http/otelhttp/internal/semconv/httpconv.go @@ -7,10 +7,13 @@ import ( "fmt" "net/http" "reflect" + "slices" "strconv" "strings" "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/metric" + "go.opentelemetry.io/otel/metric/noop" semconvNew "go.opentelemetry.io/otel/semconv/v1.26.0" ) @@ -199,6 +202,86 @@ func (n CurrentHTTPServer) Route(route string) attribute.KeyValue { return semconvNew.HTTPRoute(route) } +func (n CurrentHTTPServer) createMeasures(meter metric.Meter) (metric.Int64Histogram, metric.Int64Histogram, metric.Float64Histogram) { + if meter == nil { + return noop.Int64Histogram{}, noop.Int64Histogram{}, noop.Float64Histogram{} + } + + var err error + requestBodySizeHistogram, err := meter.Int64Histogram( + semconvNew.HTTPServerRequestBodySizeName, + metric.WithUnit(semconvNew.HTTPServerRequestBodySizeUnit), + metric.WithDescription(semconvNew.HTTPServerRequestBodySizeDescription), + ) + handleErr(err) + + responseBodySizeHistogram, err := meter.Int64Histogram( + semconvNew.HTTPServerResponseBodySizeName, + metric.WithUnit(semconvNew.HTTPServerResponseBodySizeUnit), + metric.WithDescription(semconvNew.HTTPServerResponseBodySizeDescription), + ) + handleErr(err) + requestDurationHistogram, err := meter.Float64Histogram( + semconvNew.HTTPServerRequestDurationName, + metric.WithUnit(semconvNew.HTTPServerRequestDurationUnit), + metric.WithDescription(semconvNew.HTTPServerRequestDurationDescription), + ) + handleErr(err) + + return requestBodySizeHistogram, responseBodySizeHistogram, requestDurationHistogram +} + +func (n CurrentHTTPServer) MetricAttributes(server string, req *http.Request, statusCode int, additionalAttributes []attribute.KeyValue) []attribute.KeyValue { + num := len(additionalAttributes) + 3 + var host string + var p int + if server == "" { + host, p = SplitHostPort(req.Host) + } else { + // Prioritize the primary server name. + host, p = SplitHostPort(server) + if p < 0 { + _, p = SplitHostPort(req.Host) + } + } + hostPort := requiredHTTPPort(req.TLS != nil, p) + if hostPort > 0 { + num++ + } + protoName, protoVersion := netProtocol(req.Proto) + if protoName != "" { + num++ + } + if protoVersion != "" { + num++ + } + + if statusCode > 0 { + num++ + } + + attributes := slices.Grow(additionalAttributes, num) + attributes = append(attributes, + semconvNew.HTTPRequestMethodKey.String(standardizeHTTPMethod(req.Method)), + n.scheme(req.TLS != nil), + semconvNew.ServerAddress(host)) + + if hostPort > 0 { + attributes = append(attributes, semconvNew.ServerPort(hostPort)) + } + if protoName != "" { + attributes = append(attributes, semconvNew.NetworkProtocolName(protoName)) + } + if protoVersion != "" { + attributes = append(attributes, semconvNew.NetworkProtocolVersion(protoVersion)) + } + + if statusCode > 0 { + attributes = append(attributes, semconvNew.HTTPResponseStatusCode(statusCode)) + } + return attributes +} + type CurrentHTTPClient struct{} // RequestTraceAttrs returns trace attributes for an HTTP request made by a client. diff --git a/instrumentation/net/http/otelhttp/internal/semconv/httpconv_test.go b/instrumentation/net/http/otelhttp/internal/semconv/httpconv_test.go index 47b4db548ce..899661c4ad8 100644 --- a/instrumentation/net/http/otelhttp/internal/semconv/httpconv_test.go +++ b/instrumentation/net/http/otelhttp/internal/semconv/httpconv_test.go @@ -9,11 +9,72 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" ) +func TestCurrentHttpServer_MetricAttributes(t *testing.T) { + defaultRequest, err := http.NewRequest("GET", "http://example.com/path?query=test", nil) + require.NoError(t, err) + + tests := []struct { + name string + server string + req *http.Request + statusCode int + additionalAttributes []attribute.KeyValue + wantFunc func(t *testing.T, attrs []attribute.KeyValue) + }{ + { + name: "routine testing", + server: "", + req: defaultRequest, + statusCode: 200, + additionalAttributes: []attribute.KeyValue{attribute.String("test", "test")}, + wantFunc: func(t *testing.T, attrs []attribute.KeyValue) { + require.Len(t, attrs, 7) + assert.ElementsMatch(t, []attribute.KeyValue{ + attribute.String("http.request.method", "GET"), + attribute.String("url.scheme", "http"), + attribute.String("server.address", "example.com"), + attribute.String("network.protocol.name", "http"), + attribute.String("network.protocol.version", "1.1"), + attribute.Int64("http.response.status_code", 200), + attribute.String("test", "test"), + }, attrs) + }, + }, + { + name: "use server address", + server: "example.com:9999", + req: defaultRequest, + statusCode: 200, + additionalAttributes: nil, + wantFunc: func(t *testing.T, attrs []attribute.KeyValue) { + require.Len(t, attrs, 7) + assert.ElementsMatch(t, []attribute.KeyValue{ + attribute.String("http.request.method", "GET"), + attribute.String("url.scheme", "http"), + attribute.String("server.address", "example.com"), + attribute.Int("server.port", 9999), + attribute.String("network.protocol.name", "http"), + attribute.String("network.protocol.version", "1.1"), + attribute.Int64("http.response.status_code", 200), + }, attrs) + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := CurrentHTTPServer{}.MetricAttributes(tt.server, tt.req, tt.statusCode, tt.additionalAttributes) + tt.wantFunc(t, got) + }) + } +} + func TestNewMethod(t *testing.T) { testCases := []struct { method string diff --git a/instrumentation/net/http/otelhttp/internal/semconv/test/go.mod b/instrumentation/net/http/otelhttp/internal/semconv/test/go.mod index 9a0732efd15..fb6527513b6 100644 --- a/instrumentation/net/http/otelhttp/internal/semconv/test/go.mod +++ b/instrumentation/net/http/otelhttp/internal/semconv/test/go.mod @@ -6,6 +6,7 @@ require ( github.com/stretchr/testify v1.10.0 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 go.opentelemetry.io/otel v1.33.0 + go.opentelemetry.io/otel/metric v1.33.0 go.opentelemetry.io/otel/sdk v1.33.0 go.opentelemetry.io/otel/sdk/metric v1.33.0 ) @@ -17,7 +18,6 @@ require ( github.com/google/uuid v1.6.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/otel/metric v1.33.0 // indirect go.opentelemetry.io/otel/trace v1.33.0 // indirect golang.org/x/sys v0.28.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/instrumentation/net/http/otelhttp/internal/semconv/test/httpconv_test.go b/instrumentation/net/http/otelhttp/internal/semconv/test/httpconv_test.go index da37f9765b3..7b56aa1d150 100644 --- a/instrumentation/net/http/otelhttp/internal/semconv/test/httpconv_test.go +++ b/instrumentation/net/http/otelhttp/internal/semconv/test/httpconv_test.go @@ -4,6 +4,7 @@ package test import ( + "context" "errors" "fmt" "net/http" @@ -12,9 +13,15 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconv" "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/metric" + "go.opentelemetry.io/otel/sdk/instrumentation" + sdkmetric "go.opentelemetry.io/otel/sdk/metric" + "go.opentelemetry.io/otel/sdk/metric/metricdata" + "go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest" ) func TestNewTraceRequest(t *testing.T) { @@ -47,6 +54,211 @@ func TestNewTraceRequest(t *testing.T) { testTraceRequest(t, serv, want) } +func TestNewRecordMetrics(t *testing.T) { + oldAttrs := attribute.NewSet( + attribute.String("http.scheme", "http"), + attribute.String("http.method", "POST"), + attribute.Int64("http.status_code", 301), + attribute.String("key", "value"), + attribute.String("net.host.name", "stuff"), + attribute.String("net.protocol.name", "http"), + attribute.String("net.protocol.version", "1.1"), + ) + + currAttrs := attribute.NewSet( + attribute.String("http.request.method", "POST"), + attribute.Int64("http.response.status_code", 301), + attribute.String("key", "value"), + attribute.String("network.protocol.name", "http"), + attribute.String("network.protocol.version", "1.1"), + attribute.String("server.address", "stuff"), + attribute.String("url.scheme", "http"), + ) + + // The OldHTTPServer version + expectedOldScopeMetric := metricdata.ScopeMetrics{ + Scope: instrumentation.Scope{ + Name: "test", + }, + Metrics: []metricdata.Metrics{ + { + Name: "http.server.request.size", + Description: "Measures the size of HTTP request messages.", + Unit: "By", + Data: metricdata.Sum[int64]{ + Temporality: metricdata.CumulativeTemporality, + IsMonotonic: true, + DataPoints: []metricdata.DataPoint[int64]{ + { + Attributes: oldAttrs, + }, + }, + }, + }, + { + Name: "http.server.response.size", + Description: "Measures the size of HTTP response messages.", + Unit: "By", + Data: metricdata.Sum[int64]{ + Temporality: metricdata.CumulativeTemporality, + IsMonotonic: true, + DataPoints: []metricdata.DataPoint[int64]{ + { + Attributes: oldAttrs, + }, + }, + }, + }, + { + Name: "http.server.duration", + Description: "Measures the duration of inbound HTTP requests.", + Unit: "ms", + Data: metricdata.Histogram[float64]{ + Temporality: metricdata.CumulativeTemporality, + DataPoints: []metricdata.HistogramDataPoint[float64]{ + { + Attributes: oldAttrs, + }, + }, + }, + }, + }, + } + + // the CurrentHTTPServer version + expectedCurrentScopeMetric := expectedOldScopeMetric + expectedCurrentScopeMetric.Metrics = append(expectedCurrentScopeMetric.Metrics, []metricdata.Metrics{ + { + Name: "http.server.request.body.size", + Description: "Size of HTTP server request bodies.", + Unit: "By", + Data: metricdata.Histogram[int64]{ + Temporality: metricdata.CumulativeTemporality, + DataPoints: []metricdata.HistogramDataPoint[int64]{ + { + Attributes: currAttrs, + }, + }, + }, + }, + { + Name: "http.server.response.body.size", + Description: "Size of HTTP server response bodies.", + Unit: "By", + Data: metricdata.Histogram[int64]{ + Temporality: metricdata.CumulativeTemporality, + DataPoints: []metricdata.HistogramDataPoint[int64]{ + { + Attributes: currAttrs, + }, + }, + }, + }, + { + Name: "http.server.request.duration", + Description: "Duration of HTTP server requests.", + Unit: "s", + Data: metricdata.Histogram[float64]{ + Temporality: metricdata.CumulativeTemporality, + DataPoints: []metricdata.HistogramDataPoint[float64]{ + { + Attributes: currAttrs, + }, + }, + }, + }, + }...) + + tests := []struct { + name string + setEnv bool + serverFunc func(metric.MeterProvider) semconv.HTTPServer + wantFunc func(t *testing.T, rm metricdata.ResourceMetrics) + }{ + { + name: "No environment variable set, and no Meter", + setEnv: false, + serverFunc: func(metric.MeterProvider) semconv.HTTPServer { + return semconv.NewHTTPServer(nil) + }, + wantFunc: func(t *testing.T, rm metricdata.ResourceMetrics) { + assert.Empty(t, rm.ScopeMetrics) + }, + }, + { + name: "No environment variable set, but with Meter", + setEnv: false, + serverFunc: func(mp metric.MeterProvider) semconv.HTTPServer { + return semconv.NewHTTPServer(mp.Meter("test")) + }, + wantFunc: func(t *testing.T, rm metricdata.ResourceMetrics) { + require.Len(t, rm.ScopeMetrics, 1) + + // because of OldHTTPServer + require.Len(t, rm.ScopeMetrics[0].Metrics, 3) + metricdatatest.AssertEqual(t, expectedOldScopeMetric, rm.ScopeMetrics[0], metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreValue()) + }, + }, + { + name: "Set environment variable, but no Meter", + setEnv: true, + serverFunc: func(metric.MeterProvider) semconv.HTTPServer { + return semconv.NewHTTPServer(nil) + }, + wantFunc: func(t *testing.T, rm metricdata.ResourceMetrics) { + assert.Empty(t, rm.ScopeMetrics) + }, + }, + { + name: "Set environment variable and Meter", + setEnv: true, + serverFunc: func(mp metric.MeterProvider) semconv.HTTPServer { + return semconv.NewHTTPServer(mp.Meter("test")) + }, + wantFunc: func(t *testing.T, rm metricdata.ResourceMetrics) { + require.Len(t, rm.ScopeMetrics, 1) + require.Len(t, rm.ScopeMetrics[0].Metrics, 6) + metricdatatest.AssertEqual(t, expectedCurrentScopeMetric, rm.ScopeMetrics[0], metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreValue()) + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.setEnv { + t.Setenv(semconv.OTelSemConvStabilityOptIn, "http/dup") + } + + reader := sdkmetric.NewManualReader() + mp := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader)) + + server := tt.serverFunc(mp) + req, err := http.NewRequest("POST", "http://example.com", nil) + assert.NoError(t, err) + + server.RecordMetrics(context.Background(), semconv.ServerMetricData{ + ServerName: "stuff", + ResponseSize: 200, + MetricAttributes: semconv.MetricAttributes{ + Req: req, + StatusCode: 301, + AdditionalAttributes: []attribute.KeyValue{ + attribute.String("key", "value"), + }, + }, + MetricData: semconv.MetricData{ + RequestSize: 100, + ElapsedTime: 300, + }, + }) + + rm := metricdata.ResourceMetrics{} + require.NoError(t, reader.Collect(context.Background(), &rm)) + tt.wantFunc(t, rm) + }) + } +} + func TestNewTraceResponse(t *testing.T) { testCases := []struct { name string diff --git a/instrumentation/net/http/otelhttp/internal/semconv/util.go b/instrumentation/net/http/otelhttp/internal/semconv/util.go index 93e8d0f94c1..2ce8f0d9591 100644 --- a/instrumentation/net/http/otelhttp/internal/semconv/util.go +++ b/instrumentation/net/http/otelhttp/internal/semconv/util.go @@ -96,3 +96,13 @@ func handleErr(err error) { otel.Handle(err) } } + +func standardizeHTTPMethod(method string) string { + method = strings.ToUpper(method) + switch method { + case http.MethodConnect, http.MethodDelete, http.MethodGet, http.MethodHead, http.MethodOptions, http.MethodPatch, http.MethodPost, http.MethodPut, http.MethodTrace: + default: + method = "_OTHER" + } + return method +} diff --git a/instrumentation/net/http/otelhttp/internal/semconv/util_test.go b/instrumentation/net/http/otelhttp/internal/semconv/util_test.go index b0fe5439883..ccc3619b5c0 100644 --- a/instrumentation/net/http/otelhttp/internal/semconv/util_test.go +++ b/instrumentation/net/http/otelhttp/internal/semconv/util_test.go @@ -39,3 +39,34 @@ func TestSplitHostPort(t *testing.T) { assert.Equal(t, test.port, p, test.hostport) } } + +func TestStandardizeHTTPMethod(t *testing.T) { + tests := []struct { + method string + want string + }{ + {"GET", "GET"}, + {"get", "GET"}, + {"POST", "POST"}, + {"post", "POST"}, + {"PUT", "PUT"}, + {"put", "PUT"}, + {"DELETE", "DELETE"}, + {"delete", "DELETE"}, + {"HEAD", "HEAD"}, + {"head", "HEAD"}, + {"OPTIONS", "OPTIONS"}, + {"options", "OPTIONS"}, + {"CONNECT", "CONNECT"}, + {"connect", "CONNECT"}, + {"TRACE", "TRACE"}, + {"trace", "TRACE"}, + {"PATCH", "PATCH"}, + {"patch", "PATCH"}, + {"unknown", "_OTHER"}, + {"", "_OTHER"}, + } + for _, test := range tests { + assert.Equal(t, test.want, standardizeHTTPMethod(test.method)) + } +} diff --git a/instrumentation/net/http/otelhttp/internal/semconv/v1.20.0.go b/instrumentation/net/http/otelhttp/internal/semconv/v1.20.0.go index c042249dd72..8899cdb725f 100644 --- a/instrumentation/net/http/otelhttp/internal/semconv/v1.20.0.go +++ b/instrumentation/net/http/otelhttp/internal/semconv/v1.20.0.go @@ -8,7 +8,6 @@ import ( "io" "net/http" "slices" - "strings" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconvutil" "go.opentelemetry.io/otel/attribute" @@ -144,7 +143,7 @@ func (o OldHTTPServer) MetricAttributes(server string, req *http.Request, status attributes := slices.Grow(additionalAttributes, n) attributes = append(attributes, - standardizeHTTPMethodMetric(req.Method), + semconv.HTTPMethod(standardizeHTTPMethod(req.Method)), o.scheme(req.TLS != nil), semconv.NetHostName(host)) @@ -214,7 +213,7 @@ func (o OldHTTPClient) MetricAttributes(req *http.Request, statusCode int, addit attributes := slices.Grow(additionalAttributes, n) attributes = append(attributes, - standardizeHTTPMethodMetric(req.Method), + semconv.HTTPMethod(standardizeHTTPMethod(req.Method)), semconv.NetPeerName(requestHost), ) @@ -262,13 +261,3 @@ func (o OldHTTPClient) createMeasures(meter metric.Meter) (metric.Int64Counter, return requestBytesCounter, responseBytesCounter, latencyMeasure } - -func standardizeHTTPMethodMetric(method string) attribute.KeyValue { - method = strings.ToUpper(method) - switch method { - case http.MethodConnect, http.MethodDelete, http.MethodGet, http.MethodHead, http.MethodOptions, http.MethodPatch, http.MethodPost, http.MethodPut, http.MethodTrace: - default: - method = "_OTHER" - } - return semconv.HTTPMethod(method) -} diff --git a/instrumentation/net/http/otelhttp/internal/semconv/v1.20.0_test.go b/instrumentation/net/http/otelhttp/internal/semconv/v1.20.0_test.go deleted file mode 100644 index a3731492729..00000000000 --- a/instrumentation/net/http/otelhttp/internal/semconv/v1.20.0_test.go +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -package semconv - -import ( - "testing" - - "github.com/stretchr/testify/assert" - - "go.opentelemetry.io/otel/attribute" -) - -func TestStandardizeHTTPMethodMetric(t *testing.T) { - testCases := []struct { - method string - want attribute.KeyValue - }{ - { - method: "GET", - want: attribute.String("http.method", "GET"), - }, - { - method: "POST", - want: attribute.String("http.method", "POST"), - }, - { - method: "PUT", - want: attribute.String("http.method", "PUT"), - }, - { - method: "DELETE", - want: attribute.String("http.method", "DELETE"), - }, - { - method: "HEAD", - want: attribute.String("http.method", "HEAD"), - }, - { - method: "OPTIONS", - want: attribute.String("http.method", "OPTIONS"), - }, - { - method: "CONNECT", - want: attribute.String("http.method", "CONNECT"), - }, - { - method: "TRACE", - want: attribute.String("http.method", "TRACE"), - }, - { - method: "PATCH", - want: attribute.String("http.method", "PATCH"), - }, - { - method: "test", - want: attribute.String("http.method", "_OTHER"), - }, - } - for _, tt := range testCases { - t.Run(tt.method, func(t *testing.T) { - got := standardizeHTTPMethodMetric(tt.method) - assert.Equal(t, tt.want, got) - }) - } -}