From 5d0d71fe445be0e3ea5a73f375357a5a8014b68e Mon Sep 17 00:00:00 2001 From: dmathieu <42@dmathieu.com> Date: Tue, 15 Jul 2025 13:32:24 +0200 Subject: [PATCH 1/7] remove semconvutil --- .../otelrestful/internal/semconv/env.go | 109 +--- .../otelrestful/internal/semconv/env_test.go | 66 -- .../otelrestful/internal/semconv/gen.go | 1 - .../internal/semconv/httpconvtest_test.go | 230 +------ .../otelrestful/internal/semconv/v1.20.0.go | 273 -------- .../otelrestful/internal/semconvutil/gen.go | 10 - .../internal/semconvutil/httpconv.go | 594 ------------------ .../internal/semconvutil/httpconv_test.go | 561 ----------------- .../internal/semconvutil/netconv.go | 214 ------- .../internal/semconvutil/netconv_test.go | 200 ------ .../gin/otelgin/internal/semconv/env.go | 109 +--- .../gin/otelgin/internal/semconv/env_test.go | 66 -- .../gin/otelgin/internal/semconv/gen.go | 1 - .../internal/semconv/httpconvtest_test.go | 230 +------ .../gin/otelgin/internal/semconv/v1.20.0.go | 273 -------- .../gin/otelgin/internal/semconvutil/gen.go | 10 - .../otelgin/internal/semconvutil/httpconv.go | 594 ------------------ .../internal/semconvutil/httpconv_test.go | 561 ----------------- .../otelgin/internal/semconvutil/netconv.go | 214 ------- .../internal/semconvutil/netconv_test.go | 200 ------ .../mux/otelmux/internal/semconv/env.go | 109 +--- .../mux/otelmux/internal/semconv/env_test.go | 66 -- .../mux/otelmux/internal/semconv/gen.go | 1 - .../internal/semconv/httpconvtest_test.go | 230 +------ .../mux/otelmux/internal/semconv/v1.20.0.go | 273 -------- .../mux/otelmux/internal/semconvutil/gen.go | 10 - .../otelmux/internal/semconvutil/httpconv.go | 594 ------------------ .../internal/semconvutil/httpconv_test.go | 561 ----------------- .../otelmux/internal/semconvutil/netconv.go | 214 ------- .../internal/semconvutil/netconv_test.go | 200 ------ .../echo/otelecho/internal/semconvutil/gen.go | 10 - .../otelecho/internal/semconvutil/httpconv.go | 594 ------------------ .../internal/semconvutil/httpconv_test.go | 561 ----------------- .../otelecho/internal/semconvutil/netconv.go | 214 ------- .../internal/semconvutil/netconv_test.go | 200 ------ .../otelhttptrace/internal/semconv/env.go | 109 +--- .../internal/semconv/env_test.go | 66 -- .../otelhttptrace/internal/semconv/gen.go | 1 - .../internal/semconv/httpconvtest_test.go | 230 +------ .../otelhttptrace/internal/semconv/v1.20.0.go | 273 -------- .../otelhttptrace/internal/semconvutil/gen.go | 10 - .../internal/semconvutil/httpconv.go | 594 ------------------ .../internal/semconvutil/httpconv_test.go | 561 ----------------- .../internal/semconvutil/netconv.go | 214 ------- .../internal/semconvutil/netconv_test.go | 200 ------ .../net/http/otelhttp/internal/semconv/env.go | 109 +--- .../otelhttp/internal/semconv/env_test.go | 66 -- .../net/http/otelhttp/internal/semconv/gen.go | 1 - .../internal/semconv/httpconvtest_test.go | 230 +------ .../http/otelhttp/internal/semconv/v1.20.0.go | 273 -------- .../http/otelhttp/internal/semconvutil/gen.go | 10 - .../otelhttp/internal/semconvutil/httpconv.go | 594 ------------------ .../internal/semconvutil/httpconv_test.go | 561 ----------------- .../otelhttp/internal/semconvutil/netconv.go | 214 ------- .../internal/semconvutil/netconv_test.go | 200 ------ .../net/http/otelhttp/transport.go | 9 - internal/shared/semconv/env.go.tmpl | 109 +--- internal/shared/semconv/env_test.go.tmpl | 66 -- .../shared/semconv/httpconvtest_test.go.tmpl | 230 +------ internal/shared/semconv/v1.20.0.go.tmpl | 273 -------- internal/shared/semconvutil/httpconv.go.tmpl | 594 ------------------ .../shared/semconvutil/httpconv_test.go.tmpl | 561 ----------------- internal/shared/semconvutil/netconv.go.tmpl | 214 ------- .../shared/semconvutil/netconv_test.go.tmpl | 200 ------ 64 files changed, 72 insertions(+), 15053 deletions(-) delete mode 100644 instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconv/v1.20.0.go delete mode 100644 instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconvutil/gen.go delete mode 100644 instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconvutil/httpconv.go delete mode 100644 instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconvutil/httpconv_test.go delete mode 100644 instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconvutil/netconv.go delete mode 100644 instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconvutil/netconv_test.go delete mode 100644 instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconv/v1.20.0.go delete mode 100644 instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconvutil/gen.go delete mode 100644 instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconvutil/httpconv.go delete mode 100644 instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconvutil/httpconv_test.go delete mode 100644 instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconvutil/netconv.go delete mode 100644 instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconvutil/netconv_test.go delete mode 100644 instrumentation/github.com/gorilla/mux/otelmux/internal/semconv/v1.20.0.go delete mode 100644 instrumentation/github.com/gorilla/mux/otelmux/internal/semconvutil/gen.go delete mode 100644 instrumentation/github.com/gorilla/mux/otelmux/internal/semconvutil/httpconv.go delete mode 100644 instrumentation/github.com/gorilla/mux/otelmux/internal/semconvutil/httpconv_test.go delete mode 100644 instrumentation/github.com/gorilla/mux/otelmux/internal/semconvutil/netconv.go delete mode 100644 instrumentation/github.com/gorilla/mux/otelmux/internal/semconvutil/netconv_test.go delete mode 100644 instrumentation/github.com/labstack/echo/otelecho/internal/semconvutil/gen.go delete mode 100644 instrumentation/github.com/labstack/echo/otelecho/internal/semconvutil/httpconv.go delete mode 100644 instrumentation/github.com/labstack/echo/otelecho/internal/semconvutil/httpconv_test.go delete mode 100644 instrumentation/github.com/labstack/echo/otelecho/internal/semconvutil/netconv.go delete mode 100644 instrumentation/github.com/labstack/echo/otelecho/internal/semconvutil/netconv_test.go delete mode 100644 instrumentation/net/http/httptrace/otelhttptrace/internal/semconv/v1.20.0.go delete mode 100644 instrumentation/net/http/httptrace/otelhttptrace/internal/semconvutil/gen.go delete mode 100644 instrumentation/net/http/httptrace/otelhttptrace/internal/semconvutil/httpconv.go delete mode 100644 instrumentation/net/http/httptrace/otelhttptrace/internal/semconvutil/httpconv_test.go delete mode 100644 instrumentation/net/http/httptrace/otelhttptrace/internal/semconvutil/netconv.go delete mode 100644 instrumentation/net/http/httptrace/otelhttptrace/internal/semconvutil/netconv_test.go delete mode 100644 instrumentation/net/http/otelhttp/internal/semconv/v1.20.0.go delete mode 100644 instrumentation/net/http/otelhttp/internal/semconvutil/gen.go delete mode 100644 instrumentation/net/http/otelhttp/internal/semconvutil/httpconv.go delete mode 100644 instrumentation/net/http/otelhttp/internal/semconvutil/httpconv_test.go delete mode 100644 instrumentation/net/http/otelhttp/internal/semconvutil/netconv.go delete mode 100644 instrumentation/net/http/otelhttp/internal/semconvutil/netconv_test.go delete mode 100644 internal/shared/semconv/v1.20.0.go.tmpl delete mode 100644 internal/shared/semconvutil/httpconv.go.tmpl delete mode 100644 internal/shared/semconvutil/httpconv_test.go.tmpl delete mode 100644 internal/shared/semconvutil/netconv.go.tmpl delete mode 100644 internal/shared/semconvutil/netconv_test.go.tmpl diff --git a/instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconv/env.go b/instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconv/env.go index e47d5631a0f..01dbb3e315c 100644 --- a/instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconv/env.go +++ b/instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconv/env.go @@ -10,7 +10,6 @@ import ( "context" "fmt" "net/http" - "os" "strings" "sync" @@ -33,14 +32,6 @@ type ResponseTelemetry struct { } type HTTPServer struct { - duplicate bool - - // Old metrics - requestBytesCounter metric.Int64Counter - responseBytesCounter metric.Int64Counter - serverLatencyMeasure metric.Float64Histogram - - // New metrics requestBodySizeHistogram httpconv.ServerRequestBodySize responseBodySizeHistogram httpconv.ServerResponseBodySize requestDurationHistogram httpconv.ServerRequestDuration @@ -63,20 +54,10 @@ type HTTPServer struct { // If the primary server name is not known, server should be an empty string. // The req Host will be used to determine the server instead. func (s HTTPServer) RequestTraceAttrs(server string, req *http.Request, opts RequestTraceAttrsOpts) []attribute.KeyValue { - attrs := CurrentHTTPServer{}.RequestTraceAttrs(server, req, opts) - if s.duplicate { - return OldHTTPServer{}.RequestTraceAttrs(server, req, attrs) - } - return attrs + return CurrentHTTPServer{}.RequestTraceAttrs(server, req, opts) } func (s HTTPServer) NetworkTransportAttr(network string) []attribute.KeyValue { - if s.duplicate { - return []attribute.KeyValue{ - OldHTTPServer{}.NetworkTransportAttr(network), - CurrentHTTPServer{}.NetworkTransportAttr(network), - } - } return []attribute.KeyValue{ CurrentHTTPServer{}.NetworkTransportAttr(network), } @@ -86,11 +67,7 @@ func (s HTTPServer) NetworkTransportAttr(network string) []attribute.KeyValue { // // If any of the fields in the ResponseTelemetry are not set the attribute will be omitted. func (s HTTPServer) ResponseTraceAttrs(resp ResponseTelemetry) []attribute.KeyValue { - attrs := CurrentHTTPServer{}.ResponseTraceAttrs(resp) - if s.duplicate { - return OldHTTPServer{}.ResponseTraceAttrs(resp, attrs) - } - return attrs + return CurrentHTTPServer{}.ResponseTraceAttrs(resp) } // Route returns the attribute for the route. @@ -156,18 +133,6 @@ func (s HTTPServer) RecordMetrics(ctx context.Context, md ServerMetricData) { s.requestDurationHistogram.Inst().Record(ctx, md.ElapsedTime/1000.0, o) *recordOpts = (*recordOpts)[:0] metricRecordOptionPool.Put(recordOpts) - - if s.duplicate && 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) - } } // hasOptIn returns true if the comma-separated version string contains the @@ -182,11 +147,7 @@ func hasOptIn(version, optIn string) bool { } func NewHTTPServer(meter metric.Meter) HTTPServer { - env := strings.ToLower(os.Getenv(OTelSemConvStabilityOptIn)) - duplicate := hasOptIn(env, "http/dup") - server := HTTPServer{ - duplicate: duplicate, - } + server := HTTPServer{} var err error server.requestBodySizeHistogram, err = httpconv.NewServerRequestBodySize(meter) @@ -203,32 +164,16 @@ func NewHTTPServer(meter metric.Meter) HTTPServer { ), ) handleErr(err) - - if duplicate { - server.requestBytesCounter, server.responseBytesCounter, server.serverLatencyMeasure = OldHTTPServer{}.createMeasures(meter) - } return server } type HTTPClient struct { - duplicate bool - - // old metrics - requestBytesCounter metric.Int64Counter - responseBytesCounter metric.Int64Counter - latencyMeasure metric.Float64Histogram - - // new metrics requestBodySize httpconv.ClientRequestBodySize requestDuration httpconv.ClientRequestDuration } func NewHTTPClient(meter metric.Meter) HTTPClient { - env := strings.ToLower(os.Getenv(OTelSemConvStabilityOptIn)) - duplicate := hasOptIn(env, "http/dup") - client := HTTPClient{ - duplicate: duplicate, - } + client := HTTPClient{} var err error client.requestBodySize, err = httpconv.NewClientRequestBodySize(meter) @@ -240,29 +185,17 @@ func NewHTTPClient(meter metric.Meter) HTTPClient { ) handleErr(err) - if duplicate { - client.requestBytesCounter, client.responseBytesCounter, client.latencyMeasure = OldHTTPClient{}.createMeasures(meter) - } - return client } // RequestTraceAttrs returns attributes for an HTTP request made by a client. func (c HTTPClient) RequestTraceAttrs(req *http.Request) []attribute.KeyValue { - attrs := CurrentHTTPClient{}.RequestTraceAttrs(req) - if c.duplicate { - return OldHTTPClient{}.RequestTraceAttrs(req, attrs) - } - return attrs + return CurrentHTTPClient{}.RequestTraceAttrs(req) } // ResponseTraceAttrs returns metric attributes for an HTTP request made by a client. func (c HTTPClient) ResponseTraceAttrs(resp *http.Response) []attribute.KeyValue { - attrs := CurrentHTTPClient{}.ResponseTraceAttrs(resp) - if c.duplicate { - return OldHTTPClient{}.ResponseTraceAttrs(resp, attrs) - } - return attrs + return CurrentHTTPClient{}.ResponseTraceAttrs(resp) } func (c HTTPClient) Status(code int) (codes.Code, string) { @@ -302,42 +235,14 @@ func (c HTTPClient) MetricOptions(ma MetricAttributes) map[string]MetricOpts { addOptions: set, } - if c.duplicate { - attributes := OldHTTPClient{}.MetricAttributes(ma.Req, ma.StatusCode, ma.AdditionalAttributes) - set := metric.WithAttributeSet(attribute.NewSet(attributes...)) - opts["old"] = MetricOpts{ - measurement: set, - addOptions: set, - } - } - return opts } func (s HTTPClient) RecordMetrics(ctx context.Context, md MetricData, opts map[string]MetricOpts) { s.requestBodySize.Inst().Record(ctx, md.RequestSize, opts["new"].MeasurementOption()) s.requestDuration.Inst().Record(ctx, md.ElapsedTime/1000, opts["new"].MeasurementOption()) - - if s.duplicate { - s.requestBytesCounter.Add(ctx, md.RequestSize, opts["old"].AddOptions()) - s.latencyMeasure.Record(ctx, md.ElapsedTime, opts["old"].MeasurementOption()) - } -} - -func (s HTTPClient) RecordResponseSize(ctx context.Context, responseData int64, opts map[string]MetricOpts) { - if s.responseBytesCounter == nil { - // This will happen if an HTTPClient{} is used instead of NewHTTPClient(). - return - } - - s.responseBytesCounter.Add(ctx, responseData, opts["old"].AddOptions()) } func (s HTTPClient) TraceAttributes(host string) []attribute.KeyValue { - attrs := CurrentHTTPClient{}.TraceAttributes(host) - if s.duplicate { - return OldHTTPClient{}.TraceAttributes(host, attrs) - } - - return attrs + return CurrentHTTPClient{}.TraceAttributes(host) } diff --git a/instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconv/env_test.go b/instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconv/env_test.go index 45b520854dd..11388bb8324 100644 --- a/instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconv/env_test.go +++ b/instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconv/env_test.go @@ -54,7 +54,6 @@ func TestHTTPServerDoesNotPanic(t *testing.T) { func TestServerNetworkTransportAttr(t *testing.T) { for _, tt := range []struct { name string - optinVal string network string wantAttributes []attribute.KeyValue @@ -67,29 +66,8 @@ func TestServerNetworkTransportAttr(t *testing.T) { attribute.String("network.transport", "tcp"), }, }, - { - name: "without a dup optin", - optinVal: "http/dup", - network: "tcp", - - wantAttributes: []attribute.KeyValue{ - attribute.String("net.transport", "ip_tcp"), - attribute.String("network.transport", "tcp"), - }, - }, - { - name: "with mixed categories", - optinVal: "http/dup,database", - network: "tcp", - - wantAttributes: []attribute.KeyValue{ - attribute.String("net.transport", "ip_tcp"), - attribute.String("network.transport", "tcp"), - }, - }, } { t.Run(tt.name, func(t *testing.T) { - t.Setenv(OTelSemConvStabilityOptIn, tt.optinVal) s := NewHTTPServer(nil) assert.Equal(t, tt.wantAttributes, s.NetworkTransportAttr(tt.network)) @@ -124,7 +102,6 @@ func TestHTTPClientDoesNotPanic(t *testing.T) { Req: req, StatusCode: 200, }) - tt.client.RecordResponseSize(context.Background(), 40, opts) tt.client.RecordMetrics(context.Background(), MetricData{ RequestSize: 20, ElapsedTime: 1, @@ -137,7 +114,6 @@ func TestHTTPClientDoesNotPanic(t *testing.T) { func TestHTTPClientTraceAttributes(t *testing.T) { for _, tt := range []struct { name string - optinVal string wantAttributes []attribute.KeyValue }{ @@ -148,28 +124,8 @@ func TestHTTPClientTraceAttributes(t *testing.T) { attribute.String("server.address", "example.com"), }, }, - { - name: "with optin set to duplicate", - optinVal: "http/dup", - - wantAttributes: []attribute.KeyValue{ - attribute.String("server.address", "example.com"), - attribute.String("net.host.name", "example.com"), - }, - }, - { - name: "with mixed categories", - optinVal: "http/dup,database", - - wantAttributes: []attribute.KeyValue{ - attribute.String("server.address", "example.com"), - attribute.String("net.host.name", "example.com"), - }, - }, } { t.Run(tt.name, func(t *testing.T) { - t.Setenv(OTelSemConvStabilityOptIn, tt.optinVal) - c := NewHTTPClient(nil) a := c.TraceAttributes("example.com") assert.Equal(t, tt.wantAttributes, a) @@ -180,7 +136,6 @@ func TestHTTPClientTraceAttributes(t *testing.T) { func TestClientTraceAttributes(t *testing.T) { for _, tt := range []struct { name string - optinVal string host string wantAttributes []attribute.KeyValue @@ -193,29 +148,8 @@ func TestClientTraceAttributes(t *testing.T) { attribute.String("server.address", "example.com"), }, }, - { - name: "without a dup optin", - optinVal: "http/dup", - host: "example.com", - - wantAttributes: []attribute.KeyValue{ - attribute.String("server.address", "example.com"), - attribute.String("net.host.name", "example.com"), - }, - }, - { - name: "with mixed categories", - optinVal: "http/dup", - host: "example.com", - - wantAttributes: []attribute.KeyValue{ - attribute.String("server.address", "example.com"), - attribute.String("net.host.name", "example.com"), - }, - }, } { t.Run(tt.name, func(t *testing.T) { - t.Setenv(OTelSemConvStabilityOptIn, tt.optinVal) s := NewHTTPClient(nil) assert.Equal(t, tt.wantAttributes, s.TraceAttributes(tt.host)) diff --git a/instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconv/gen.go b/instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconv/gen.go index 2ed878d9db6..0af4a8c6d67 100644 --- a/instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconv/gen.go +++ b/instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconv/gen.go @@ -13,4 +13,3 @@ package semconv // import "go.opentelemetry.io/contrib/instrumentation/github.co //go:generate gotmpl --body=../../../../../../../internal/shared/semconv/httpconvtest_test.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful\" }" --out=httpconvtest_test.go //go:generate gotmpl --body=../../../../../../../internal/shared/semconv/util.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful\" }" --out=util.go //go:generate gotmpl --body=../../../../../../../internal/shared/semconv/util_test.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful\" }" --out=util_test.go -//go:generate gotmpl --body=../../../../../../../internal/shared/semconv/v1.20.0.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful\" }" --out=v1.20.0.go diff --git a/instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconv/httpconvtest_test.go b/instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconv/httpconvtest_test.go index f4d8010f951..2e92026e8dc 100644 --- a/instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconv/httpconvtest_test.go +++ b/instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconv/httpconvtest_test.go @@ -28,7 +28,6 @@ import ( ) func TestNewTraceRequest(t *testing.T) { - t.Setenv("OTEL_SEMCONV_STABILITY_OPT_IN", "http/dup") serv := semconv.NewHTTPServer(nil) want := func(req testServerReq) []attribute.KeyValue { return []attribute.KeyValue{ @@ -42,16 +41,6 @@ func TestNewTraceRequest(t *testing.T) { attribute.String("client.address", req.clientIP), attribute.String("network.protocol.version", "1.1"), attribute.String("url.path", "/"), - attribute.String("http.method", "GET"), - attribute.String("http.scheme", "http"), - attribute.String("net.host.name", req.hostname), - attribute.Int("net.host.port", req.serverPort), - attribute.String("net.sock.peer.addr", req.peerAddr), - attribute.Int("net.sock.peer.port", req.peerPort), - attribute.String("user_agent.original", "Go-http-client/1.1"), - attribute.String("http.client_ip", req.clientIP), - attribute.String("net.protocol.version", "1.1"), - attribute.String("http.target", "/"), } } testTraceRequest(t, serv, want) @@ -174,13 +163,11 @@ func TestNewServerRecordMetrics(t *testing.T) { 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, + name: "No Meter", serverFunc: func(metric.MeterProvider) semconv.HTTPServer { return semconv.NewHTTPServer(nil) }, @@ -189,8 +176,7 @@ func TestNewServerRecordMetrics(t *testing.T) { }, }, { - name: "No environment variable set, but with Meter", - setEnv: false, + name: "With Meter", serverFunc: func(mp metric.MeterProvider) semconv.HTTPServer { return semconv.NewHTTPServer(mp.Meter("test")) }, @@ -202,36 +188,10 @@ func TestNewServerRecordMetrics(t *testing.T) { metricdatatest.AssertEqual(t, expectedCurrentScopeMetric, 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, expectedOldScopeMetric, 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)) @@ -312,7 +272,6 @@ func TestNewTraceResponse(t *testing.T) { } func TestNewTraceRequest_Client(t *testing.T) { - t.Setenv("OTEL_SEMCONV_STABILITY_OPT_IN", "http/dup") body := strings.NewReader("Hello, world!") url := "https://example.com:8888/foo/bar?stuff=morestuff" req := httptest.NewRequest("pOST", url, body) @@ -321,29 +280,22 @@ func TestNewTraceRequest_Client(t *testing.T) { want := []attribute.KeyValue{ attribute.String("http.request.method", "POST"), attribute.String("http.request.method_original", "pOST"), - attribute.String("http.method", "pOST"), attribute.String("url.full", url), - attribute.String("http.url", url), attribute.String("server.address", "example.com"), attribute.Int("server.port", 8888), attribute.String("network.protocol.version", "1.1"), - attribute.String("net.peer.name", "example.com"), - attribute.Int("net.peer.port", 8888), - attribute.String("user_agent.original", "go-test-agent"), - attribute.Int("http.request_content_length", 13), } client := semconv.NewHTTPClient(nil) assert.ElementsMatch(t, want, client.RequestTraceAttrs(req)) } func TestNewTraceResponse_Client(t *testing.T) { - t.Setenv("OTEL_SEMCONV_STABILITY_OPT_IN", "http/dup") testcases := []struct { resp http.Response want []attribute.KeyValue }{ - {resp: http.Response{StatusCode: 200, ContentLength: 123}, want: []attribute.KeyValue{attribute.Int("http.response.status_code", 200), attribute.Int("http.status_code", 200), attribute.Int("http.response_content_length", 123)}}, - {resp: http.Response{StatusCode: 404, ContentLength: 0}, want: []attribute.KeyValue{attribute.Int("http.response.status_code", 404), attribute.Int("http.status_code", 404), attribute.String("error.type", "404")}}, + {resp: http.Response{StatusCode: 200, ContentLength: 123}, want: []attribute.KeyValue{attribute.Int("http.response.status_code", 200)}}, + {resp: http.Response{StatusCode: 404, ContentLength: 0}, want: []attribute.KeyValue{attribute.Int("http.response.status_code", 404), attribute.String("error.type", "404")}}, } for _, tt := range testcases { @@ -401,12 +353,6 @@ func TestRequestErrorType(t *testing.T) { } func TestNewClientRecordMetrics(t *testing.T) { - oldAttrs := attribute.NewSet( - attribute.String("http.method", "POST"), - attribute.Int64("http.status_code", 301), - attribute.String("net.peer.name", "example.com"), - ) - currAttrs := attribute.NewSet( attribute.String("http.request.method", "POST"), attribute.Int64("http.response.status_code", 301), @@ -451,47 +397,13 @@ func TestNewClientRecordMetrics(t *testing.T) { }, } - // The OldHTTPClient version - expectedOldScopeMetric := expectedCurrentScopeMetric - expectedOldScopeMetric.Metrics = append(expectedOldScopeMetric.Metrics, []metricdata.Metrics{ - { - Name: "http.client.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.client.duration", - Description: "Measures the duration of outbound HTTP requests.", - Unit: "ms", - Data: metricdata.Histogram[float64]{ - Temporality: metricdata.CumulativeTemporality, - DataPoints: []metricdata.HistogramDataPoint[float64]{ - { - Attributes: oldAttrs, - }, - }, - }, - }, - }...) - tests := []struct { name string - setEnv bool clientFunc func(metric.MeterProvider) semconv.HTTPClient wantFunc func(t *testing.T, rm metricdata.ResourceMetrics) }{ { name: "No environment variable set, and no Meter", - setEnv: false, clientFunc: func(metric.MeterProvider) semconv.HTTPClient { return semconv.NewHTTPClient(nil) }, @@ -500,8 +412,7 @@ func TestNewClientRecordMetrics(t *testing.T) { }, }, { - name: "No environment variable set, but with Meter", - setEnv: false, + name: "With Meter", clientFunc: func(mp metric.MeterProvider) semconv.HTTPClient { return semconv.NewHTTPClient(mp.Meter("test")) }, @@ -512,36 +423,10 @@ func TestNewClientRecordMetrics(t *testing.T) { metricdatatest.AssertEqual(t, expectedCurrentScopeMetric, rm.ScopeMetrics[0], metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreValue()) }, }, - { - name: "Set environment variable, but no Meter", - setEnv: true, - clientFunc: func(metric.MeterProvider) semconv.HTTPClient { - return semconv.NewHTTPClient(nil) - }, - wantFunc: func(t *testing.T, rm metricdata.ResourceMetrics) { - assert.Empty(t, rm.ScopeMetrics) - }, - }, - { - name: "Set environment variable and Meter", - setEnv: true, - clientFunc: func(mp metric.MeterProvider) semconv.HTTPClient { - return semconv.NewHTTPClient(mp.Meter("test")) - }, - wantFunc: func(t *testing.T, rm metricdata.ResourceMetrics) { - require.Len(t, rm.ScopeMetrics, 1) - require.Len(t, rm.ScopeMetrics[0].Metrics, 4) - metricdatatest.AssertEqual(t, expectedOldScopeMetric, 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)) @@ -564,111 +449,6 @@ func TestNewClientRecordMetrics(t *testing.T) { } } -func TestClientRecordResponseSize(t *testing.T) { - oldAttrs := attribute.NewSet( - attribute.String("http.method", "POST"), - attribute.Int64("http.status_code", 301), - attribute.String("net.peer.name", "example.com"), - ) - - // The OldHTTPClient version - expectedOldScopeMetric := metricdata.ScopeMetrics{ - Scope: instrumentation.Scope{ - Name: "test", - }, - Metrics: []metricdata.Metrics{ - { - Name: "http.client.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, - }, - }, - }, - }, - }, - } - - tests := []struct { - name string - setEnv bool - clientFunc func(metric.MeterProvider) semconv.HTTPClient - wantFunc func(t *testing.T, rm metricdata.ResourceMetrics) - }{ - { - name: "No environment variable set, and no Meter", - setEnv: false, - clientFunc: func(metric.MeterProvider) semconv.HTTPClient { - return semconv.NewHTTPClient(nil) - }, - wantFunc: func(t *testing.T, rm metricdata.ResourceMetrics) { - assert.Empty(t, rm.ScopeMetrics) - }, - }, - { - name: "No environment variable set, but with Meter", - setEnv: false, - clientFunc: func(mp metric.MeterProvider) semconv.HTTPClient { - return semconv.NewHTTPClient(mp.Meter("test")) - }, - wantFunc: func(t *testing.T, rm metricdata.ResourceMetrics) { - require.Empty(t, rm.ScopeMetrics) - }, - }, - { - name: "Set environment variable, but no Meter", - setEnv: true, - clientFunc: func(metric.MeterProvider) semconv.HTTPClient { - return semconv.NewHTTPClient(nil) - }, - wantFunc: func(t *testing.T, rm metricdata.ResourceMetrics) { - assert.Empty(t, rm.ScopeMetrics) - }, - }, - { - name: "Set environment variable and Meter", - setEnv: true, - clientFunc: func(mp metric.MeterProvider) semconv.HTTPClient { - return semconv.NewHTTPClient(mp.Meter("test")) - }, - wantFunc: func(t *testing.T, rm metricdata.ResourceMetrics) { - require.Len(t, rm.ScopeMetrics, 1) - require.Len(t, rm.ScopeMetrics[0].Metrics, 1) - metricdatatest.AssertEqual(t, expectedOldScopeMetric, 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)) - - client := tt.clientFunc(mp) - req, err := http.NewRequest("POST", "http://example.com", nil) - assert.NoError(t, err) - - client.RecordResponseSize(context.Background(), 100, client.MetricOptions(semconv.MetricAttributes{ - Req: req, - StatusCode: 301, - })) - - rm := metricdata.ResourceMetrics{} - require.NoError(t, reader.Collect(context.Background(), &rm)) - tt.wantFunc(t, rm) - }) - } -} - type customError struct{} func (customError) Error() string { diff --git a/instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconv/v1.20.0.go b/instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconv/v1.20.0.go deleted file mode 100644 index cb3ccb6e111..00000000000 --- a/instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconv/v1.20.0.go +++ /dev/null @@ -1,273 +0,0 @@ -// Code generated by gotmpl. DO NOT MODIFY. -// source: internal/shared/semconv/v120.0.go.tmpl - -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -package semconv // import "go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconv" - -import ( - "errors" - "io" - "net/http" - "slices" - - "go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconvutil" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/metric" - "go.opentelemetry.io/otel/metric/noop" - semconv "go.opentelemetry.io/otel/semconv/v1.20.0" -) - -type OldHTTPServer struct{} - -// RequestTraceAttrs returns trace attributes for an HTTP request received by a -// server. -// -// The server must be the primary server name if it is known. For example this -// would be the ServerName directive -// (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache -// server, and the server_name directive -// (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an -// nginx server. More generically, the primary server name would be the host -// header value that matches the default virtual host of an HTTP server. It -// should include the host identifier and if a port is used to route to the -// server that port identifier should be included as an appropriate port -// suffix. -// -// If the primary server name is not known, server should be an empty string. -// The req Host will be used to determine the server instead. -func (o OldHTTPServer) RequestTraceAttrs(server string, req *http.Request, attrs []attribute.KeyValue) []attribute.KeyValue { - return semconvutil.HTTPServerRequest(server, req, semconvutil.HTTPServerRequestOptions{}, attrs) -} - -func (o OldHTTPServer) NetworkTransportAttr(network string) attribute.KeyValue { - return semconvutil.NetTransport(network) -} - -// ResponseTraceAttrs returns trace attributes for telemetry from an HTTP response. -// -// If any of the fields in the ResponseTelemetry are not set the attribute will be omitted. -func (o OldHTTPServer) ResponseTraceAttrs(resp ResponseTelemetry, attributes []attribute.KeyValue) []attribute.KeyValue { - if resp.ReadBytes > 0 { - attributes = append(attributes, semconv.HTTPRequestContentLength(int(resp.ReadBytes))) - } - if resp.ReadError != nil && !errors.Is(resp.ReadError, io.EOF) { - // This is not in the semantic conventions, but is historically provided - attributes = append(attributes, attribute.String("http.read_error", resp.ReadError.Error())) - } - if resp.WriteBytes > 0 { - attributes = append(attributes, semconv.HTTPResponseContentLength(int(resp.WriteBytes))) - } - if resp.StatusCode > 0 { - attributes = append(attributes, semconv.HTTPStatusCode(resp.StatusCode)) - } - if resp.WriteError != nil && !errors.Is(resp.WriteError, io.EOF) { - // This is not in the semantic conventions, but is historically provided - attributes = append(attributes, attribute.String("http.write_error", resp.WriteError.Error())) - } - - return attributes -} - -// Route returns the attribute for the route. -func (o OldHTTPServer) Route(route string) attribute.KeyValue { - return semconv.HTTPRoute(route) -} - -// HTTPStatusCode returns the attribute for the HTTP status code. -// This is a temporary function needed by metrics. This will be removed when MetricsRequest is added. -func HTTPStatusCode(status int) attribute.KeyValue { - return semconv.HTTPStatusCode(status) -} - -// Server HTTP metrics. -const ( - serverRequestSize = "http.server.request.size" // Incoming request bytes total - serverResponseSize = "http.server.response.size" // Incoming response bytes total - serverDuration = "http.server.duration" // Incoming end to end duration, milliseconds -) - -func (h OldHTTPServer) createMeasures(meter metric.Meter) (metric.Int64Counter, metric.Int64Counter, metric.Float64Histogram) { - if meter == nil { - return noop.Int64Counter{}, noop.Int64Counter{}, noop.Float64Histogram{} - } - var err error - requestBytesCounter, err := meter.Int64Counter( - serverRequestSize, - metric.WithUnit("By"), - metric.WithDescription("Measures the size of HTTP request messages."), - ) - handleErr(err) - - responseBytesCounter, err := meter.Int64Counter( - serverResponseSize, - metric.WithUnit("By"), - metric.WithDescription("Measures the size of HTTP response messages."), - ) - handleErr(err) - - serverLatencyMeasure, err := meter.Float64Histogram( - serverDuration, - metric.WithUnit("ms"), - metric.WithDescription("Measures the duration of inbound HTTP requests."), - ) - handleErr(err) - - return requestBytesCounter, responseBytesCounter, serverLatencyMeasure -} - -func (o OldHTTPServer) MetricAttributes(server string, req *http.Request, statusCode int, additionalAttributes []attribute.KeyValue) []attribute.KeyValue { - n := 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 { - n++ - } - protoName, protoVersion := netProtocol(req.Proto) - if protoName != "" { - n++ - } - if protoVersion != "" { - n++ - } - - if statusCode > 0 { - n++ - } - - attributes := slices.Grow(additionalAttributes, n) - attributes = append(attributes, - semconv.HTTPMethod(standardizeHTTPMethod(req.Method)), - o.scheme(req.TLS != nil), - semconv.NetHostName(host)) - - if hostPort > 0 { - attributes = append(attributes, semconv.NetHostPort(hostPort)) - } - if protoName != "" { - attributes = append(attributes, semconv.NetProtocolName(protoName)) - } - if protoVersion != "" { - attributes = append(attributes, semconv.NetProtocolVersion(protoVersion)) - } - - if statusCode > 0 { - attributes = append(attributes, semconv.HTTPStatusCode(statusCode)) - } - return attributes -} - -func (o OldHTTPServer) scheme(https bool) attribute.KeyValue { // nolint:revive - if https { - return semconv.HTTPSchemeHTTPS - } - return semconv.HTTPSchemeHTTP -} - -type OldHTTPClient struct{} - -func (o OldHTTPClient) RequestTraceAttrs(req *http.Request, attrs []attribute.KeyValue) []attribute.KeyValue { - return semconvutil.HTTPClientRequest(req, attrs) -} - -func (o OldHTTPClient) ResponseTraceAttrs(resp *http.Response, attrs []attribute.KeyValue) []attribute.KeyValue { - return semconvutil.HTTPClientResponse(resp, attrs) -} - -func (o OldHTTPClient) MetricAttributes(req *http.Request, statusCode int, additionalAttributes []attribute.KeyValue) []attribute.KeyValue { - /* The following semantic conventions are returned if present: - http.method string - http.status_code int - net.peer.name string - net.peer.port int - */ - - n := 2 // method, peer name. - var h string - if req.URL != nil { - h = req.URL.Host - } - var requestHost string - var requestPort int - for _, hostport := range []string{h, req.Header.Get("Host")} { - requestHost, requestPort = SplitHostPort(hostport) - if requestHost != "" || requestPort > 0 { - break - } - } - - port := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", requestPort) - if port > 0 { - n++ - } - - if statusCode > 0 { - n++ - } - - attributes := slices.Grow(additionalAttributes, n) - attributes = append(attributes, - semconv.HTTPMethod(standardizeHTTPMethod(req.Method)), - semconv.NetPeerName(requestHost), - ) - - if port > 0 { - attributes = append(attributes, semconv.NetPeerPort(port)) - } - - if statusCode > 0 { - attributes = append(attributes, semconv.HTTPStatusCode(statusCode)) - } - return attributes -} - -// Client HTTP metrics. -const ( - clientRequestSize = "http.client.request.size" // Incoming request bytes total - clientResponseSize = "http.client.response.size" // Incoming response bytes total - clientDuration = "http.client.duration" // Incoming end to end duration, milliseconds -) - -func (o OldHTTPClient) createMeasures(meter metric.Meter) (metric.Int64Counter, metric.Int64Counter, metric.Float64Histogram) { - if meter == nil { - return noop.Int64Counter{}, noop.Int64Counter{}, noop.Float64Histogram{} - } - requestBytesCounter, err := meter.Int64Counter( - clientRequestSize, - metric.WithUnit("By"), - metric.WithDescription("Measures the size of HTTP request messages."), - ) - handleErr(err) - - responseBytesCounter, err := meter.Int64Counter( - clientResponseSize, - metric.WithUnit("By"), - metric.WithDescription("Measures the size of HTTP response messages."), - ) - handleErr(err) - - latencyMeasure, err := meter.Float64Histogram( - clientDuration, - metric.WithUnit("ms"), - metric.WithDescription("Measures the duration of outbound HTTP requests."), - ) - handleErr(err) - - return requestBytesCounter, responseBytesCounter, latencyMeasure -} - -// TraceAttributes returns attributes for httptrace. -func (c OldHTTPClient) TraceAttributes(host string, attrs []attribute.KeyValue) []attribute.KeyValue { - return append(attrs, semconv.NetHostName(host)) -} diff --git a/instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconvutil/gen.go b/instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconvutil/gen.go deleted file mode 100644 index dd23373c8f9..00000000000 --- a/instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconvutil/gen.go +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -package semconvutil // import "go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconvutil" - -// Generate semconvutil package: -//go:generate gotmpl --body=../../../../../../../internal/shared/semconvutil/httpconv_test.go.tmpl "--data={}" --out=httpconv_test.go -//go:generate gotmpl --body=../../../../../../../internal/shared/semconvutil/httpconv.go.tmpl "--data={}" --out=httpconv.go -//go:generate gotmpl --body=../../../../../../../internal/shared/semconvutil/netconv_test.go.tmpl "--data={}" --out=netconv_test.go -//go:generate gotmpl --body=../../../../../../../internal/shared/semconvutil/netconv.go.tmpl "--data={}" --out=netconv.go diff --git a/instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconvutil/httpconv.go b/instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconvutil/httpconv.go deleted file mode 100644 index d11e0356349..00000000000 --- a/instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconvutil/httpconv.go +++ /dev/null @@ -1,594 +0,0 @@ -// Code generated by gotmpl. DO NOT MODIFY. -// source: internal/shared/semconvutil/httpconv.go.tmpl - -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -// Package semconvutil provides OpenTelemetry semantic convention utilities. -package semconvutil // import "go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconvutil" - -import ( - "fmt" - "net/http" - "slices" - "strings" - - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/codes" - semconv "go.opentelemetry.io/otel/semconv/v1.20.0" -) - -type HTTPServerRequestOptions struct { - // If set, this is used as value for the "http.client_ip" attribute. - HTTPClientIP string -} - -// HTTPClientResponse returns trace attributes for an HTTP response received by a -// client from a server. It will return the following attributes if the related -// values are defined in resp: "http.status.code", -// "http.response_content_length". -// -// This does not add all OpenTelemetry required attributes for an HTTP event, -// it assumes ClientRequest was used to create the span with a complete set of -// attributes. If a complete set of attributes can be generated using the -// request contained in resp. For example: -// -// HTTPClientResponse(resp, ClientRequest(resp.Request))) -func HTTPClientResponse(resp *http.Response, attrs []attribute.KeyValue) []attribute.KeyValue { - return hc.ClientResponse(resp, attrs) -} - -// HTTPClientRequest returns trace attributes for an HTTP request made by a client. -// The following attributes are always returned: "http.url", "http.method", -// "net.peer.name". The following attributes are returned if the related values -// are defined in req: "net.peer.port", "user_agent.original", -// "http.request_content_length". -func HTTPClientRequest(req *http.Request, attrs []attribute.KeyValue) []attribute.KeyValue { - return hc.ClientRequest(req, attrs) -} - -// HTTPClientRequestMetrics returns metric attributes for an HTTP request made by a client. -// The following attributes are always returned: "http.method", "net.peer.name". -// The following attributes are returned if the -// related values are defined in req: "net.peer.port". -func HTTPClientRequestMetrics(req *http.Request) []attribute.KeyValue { - return hc.ClientRequestMetrics(req) -} - -// HTTPClientStatus returns a span status code and message for an HTTP status code -// value received by a client. -func HTTPClientStatus(code int) (codes.Code, string) { - return hc.ClientStatus(code) -} - -// HTTPServerRequest returns trace attributes for an HTTP request received by a -// server. -// -// The server must be the primary server name if it is known. For example this -// would be the ServerName directive -// (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache -// server, and the server_name directive -// (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an -// nginx server. More generically, the primary server name would be the host -// header value that matches the default virtual host of an HTTP server. It -// should include the host identifier and if a port is used to route to the -// server that port identifier should be included as an appropriate port -// suffix. -// -// If the primary server name is not known, server should be an empty string. -// The req Host will be used to determine the server instead. -// -// The following attributes are always returned: "http.method", "http.scheme", -// "http.target", "net.host.name". The following attributes are returned if -// they related values are defined in req: "net.host.port", "net.sock.peer.addr", -// "net.sock.peer.port", "user_agent.original", "http.client_ip". -func HTTPServerRequest(server string, req *http.Request, opts HTTPServerRequestOptions, attrs []attribute.KeyValue) []attribute.KeyValue { - return hc.ServerRequest(server, req, opts, attrs) -} - -// HTTPServerRequestMetrics returns metric attributes for an HTTP request received by a -// server. -// -// The server must be the primary server name if it is known. For example this -// would be the ServerName directive -// (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache -// server, and the server_name directive -// (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an -// nginx server. More generically, the primary server name would be the host -// header value that matches the default virtual host of an HTTP server. It -// should include the host identifier and if a port is used to route to the -// server that port identifier should be included as an appropriate port -// suffix. -// -// If the primary server name is not known, server should be an empty string. -// The req Host will be used to determine the server instead. -// -// The following attributes are always returned: "http.method", "http.scheme", -// "net.host.name". The following attributes are returned if they related -// values are defined in req: "net.host.port". -func HTTPServerRequestMetrics(server string, req *http.Request) []attribute.KeyValue { - return hc.ServerRequestMetrics(server, req) -} - -// HTTPServerStatus returns a span status code and message for an HTTP status code -// value returned by a server. Status codes in the 400-499 range are not -// returned as errors. -func HTTPServerStatus(code int) (codes.Code, string) { - return hc.ServerStatus(code) -} - -// httpConv are the HTTP semantic convention attributes defined for a version -// of the OpenTelemetry specification. -type httpConv struct { - NetConv *netConv - - HTTPClientIPKey attribute.Key - HTTPMethodKey attribute.Key - HTTPRequestContentLengthKey attribute.Key - HTTPResponseContentLengthKey attribute.Key - HTTPRouteKey attribute.Key - HTTPSchemeHTTP attribute.KeyValue - HTTPSchemeHTTPS attribute.KeyValue - HTTPStatusCodeKey attribute.Key - HTTPTargetKey attribute.Key - HTTPURLKey attribute.Key - UserAgentOriginalKey attribute.Key -} - -var hc = &httpConv{ - NetConv: nc, - - HTTPClientIPKey: semconv.HTTPClientIPKey, - HTTPMethodKey: semconv.HTTPMethodKey, - HTTPRequestContentLengthKey: semconv.HTTPRequestContentLengthKey, - HTTPResponseContentLengthKey: semconv.HTTPResponseContentLengthKey, - HTTPRouteKey: semconv.HTTPRouteKey, - HTTPSchemeHTTP: semconv.HTTPSchemeHTTP, - HTTPSchemeHTTPS: semconv.HTTPSchemeHTTPS, - HTTPStatusCodeKey: semconv.HTTPStatusCodeKey, - HTTPTargetKey: semconv.HTTPTargetKey, - HTTPURLKey: semconv.HTTPURLKey, - UserAgentOriginalKey: semconv.UserAgentOriginalKey, -} - -// ClientResponse returns attributes for an HTTP response received by a client -// from a server. The following attributes are returned if the related values -// are defined in resp: "http.status.code", "http.response_content_length". -// -// This does not add all OpenTelemetry required attributes for an HTTP event, -// it assumes ClientRequest was used to create the span with a complete set of -// attributes. If a complete set of attributes can be generated using the -// request contained in resp. For example: -// -// ClientResponse(resp, ClientRequest(resp.Request)) -func (c *httpConv) ClientResponse(resp *http.Response, attrs []attribute.KeyValue) []attribute.KeyValue { - /* The following semantic conventions are returned if present: - http.status_code int - http.response_content_length int - */ - var n int - if resp.StatusCode > 0 { - n++ - } - if resp.ContentLength > 0 { - n++ - } - if n == 0 { - return attrs - } - - attrs = slices.Grow(attrs, n) - if resp.StatusCode > 0 { - attrs = append(attrs, c.HTTPStatusCodeKey.Int(resp.StatusCode)) - } - if resp.ContentLength > 0 { - attrs = append(attrs, c.HTTPResponseContentLengthKey.Int(int(resp.ContentLength))) - } - return attrs -} - -// ClientRequest returns attributes for an HTTP request made by a client. The -// following attributes are always returned: "http.url", "http.method", -// "net.peer.name". The following attributes are returned if the related values -// are defined in req: "net.peer.port", "user_agent.original", -// "http.request_content_length", "user_agent.original". -func (c *httpConv) ClientRequest(req *http.Request, attrs []attribute.KeyValue) []attribute.KeyValue { - /* The following semantic conventions are returned if present: - http.method string - user_agent.original string - http.url string - net.peer.name string - net.peer.port int - http.request_content_length int - */ - - /* The following semantic conventions are not returned: - http.status_code This requires the response. See ClientResponse. - http.response_content_length This requires the response. See ClientResponse. - net.sock.family This requires the socket used. - net.sock.peer.addr This requires the socket used. - net.sock.peer.name This requires the socket used. - net.sock.peer.port This requires the socket used. - http.resend_count This is something outside of a single request. - net.protocol.name The value is the Request is ignored, and the go client will always use "http". - net.protocol.version The value in the Request is ignored, and the go client will always use 1.1 or 2.0. - */ - n := 3 // URL, peer name, proto, and method. - var h string - if req.URL != nil { - h = req.URL.Host - } - peer, p := firstHostPort(h, req.Header.Get("Host")) - port := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", p) - if port > 0 { - n++ - } - useragent := req.UserAgent() - if useragent != "" { - n++ - } - if req.ContentLength > 0 { - n++ - } - - attrs = slices.Grow(attrs, n) - attrs = append(attrs, c.method(req.Method)) - - var u string - if req.URL != nil { - // Remove any username/password info that may be in the URL. - userinfo := req.URL.User - req.URL.User = nil - u = req.URL.String() - // Restore any username/password info that was removed. - req.URL.User = userinfo - } - attrs = append(attrs, c.HTTPURLKey.String(u)) - - attrs = append(attrs, c.NetConv.PeerName(peer)) - if port > 0 { - attrs = append(attrs, c.NetConv.PeerPort(port)) - } - - if useragent != "" { - attrs = append(attrs, c.UserAgentOriginalKey.String(useragent)) - } - - if l := req.ContentLength; l > 0 { - attrs = append(attrs, c.HTTPRequestContentLengthKey.Int64(l)) - } - - return attrs -} - -// ClientRequestMetrics returns metric attributes for an HTTP request made by a client. The -// following attributes are always returned: "http.method", "net.peer.name". -// The following attributes are returned if the related values -// are defined in req: "net.peer.port". -func (c *httpConv) ClientRequestMetrics(req *http.Request) []attribute.KeyValue { - /* The following semantic conventions are returned if present: - http.method string - net.peer.name string - net.peer.port int - */ - - n := 2 // method, peer name. - var h string - if req.URL != nil { - h = req.URL.Host - } - peer, p := firstHostPort(h, req.Header.Get("Host")) - port := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", p) - if port > 0 { - n++ - } - - attrs := make([]attribute.KeyValue, 0, n) - attrs = append(attrs, c.method(req.Method), c.NetConv.PeerName(peer)) - - if port > 0 { - attrs = append(attrs, c.NetConv.PeerPort(port)) - } - - return attrs -} - -// ServerRequest returns attributes for an HTTP request received by a server. -// -// The server must be the primary server name if it is known. For example this -// would be the ServerName directive -// (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache -// server, and the server_name directive -// (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an -// nginx server. More generically, the primary server name would be the host -// header value that matches the default virtual host of an HTTP server. It -// should include the host identifier and if a port is used to route to the -// server that port identifier should be included as an appropriate port -// suffix. -// -// If the primary server name is not known, server should be an empty string. -// The req Host will be used to determine the server instead. -// -// The following attributes are always returned: "http.method", "http.scheme", -// "http.target", "net.host.name". The following attributes are returned if they -// related values are defined in req: "net.host.port", "net.sock.peer.addr", -// "net.sock.peer.port", "user_agent.original", "http.client_ip", -// "net.protocol.name", "net.protocol.version". -func (c *httpConv) ServerRequest(server string, req *http.Request, opts HTTPServerRequestOptions, attrs []attribute.KeyValue) []attribute.KeyValue { - /* The following semantic conventions are returned if present: - http.method string - http.scheme string - net.host.name string - net.host.port int - net.sock.peer.addr string - net.sock.peer.port int - user_agent.original string - http.client_ip string - net.protocol.name string Note: not set if the value is "http". - net.protocol.version string - http.target string Note: doesn't include the query parameter. - */ - - /* The following semantic conventions are not returned: - http.status_code This requires the response. - http.request_content_length This requires the len() of body, which can mutate it. - http.response_content_length This requires the response. - http.route This is not available. - net.sock.peer.name This would require a DNS lookup. - net.sock.host.addr The request doesn't have access to the underlying socket. - net.sock.host.port The request doesn't have access to the underlying socket. - - */ - n := 4 // Method, scheme, proto, and host name. - 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 { - n++ - } - peer, peerPort := splitHostPort(req.RemoteAddr) - if peer != "" { - n++ - if peerPort > 0 { - n++ - } - } - useragent := req.UserAgent() - if useragent != "" { - n++ - } - - // For client IP, use, in order: - // 1. The value passed in the options - // 2. The value in the X-Forwarded-For header - // 3. The peer address - clientIP := opts.HTTPClientIP - if clientIP == "" { - clientIP = serverClientIP(req.Header.Get("X-Forwarded-For")) - if clientIP == "" { - clientIP = peer - } - } - if clientIP != "" { - n++ - } - - var target string - if req.URL != nil { - target = req.URL.Path - if target != "" { - n++ - } - } - protoName, protoVersion := netProtocol(req.Proto) - if protoName != "" && protoName != "http" { - n++ - } - if protoVersion != "" { - n++ - } - - attrs = slices.Grow(attrs, n) - - attrs = append(attrs, c.method(req.Method)) - attrs = append(attrs, c.scheme(req.TLS != nil)) - attrs = append(attrs, c.NetConv.HostName(host)) - - if hostPort > 0 { - attrs = append(attrs, c.NetConv.HostPort(hostPort)) - } - - if peer != "" { - // The Go HTTP server sets RemoteAddr to "IP:port", this will not be a - // file-path that would be interpreted with a sock family. - attrs = append(attrs, c.NetConv.SockPeerAddr(peer)) - if peerPort > 0 { - attrs = append(attrs, c.NetConv.SockPeerPort(peerPort)) - } - } - - if useragent != "" { - attrs = append(attrs, c.UserAgentOriginalKey.String(useragent)) - } - - if clientIP != "" { - attrs = append(attrs, c.HTTPClientIPKey.String(clientIP)) - } - - if target != "" { - attrs = append(attrs, c.HTTPTargetKey.String(target)) - } - - if protoName != "" && protoName != "http" { - attrs = append(attrs, c.NetConv.NetProtocolName.String(protoName)) - } - if protoVersion != "" { - attrs = append(attrs, c.NetConv.NetProtocolVersion.String(protoVersion)) - } - - return attrs -} - -// ServerRequestMetrics returns metric attributes for an HTTP request received -// by a server. -// -// The server must be the primary server name if it is known. For example this -// would be the ServerName directive -// (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache -// server, and the server_name directive -// (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an -// nginx server. More generically, the primary server name would be the host -// header value that matches the default virtual host of an HTTP server. It -// should include the host identifier and if a port is used to route to the -// server that port identifier should be included as an appropriate port -// suffix. -// -// If the primary server name is not known, server should be an empty string. -// The req Host will be used to determine the server instead. -// -// The following attributes are always returned: "http.method", "http.scheme", -// "net.host.name". The following attributes are returned if they related -// values are defined in req: "net.host.port". -func (c *httpConv) ServerRequestMetrics(server string, req *http.Request) []attribute.KeyValue { - /* The following semantic conventions are returned if present: - http.scheme string - http.route string - http.method string - http.status_code int - net.host.name string - net.host.port int - net.protocol.name string Note: not set if the value is "http". - net.protocol.version string - */ - - n := 3 // Method, scheme, and host name. - 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 { - n++ - } - protoName, protoVersion := netProtocol(req.Proto) - if protoName != "" { - n++ - } - if protoVersion != "" { - n++ - } - - attrs := make([]attribute.KeyValue, 0, n) - - attrs = append(attrs, c.methodMetric(req.Method)) - attrs = append(attrs, c.scheme(req.TLS != nil)) - attrs = append(attrs, c.NetConv.HostName(host)) - - if hostPort > 0 { - attrs = append(attrs, c.NetConv.HostPort(hostPort)) - } - if protoName != "" { - attrs = append(attrs, c.NetConv.NetProtocolName.String(protoName)) - } - if protoVersion != "" { - attrs = append(attrs, c.NetConv.NetProtocolVersion.String(protoVersion)) - } - - return attrs -} - -func (c *httpConv) method(method string) attribute.KeyValue { - if method == "" { - return c.HTTPMethodKey.String(http.MethodGet) - } - return c.HTTPMethodKey.String(method) -} - -func (c *httpConv) methodMetric(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 c.HTTPMethodKey.String(method) -} - -func (c *httpConv) scheme(https bool) attribute.KeyValue { // nolint:revive - if https { - return c.HTTPSchemeHTTPS - } - return c.HTTPSchemeHTTP -} - -func serverClientIP(xForwardedFor string) string { - if idx := strings.Index(xForwardedFor, ","); idx >= 0 { - xForwardedFor = xForwardedFor[:idx] - } - return xForwardedFor -} - -func requiredHTTPPort(https bool, port int) int { // nolint:revive - if https { - if port > 0 && port != 443 { - return port - } - } else { - if port > 0 && port != 80 { - return port - } - } - return -1 -} - -// Return the request host and port from the first non-empty source. -func firstHostPort(source ...string) (host string, port int) { - for _, hostport := range source { - host, port = splitHostPort(hostport) - if host != "" || port > 0 { - break - } - } - return -} - -// ClientStatus returns a span status code and message for an HTTP status code -// value received by a client. -func (c *httpConv) ClientStatus(code int) (codes.Code, string) { - if code < 100 || code >= 600 { - return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code) - } - if code >= 400 { - return codes.Error, "" - } - return codes.Unset, "" -} - -// ServerStatus returns a span status code and message for an HTTP status code -// value returned by a server. Status codes in the 400-499 range are not -// returned as errors. -func (c *httpConv) ServerStatus(code int) (codes.Code, string) { - if code < 100 || code >= 600 { - return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code) - } - if code >= 500 { - return codes.Error, "" - } - return codes.Unset, "" -} diff --git a/instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconvutil/httpconv_test.go b/instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconvutil/httpconv_test.go deleted file mode 100644 index ce3a4eb3319..00000000000 --- a/instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconvutil/httpconv_test.go +++ /dev/null @@ -1,561 +0,0 @@ -// Code generated by gotmpl. DO NOT MODIFY. -// source: internal/shared/semconvutil/httpconv_test.go.tmpl - -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -package semconvutil - -import ( - "net/http" - "net/http/httptest" - "net/url" - "strconv" - "testing" - "time" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/codes" -) - -func TestHTTPClientResponse(t *testing.T) { - const stat, n = 201, 397 - resp := &http.Response{ - StatusCode: stat, - ContentLength: n, - } - got := HTTPClientResponse(resp, nil) - assert.Equal(t, 2, cap(got), "slice capacity") - assert.ElementsMatch(t, []attribute.KeyValue{ - attribute.Key("http.status_code").Int(stat), - attribute.Key("http.response_content_length").Int(n), - }, got) -} - -func TestHTTPSClientRequest(t *testing.T) { - req := &http.Request{ - Method: http.MethodGet, - URL: &url.URL{ - Scheme: "https", - Host: "127.0.0.1:443", - Path: "/resource", - }, - Proto: "HTTP/1.0", - ProtoMajor: 1, - ProtoMinor: 0, - } - - assert.ElementsMatch( - t, - []attribute.KeyValue{ - attribute.String("http.method", "GET"), - attribute.String("http.url", "https://127.0.0.1:443/resource"), - attribute.String("net.peer.name", "127.0.0.1"), - }, - HTTPClientRequest(req, nil), - ) -} - -func TestHTTPSClientRequestMetrics(t *testing.T) { - req := &http.Request{ - Method: http.MethodGet, - URL: &url.URL{ - Scheme: "https", - Host: "127.0.0.1:443", - Path: "/resource", - }, - Proto: "HTTP/1.0", - ProtoMajor: 1, - ProtoMinor: 0, - } - - assert.ElementsMatch( - t, - []attribute.KeyValue{ - attribute.String("http.method", "GET"), - attribute.String("net.peer.name", "127.0.0.1"), - }, - HTTPClientRequestMetrics(req), - ) -} - -func TestHTTPClientRequest(t *testing.T) { - const ( - user = "alice" - n = 128 - agent = "Go-http-client/1.1" - ) - req := &http.Request{ - Method: http.MethodGet, - URL: &url.URL{ - Scheme: "http", - Host: "127.0.0.1:8080", - Path: "/resource", - }, - Proto: "HTTP/1.0", - ProtoMajor: 1, - ProtoMinor: 0, - Header: http.Header{ - "User-Agent": []string{agent}, - }, - ContentLength: n, - } - req.SetBasicAuth(user, "pswrd") - - assert.ElementsMatch( - t, - []attribute.KeyValue{ - attribute.String("http.method", "GET"), - attribute.String("http.url", "http://127.0.0.1:8080/resource"), - attribute.String("net.peer.name", "127.0.0.1"), - attribute.Int("net.peer.port", 8080), - attribute.String("user_agent.original", agent), - attribute.Int("http.request_content_length", n), - }, - HTTPClientRequest(req, nil), - ) -} - -func TestHTTPClientRequestMetrics(t *testing.T) { - const ( - user = "alice" - n = 128 - agent = "Go-http-client/1.1" - ) - req := &http.Request{ - Method: http.MethodGet, - URL: &url.URL{ - Scheme: "http", - Host: "127.0.0.1:8080", - Path: "/resource", - }, - Proto: "HTTP/1.0", - ProtoMajor: 1, - ProtoMinor: 0, - Header: http.Header{ - "User-Agent": []string{agent}, - }, - ContentLength: n, - } - req.SetBasicAuth(user, "pswrd") - - assert.ElementsMatch( - t, - []attribute.KeyValue{ - attribute.String("http.method", "GET"), - attribute.String("net.peer.name", "127.0.0.1"), - attribute.Int("net.peer.port", 8080), - }, - HTTPClientRequestMetrics(req), - ) -} - -func TestHTTPClientRequestRequired(t *testing.T) { - req := new(http.Request) - var got []attribute.KeyValue - assert.NotPanics(t, func() { got = HTTPClientRequest(req, nil) }) - want := []attribute.KeyValue{ - attribute.String("http.method", "GET"), - attribute.String("http.url", ""), - attribute.String("net.peer.name", ""), - } - assert.Equal(t, want, got) -} - -func TestHTTPServerRequest(t *testing.T) { - for _, tt := range []struct { - name string - requestModifierFn func(r *http.Request) - httpServerRequestOpts HTTPServerRequestOptions - - wantClientIP string - }{ - { - name: "with a client IP from the network", - wantClientIP: "1.2.3.4", - }, - { - name: "with a client IP from x-forwarded-for header", - requestModifierFn: func(r *http.Request) { - r.Header.Add("X-Forwarded-For", "5.6.7.8") - }, - wantClientIP: "5.6.7.8", - }, - { - name: "with a client IP in options", - requestModifierFn: func(r *http.Request) { - r.Header.Add("X-Forwarded-For", "5.6.7.8") - }, - httpServerRequestOpts: HTTPServerRequestOptions{ - HTTPClientIP: "9.8.7.6", - }, - wantClientIP: "9.8.7.6", - }, - } { - t.Run(tt.name, func(t *testing.T) { - reqCh := make(chan *http.Request, 1) - handler := func(w http.ResponseWriter, r *http.Request) { - r.RemoteAddr = "1.2.3.4:5678" - reqCh <- r - w.WriteHeader(http.StatusOK) - } - - srv := httptest.NewServer(http.HandlerFunc(handler)) - defer srv.Close() - - srvURL, err := url.Parse(srv.URL) - require.NoError(t, err) - srvPort, err := strconv.ParseInt(srvURL.Port(), 10, 32) - require.NoError(t, err) - - req, err := http.NewRequest(http.MethodGet, srv.URL, nil) - require.NoError(t, err) - - if tt.requestModifierFn != nil { - tt.requestModifierFn(req) - } - - resp, err := srv.Client().Do(req) - require.NoError(t, err) - require.NoError(t, resp.Body.Close()) - - var got *http.Request - select { - case got = <-reqCh: - // All good - case <-time.After(5 * time.Second): - t.Fatal("Did not receive a signal in 5s") - } - - peer, peerPort := splitHostPort(got.RemoteAddr) - - const user = "alice" - got.SetBasicAuth(user, "pswrd") - - assert.ElementsMatch(t, - []attribute.KeyValue{ - attribute.String("http.method", "GET"), - attribute.String("http.scheme", "http"), - attribute.String("net.host.name", srvURL.Hostname()), - attribute.Int("net.host.port", int(srvPort)), - attribute.String("net.sock.peer.addr", peer), - attribute.Int("net.sock.peer.port", peerPort), - attribute.String("user_agent.original", "Go-http-client/1.1"), - attribute.String("http.client_ip", tt.wantClientIP), - attribute.String("net.protocol.version", "1.1"), - attribute.String("http.target", "/"), - }, - HTTPServerRequest("", got, tt.httpServerRequestOpts, nil)) - }) - } -} - -func TestHTTPServerRequestMetrics(t *testing.T) { - got := make(chan *http.Request, 1) - handler := func(w http.ResponseWriter, r *http.Request) { - got <- r - w.WriteHeader(http.StatusOK) - } - - srv := httptest.NewServer(http.HandlerFunc(handler)) - defer srv.Close() - - srvURL, err := url.Parse(srv.URL) - require.NoError(t, err) - srvPort, err := strconv.ParseInt(srvURL.Port(), 10, 32) - require.NoError(t, err) - - resp, err := srv.Client().Get(srv.URL) - require.NoError(t, err) - require.NoError(t, resp.Body.Close()) - - var req *http.Request - select { - case req = <-got: - // All good - case <-time.After(5 * time.Second): - t.Fatal("did not receive a signal in 5s") - } - - assert.ElementsMatch(t, - []attribute.KeyValue{ - attribute.String("http.method", "GET"), - attribute.String("http.scheme", "http"), - attribute.String("net.host.name", srvURL.Hostname()), - attribute.Int("net.host.port", int(srvPort)), - attribute.String("net.protocol.name", "http"), - attribute.String("net.protocol.version", "1.1"), - }, - HTTPServerRequestMetrics("", req)) -} - -func TestHTTPServerName(t *testing.T) { - req := new(http.Request) - var got []attribute.KeyValue - const ( - host = "test.semconv.server" - port = 8080 - ) - portStr := strconv.Itoa(port) - server := host + ":" + portStr - assert.NotPanics(t, func() { got = HTTPServerRequest(server, req, HTTPServerRequestOptions{}, nil) }) - assert.Contains(t, got, attribute.String("net.host.name", host)) - assert.Contains(t, got, attribute.Int("net.host.port", port)) - - req = &http.Request{Host: "alt.host.name:" + portStr} - // The server parameter does not include a port, ServerRequest should use - // the port in the request Host field. - assert.NotPanics(t, func() { got = HTTPServerRequest(host, req, HTTPServerRequestOptions{}, nil) }) - assert.Contains(t, got, attribute.String("net.host.name", host)) - assert.Contains(t, got, attribute.Int("net.host.port", port)) -} - -func TestHTTPServerRequestFailsGracefully(t *testing.T) { - req := new(http.Request) - var got []attribute.KeyValue - assert.NotPanics(t, func() { got = HTTPServerRequest("", req, HTTPServerRequestOptions{}, nil) }) - want := []attribute.KeyValue{ - attribute.String("http.method", "GET"), - attribute.String("http.scheme", "http"), - attribute.String("net.host.name", ""), - } - assert.ElementsMatch(t, want, got) -} - -func TestHTTPMethod(t *testing.T) { - assert.Equal(t, attribute.String("http.method", "POST"), hc.method("POST")) - assert.Equal(t, attribute.String("http.method", "GET"), hc.method("")) - assert.Equal(t, attribute.String("http.method", "garbage"), hc.method("garbage")) -} - -func TestHTTPScheme(t *testing.T) { - assert.Equal(t, attribute.String("http.scheme", "http"), hc.scheme(false)) - assert.Equal(t, attribute.String("http.scheme", "https"), hc.scheme(true)) -} - -func TestHTTPServerClientIP(t *testing.T) { - tests := []struct { - xForwardedFor string - want string - }{ - {"", ""}, - {"127.0.0.1", "127.0.0.1"}, - {"127.0.0.1,127.0.0.5", "127.0.0.1"}, - } - for _, test := range tests { - got := serverClientIP(test.xForwardedFor) - assert.Equal(t, test.want, got, test.xForwardedFor) - } -} - -func TestRequiredHTTPPort(t *testing.T) { - tests := []struct { - https bool - port int - want int - }{ - {true, 443, -1}, - {true, 80, 80}, - {true, 8081, 8081}, - {false, 443, 443}, - {false, 80, -1}, - {false, 8080, 8080}, - } - for _, test := range tests { - got := requiredHTTPPort(test.https, test.port) - assert.Equalf(t, test.want, got, "HTTPS: %t, Port: %d", test.https, test.port) - } -} - -func TestFirstHostPort(t *testing.T) { - host, port := "127.0.0.1", 8080 - hostport := "127.0.0.1:8080" - sources := [][]string{ - {hostport}, - {"", hostport}, - {"", "", hostport}, - {"", "", hostport, ""}, - {"", "", hostport, "127.0.0.3:80"}, - } - - for _, src := range sources { - h, p := firstHostPort(src...) - assert.Equal(t, host, h, "%+v", src) - assert.Equal(t, port, p, "%+v", src) - } -} - -func TestHTTPClientStatus(t *testing.T) { - tests := []struct { - code int - stat codes.Code - msg bool - }{ - {0, codes.Error, true}, - {http.StatusContinue, codes.Unset, false}, - {http.StatusSwitchingProtocols, codes.Unset, false}, - {http.StatusProcessing, codes.Unset, false}, - {http.StatusEarlyHints, codes.Unset, false}, - {http.StatusOK, codes.Unset, false}, - {http.StatusCreated, codes.Unset, false}, - {http.StatusAccepted, codes.Unset, false}, - {http.StatusNonAuthoritativeInfo, codes.Unset, false}, - {http.StatusNoContent, codes.Unset, false}, - {http.StatusResetContent, codes.Unset, false}, - {http.StatusPartialContent, codes.Unset, false}, - {http.StatusMultiStatus, codes.Unset, false}, - {http.StatusAlreadyReported, codes.Unset, false}, - {http.StatusIMUsed, codes.Unset, false}, - {http.StatusMultipleChoices, codes.Unset, false}, - {http.StatusMovedPermanently, codes.Unset, false}, - {http.StatusFound, codes.Unset, false}, - {http.StatusSeeOther, codes.Unset, false}, - {http.StatusNotModified, codes.Unset, false}, - {http.StatusUseProxy, codes.Unset, false}, - {306, codes.Unset, false}, - {http.StatusTemporaryRedirect, codes.Unset, false}, - {http.StatusPermanentRedirect, codes.Unset, false}, - {http.StatusBadRequest, codes.Error, false}, - {http.StatusUnauthorized, codes.Error, false}, - {http.StatusPaymentRequired, codes.Error, false}, - {http.StatusForbidden, codes.Error, false}, - {http.StatusNotFound, codes.Error, false}, - {http.StatusMethodNotAllowed, codes.Error, false}, - {http.StatusNotAcceptable, codes.Error, false}, - {http.StatusProxyAuthRequired, codes.Error, false}, - {http.StatusRequestTimeout, codes.Error, false}, - {http.StatusConflict, codes.Error, false}, - {http.StatusGone, codes.Error, false}, - {http.StatusLengthRequired, codes.Error, false}, - {http.StatusPreconditionFailed, codes.Error, false}, - {http.StatusRequestEntityTooLarge, codes.Error, false}, - {http.StatusRequestURITooLong, codes.Error, false}, - {http.StatusUnsupportedMediaType, codes.Error, false}, - {http.StatusRequestedRangeNotSatisfiable, codes.Error, false}, - {http.StatusExpectationFailed, codes.Error, false}, - {http.StatusTeapot, codes.Error, false}, - {http.StatusMisdirectedRequest, codes.Error, false}, - {http.StatusUnprocessableEntity, codes.Error, false}, - {http.StatusLocked, codes.Error, false}, - {http.StatusFailedDependency, codes.Error, false}, - {http.StatusTooEarly, codes.Error, false}, - {http.StatusUpgradeRequired, codes.Error, false}, - {http.StatusPreconditionRequired, codes.Error, false}, - {http.StatusTooManyRequests, codes.Error, false}, - {http.StatusRequestHeaderFieldsTooLarge, codes.Error, false}, - {http.StatusUnavailableForLegalReasons, codes.Error, false}, - {499, codes.Error, false}, - {http.StatusInternalServerError, codes.Error, false}, - {http.StatusNotImplemented, codes.Error, false}, - {http.StatusBadGateway, codes.Error, false}, - {http.StatusServiceUnavailable, codes.Error, false}, - {http.StatusGatewayTimeout, codes.Error, false}, - {http.StatusHTTPVersionNotSupported, codes.Error, false}, - {http.StatusVariantAlsoNegotiates, codes.Error, false}, - {http.StatusInsufficientStorage, codes.Error, false}, - {http.StatusLoopDetected, codes.Error, false}, - {http.StatusNotExtended, codes.Error, false}, - {http.StatusNetworkAuthenticationRequired, codes.Error, false}, - {600, codes.Error, true}, - } - - for _, test := range tests { - t.Run(strconv.Itoa(test.code), func(t *testing.T) { - c, msg := HTTPClientStatus(test.code) - assert.Equal(t, test.stat, c) - if test.msg && msg == "" { - t.Errorf("expected non-empty message for %d", test.code) - } else if !test.msg && msg != "" { - t.Errorf("expected empty message for %d, got: %s", test.code, msg) - } - }) - } -} - -func TestHTTPServerStatus(t *testing.T) { - tests := []struct { - code int - stat codes.Code - msg bool - }{ - {0, codes.Error, true}, - {http.StatusContinue, codes.Unset, false}, - {http.StatusSwitchingProtocols, codes.Unset, false}, - {http.StatusProcessing, codes.Unset, false}, - {http.StatusEarlyHints, codes.Unset, false}, - {http.StatusOK, codes.Unset, false}, - {http.StatusCreated, codes.Unset, false}, - {http.StatusAccepted, codes.Unset, false}, - {http.StatusNonAuthoritativeInfo, codes.Unset, false}, - {http.StatusNoContent, codes.Unset, false}, - {http.StatusResetContent, codes.Unset, false}, - {http.StatusPartialContent, codes.Unset, false}, - {http.StatusMultiStatus, codes.Unset, false}, - {http.StatusAlreadyReported, codes.Unset, false}, - {http.StatusIMUsed, codes.Unset, false}, - {http.StatusMultipleChoices, codes.Unset, false}, - {http.StatusMovedPermanently, codes.Unset, false}, - {http.StatusFound, codes.Unset, false}, - {http.StatusSeeOther, codes.Unset, false}, - {http.StatusNotModified, codes.Unset, false}, - {http.StatusUseProxy, codes.Unset, false}, - {306, codes.Unset, false}, - {http.StatusTemporaryRedirect, codes.Unset, false}, - {http.StatusPermanentRedirect, codes.Unset, false}, - {http.StatusBadRequest, codes.Unset, false}, - {http.StatusUnauthorized, codes.Unset, false}, - {http.StatusPaymentRequired, codes.Unset, false}, - {http.StatusForbidden, codes.Unset, false}, - {http.StatusNotFound, codes.Unset, false}, - {http.StatusMethodNotAllowed, codes.Unset, false}, - {http.StatusNotAcceptable, codes.Unset, false}, - {http.StatusProxyAuthRequired, codes.Unset, false}, - {http.StatusRequestTimeout, codes.Unset, false}, - {http.StatusConflict, codes.Unset, false}, - {http.StatusGone, codes.Unset, false}, - {http.StatusLengthRequired, codes.Unset, false}, - {http.StatusPreconditionFailed, codes.Unset, false}, - {http.StatusRequestEntityTooLarge, codes.Unset, false}, - {http.StatusRequestURITooLong, codes.Unset, false}, - {http.StatusUnsupportedMediaType, codes.Unset, false}, - {http.StatusRequestedRangeNotSatisfiable, codes.Unset, false}, - {http.StatusExpectationFailed, codes.Unset, false}, - {http.StatusTeapot, codes.Unset, false}, - {http.StatusMisdirectedRequest, codes.Unset, false}, - {http.StatusUnprocessableEntity, codes.Unset, false}, - {http.StatusLocked, codes.Unset, false}, - {http.StatusFailedDependency, codes.Unset, false}, - {http.StatusTooEarly, codes.Unset, false}, - {http.StatusUpgradeRequired, codes.Unset, false}, - {http.StatusPreconditionRequired, codes.Unset, false}, - {http.StatusTooManyRequests, codes.Unset, false}, - {http.StatusRequestHeaderFieldsTooLarge, codes.Unset, false}, - {http.StatusUnavailableForLegalReasons, codes.Unset, false}, - {499, codes.Unset, false}, - {http.StatusInternalServerError, codes.Error, false}, - {http.StatusNotImplemented, codes.Error, false}, - {http.StatusBadGateway, codes.Error, false}, - {http.StatusServiceUnavailable, codes.Error, false}, - {http.StatusGatewayTimeout, codes.Error, false}, - {http.StatusHTTPVersionNotSupported, codes.Error, false}, - {http.StatusVariantAlsoNegotiates, codes.Error, false}, - {http.StatusInsufficientStorage, codes.Error, false}, - {http.StatusLoopDetected, codes.Error, false}, - {http.StatusNotExtended, codes.Error, false}, - {http.StatusNetworkAuthenticationRequired, codes.Error, false}, - {600, codes.Error, true}, - } - - for _, test := range tests { - c, msg := HTTPServerStatus(test.code) - assert.Equal(t, test.stat, c) - if test.msg && msg == "" { - t.Errorf("expected non-empty message for %d", test.code) - } else if !test.msg && msg != "" { - t.Errorf("expected empty message for %d, got: %s", test.code, msg) - } - } -} diff --git a/instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconvutil/netconv.go b/instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconvutil/netconv.go deleted file mode 100644 index 2bd9bf14193..00000000000 --- a/instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconvutil/netconv.go +++ /dev/null @@ -1,214 +0,0 @@ -// Code generated by gotmpl. DO NOT MODIFY. -// source: internal/shared/semconvutil/netconv.go.tmpl - -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -package semconvutil // import "go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconvutil" - -import ( - "net" - "strconv" - "strings" - - "go.opentelemetry.io/otel/attribute" - semconv "go.opentelemetry.io/otel/semconv/v1.20.0" -) - -// NetTransport returns a trace attribute describing the transport protocol of the -// passed network. See the net.Dial for information about acceptable network -// values. -func NetTransport(network string) attribute.KeyValue { - return nc.Transport(network) -} - -// netConv are the network semantic convention attributes defined for a version -// of the OpenTelemetry specification. -type netConv struct { - NetHostNameKey attribute.Key - NetHostPortKey attribute.Key - NetPeerNameKey attribute.Key - NetPeerPortKey attribute.Key - NetProtocolName attribute.Key - NetProtocolVersion attribute.Key - NetSockFamilyKey attribute.Key - NetSockPeerAddrKey attribute.Key - NetSockPeerPortKey attribute.Key - NetSockHostAddrKey attribute.Key - NetSockHostPortKey attribute.Key - NetTransportOther attribute.KeyValue - NetTransportTCP attribute.KeyValue - NetTransportUDP attribute.KeyValue - NetTransportInProc attribute.KeyValue -} - -var nc = &netConv{ - NetHostNameKey: semconv.NetHostNameKey, - NetHostPortKey: semconv.NetHostPortKey, - NetPeerNameKey: semconv.NetPeerNameKey, - NetPeerPortKey: semconv.NetPeerPortKey, - NetProtocolName: semconv.NetProtocolNameKey, - NetProtocolVersion: semconv.NetProtocolVersionKey, - NetSockFamilyKey: semconv.NetSockFamilyKey, - NetSockPeerAddrKey: semconv.NetSockPeerAddrKey, - NetSockPeerPortKey: semconv.NetSockPeerPortKey, - NetSockHostAddrKey: semconv.NetSockHostAddrKey, - NetSockHostPortKey: semconv.NetSockHostPortKey, - NetTransportOther: semconv.NetTransportOther, - NetTransportTCP: semconv.NetTransportTCP, - NetTransportUDP: semconv.NetTransportUDP, - NetTransportInProc: semconv.NetTransportInProc, -} - -func (c *netConv) Transport(network string) attribute.KeyValue { - switch network { - case "tcp", "tcp4", "tcp6": - return c.NetTransportTCP - case "udp", "udp4", "udp6": - return c.NetTransportUDP - case "unix", "unixgram", "unixpacket": - return c.NetTransportInProc - default: - // "ip:*", "ip4:*", and "ip6:*" all are considered other. - return c.NetTransportOther - } -} - -// Host returns attributes for a network host address. -func (c *netConv) Host(address string) []attribute.KeyValue { - h, p := splitHostPort(address) - var n int - if h != "" { - n++ - if p > 0 { - n++ - } - } - - if n == 0 { - return nil - } - - attrs := make([]attribute.KeyValue, 0, n) - attrs = append(attrs, c.HostName(h)) - if p > 0 { - attrs = append(attrs, c.HostPort(p)) - } - return attrs -} - -func (c *netConv) HostName(name string) attribute.KeyValue { - return c.NetHostNameKey.String(name) -} - -func (c *netConv) HostPort(port int) attribute.KeyValue { - return c.NetHostPortKey.Int(port) -} - -func family(network, address string) string { - switch network { - case "unix", "unixgram", "unixpacket": - return "unix" - default: - if ip := net.ParseIP(address); ip != nil { - if ip.To4() == nil { - return "inet6" - } - return "inet" - } - } - return "" -} - -// Peer returns attributes for a network peer address. -func (c *netConv) Peer(address string) []attribute.KeyValue { - h, p := splitHostPort(address) - var n int - if h != "" { - n++ - if p > 0 { - n++ - } - } - - if n == 0 { - return nil - } - - attrs := make([]attribute.KeyValue, 0, n) - attrs = append(attrs, c.PeerName(h)) - if p > 0 { - attrs = append(attrs, c.PeerPort(p)) - } - return attrs -} - -func (c *netConv) PeerName(name string) attribute.KeyValue { - return c.NetPeerNameKey.String(name) -} - -func (c *netConv) PeerPort(port int) attribute.KeyValue { - return c.NetPeerPortKey.Int(port) -} - -func (c *netConv) SockPeerAddr(addr string) attribute.KeyValue { - return c.NetSockPeerAddrKey.String(addr) -} - -func (c *netConv) SockPeerPort(port int) attribute.KeyValue { - return c.NetSockPeerPortKey.Int(port) -} - -// splitHostPort splits a network address hostport of the form "host", -// "host%zone", "[host]", "[host%zone], "host:port", "host%zone:port", -// "[host]:port", "[host%zone]:port", or ":port" into host or host%zone and -// port. -// -// An empty host is returned if it is not provided or unparsable. A negative -// port is returned if it is not provided or unparsable. -func splitHostPort(hostport string) (host string, port int) { - port = -1 - - if strings.HasPrefix(hostport, "[") { - addrEnd := strings.LastIndex(hostport, "]") - if addrEnd < 0 { - // Invalid hostport. - return - } - if i := strings.LastIndex(hostport[addrEnd:], ":"); i < 0 { - host = hostport[1:addrEnd] - return - } - } else { - if i := strings.LastIndex(hostport, ":"); i < 0 { - host = hostport - return - } - } - - host, pStr, err := net.SplitHostPort(hostport) - if err != nil { - return - } - - p, err := strconv.ParseUint(pStr, 10, 16) - if err != nil { - return - } - return host, int(p) // nolint: gosec // Bitsize checked to be 16 above. -} - -func netProtocol(proto string) (name string, version string) { - name, version, _ = strings.Cut(proto, "/") - switch name { - case "HTTP": - name = "http" - case "QUIC": - name = "quic" - case "SPDY": - name = "spdy" - default: - name = strings.ToLower(name) - } - return name, version -} diff --git a/instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconvutil/netconv_test.go b/instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconvutil/netconv_test.go deleted file mode 100644 index 4f3c1540c13..00000000000 --- a/instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconvutil/netconv_test.go +++ /dev/null @@ -1,200 +0,0 @@ -// Code generated by gotmpl. DO NOT MODIFY. -// source: internal/shared/semconvutil/netconv_test.go.tmpl - -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -package semconvutil - -import ( - "testing" - - "github.com/stretchr/testify/assert" - - "go.opentelemetry.io/otel/attribute" -) - -const ( - addr = "127.0.0.1" - port = 1834 -) - -func TestNetTransport(t *testing.T) { - transports := map[string]attribute.KeyValue{ - "tcp": attribute.String("net.transport", "ip_tcp"), - "tcp4": attribute.String("net.transport", "ip_tcp"), - "tcp6": attribute.String("net.transport", "ip_tcp"), - "udp": attribute.String("net.transport", "ip_udp"), - "udp4": attribute.String("net.transport", "ip_udp"), - "udp6": attribute.String("net.transport", "ip_udp"), - "unix": attribute.String("net.transport", "inproc"), - "unixgram": attribute.String("net.transport", "inproc"), - "unixpacket": attribute.String("net.transport", "inproc"), - "ip:1": attribute.String("net.transport", "other"), - "ip:icmp": attribute.String("net.transport", "other"), - "ip4:proto": attribute.String("net.transport", "other"), - "ip6:proto": attribute.String("net.transport", "other"), - } - - for network, want := range transports { - assert.Equal(t, want, NetTransport(network)) - } -} - -func TestNetHost(t *testing.T) { - testAddrs(t, []addrTest{ - {address: "", expected: nil}, - {address: "192.0.0.1", expected: []attribute.KeyValue{ - nc.HostName("192.0.0.1"), - }}, - {address: "192.0.0.1:9090", expected: []attribute.KeyValue{ - nc.HostName("192.0.0.1"), - nc.HostPort(9090), - }}, - }, nc.Host) -} - -func TestNetHostName(t *testing.T) { - expected := attribute.Key("net.host.name").String(addr) - assert.Equal(t, expected, nc.HostName(addr)) -} - -func TestNetHostPort(t *testing.T) { - expected := attribute.Key("net.host.port").Int(port) - assert.Equal(t, expected, nc.HostPort(port)) -} - -func TestNetPeer(t *testing.T) { - testAddrs(t, []addrTest{ - {address: "", expected: nil}, - {address: "example.com", expected: []attribute.KeyValue{ - nc.PeerName("example.com"), - }}, - {address: "/tmp/file", expected: []attribute.KeyValue{ - nc.PeerName("/tmp/file"), - }}, - {address: "192.0.0.1", expected: []attribute.KeyValue{ - nc.PeerName("192.0.0.1"), - }}, - {address: ":9090", expected: nil}, - {address: "192.0.0.1:9090", expected: []attribute.KeyValue{ - nc.PeerName("192.0.0.1"), - nc.PeerPort(9090), - }}, - }, nc.Peer) -} - -func TestNetPeerName(t *testing.T) { - expected := attribute.Key("net.peer.name").String(addr) - assert.Equal(t, expected, nc.PeerName(addr)) -} - -func TestNetPeerPort(t *testing.T) { - expected := attribute.Key("net.peer.port").Int(port) - assert.Equal(t, expected, nc.PeerPort(port)) -} - -func TestNetSockPeerName(t *testing.T) { - expected := attribute.Key("net.sock.peer.addr").String(addr) - assert.Equal(t, expected, nc.SockPeerAddr(addr)) -} - -func TestNetSockPeerPort(t *testing.T) { - expected := attribute.Key("net.sock.peer.port").Int(port) - assert.Equal(t, expected, nc.SockPeerPort(port)) -} - -func TestNetFamily(t *testing.T) { - tests := []struct { - network string - address string - expect string - }{ - {"", "", ""}, - {"unix", "", "unix"}, - {"unix", "gibberish", "unix"}, - {"unixgram", "", "unix"}, - {"unixgram", "gibberish", "unix"}, - {"unixpacket", "gibberish", "unix"}, - {"tcp", "123.0.2.8", "inet"}, - {"tcp", "gibberish", ""}, - {"", "123.0.2.8", "inet"}, - {"", "gibberish", ""}, - {"tcp", "fe80::1", "inet6"}, - {"", "fe80::1", "inet6"}, - } - - for _, test := range tests { - got := family(test.network, test.address) - assert.Equal(t, test.expect, got, test.network+"/"+test.address) - } -} - -func TestSplitHostPort(t *testing.T) { - tests := []struct { - hostport string - host string - port int - }{ - {"", "", -1}, - {":8080", "", 8080}, - {"127.0.0.1", "127.0.0.1", -1}, - {"www.example.com", "www.example.com", -1}, - {"127.0.0.1%25en0", "127.0.0.1%25en0", -1}, - {"[]", "", -1}, // Ensure this doesn't panic. - {"[fe80::1", "", -1}, - {"[fe80::1]", "fe80::1", -1}, - {"[fe80::1%25en0]", "fe80::1%25en0", -1}, - {"[fe80::1]:8080", "fe80::1", 8080}, - {"[fe80::1]::", "", -1}, // Too many colons. - {"127.0.0.1:", "127.0.0.1", -1}, - {"127.0.0.1:port", "127.0.0.1", -1}, - {"127.0.0.1:8080", "127.0.0.1", 8080}, - {"www.example.com:8080", "www.example.com", 8080}, - {"127.0.0.1%25en0:8080", "127.0.0.1%25en0", 8080}, - } - - for _, test := range tests { - h, p := splitHostPort(test.hostport) - assert.Equal(t, test.host, h, test.hostport) - assert.Equal(t, test.port, p, test.hostport) - } -} - -type addrTest struct { - address string - expected []attribute.KeyValue -} - -func testAddrs(t *testing.T, tests []addrTest, f func(string) []attribute.KeyValue) { - t.Helper() - - for _, test := range tests { - got := f(test.address) - assert.Equal(t, cap(test.expected), cap(got), "slice capacity") - assert.ElementsMatch(t, test.expected, got, test.address) - } -} - -func TestNetProtocol(t *testing.T) { - type testCase struct { - name, version string - } - tests := map[string]testCase{ - "HTTP/1.0": {name: "http", version: "1.0"}, - "HTTP/1.1": {name: "http", version: "1.1"}, - "HTTP/2": {name: "http", version: "2"}, - "HTTP/3": {name: "http", version: "3"}, - "SPDY": {name: "spdy"}, - "SPDY/2": {name: "spdy", version: "2"}, - "QUIC": {name: "quic"}, - "unknown/proto/2": {name: "unknown", version: "proto/2"}, - "other": {name: "other"}, - } - - for proto, want := range tests { - name, version := netProtocol(proto) - assert.Equal(t, want.name, name) - assert.Equal(t, want.version, version) - } -} diff --git a/instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconv/env.go b/instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconv/env.go index c66be2cb39d..69423c3fd68 100644 --- a/instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconv/env.go +++ b/instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconv/env.go @@ -10,7 +10,6 @@ import ( "context" "fmt" "net/http" - "os" "strings" "sync" @@ -33,14 +32,6 @@ type ResponseTelemetry struct { } type HTTPServer struct { - duplicate bool - - // Old metrics - requestBytesCounter metric.Int64Counter - responseBytesCounter metric.Int64Counter - serverLatencyMeasure metric.Float64Histogram - - // New metrics requestBodySizeHistogram httpconv.ServerRequestBodySize responseBodySizeHistogram httpconv.ServerResponseBodySize requestDurationHistogram httpconv.ServerRequestDuration @@ -63,20 +54,10 @@ type HTTPServer struct { // If the primary server name is not known, server should be an empty string. // The req Host will be used to determine the server instead. func (s HTTPServer) RequestTraceAttrs(server string, req *http.Request, opts RequestTraceAttrsOpts) []attribute.KeyValue { - attrs := CurrentHTTPServer{}.RequestTraceAttrs(server, req, opts) - if s.duplicate { - return OldHTTPServer{}.RequestTraceAttrs(server, req, attrs) - } - return attrs + return CurrentHTTPServer{}.RequestTraceAttrs(server, req, opts) } func (s HTTPServer) NetworkTransportAttr(network string) []attribute.KeyValue { - if s.duplicate { - return []attribute.KeyValue{ - OldHTTPServer{}.NetworkTransportAttr(network), - CurrentHTTPServer{}.NetworkTransportAttr(network), - } - } return []attribute.KeyValue{ CurrentHTTPServer{}.NetworkTransportAttr(network), } @@ -86,11 +67,7 @@ func (s HTTPServer) NetworkTransportAttr(network string) []attribute.KeyValue { // // If any of the fields in the ResponseTelemetry are not set the attribute will be omitted. func (s HTTPServer) ResponseTraceAttrs(resp ResponseTelemetry) []attribute.KeyValue { - attrs := CurrentHTTPServer{}.ResponseTraceAttrs(resp) - if s.duplicate { - return OldHTTPServer{}.ResponseTraceAttrs(resp, attrs) - } - return attrs + return CurrentHTTPServer{}.ResponseTraceAttrs(resp) } // Route returns the attribute for the route. @@ -156,18 +133,6 @@ func (s HTTPServer) RecordMetrics(ctx context.Context, md ServerMetricData) { s.requestDurationHistogram.Inst().Record(ctx, md.ElapsedTime/1000.0, o) *recordOpts = (*recordOpts)[:0] metricRecordOptionPool.Put(recordOpts) - - if s.duplicate && 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) - } } // hasOptIn returns true if the comma-separated version string contains the @@ -182,11 +147,7 @@ func hasOptIn(version, optIn string) bool { } func NewHTTPServer(meter metric.Meter) HTTPServer { - env := strings.ToLower(os.Getenv(OTelSemConvStabilityOptIn)) - duplicate := hasOptIn(env, "http/dup") - server := HTTPServer{ - duplicate: duplicate, - } + server := HTTPServer{} var err error server.requestBodySizeHistogram, err = httpconv.NewServerRequestBodySize(meter) @@ -203,32 +164,16 @@ func NewHTTPServer(meter metric.Meter) HTTPServer { ), ) handleErr(err) - - if duplicate { - server.requestBytesCounter, server.responseBytesCounter, server.serverLatencyMeasure = OldHTTPServer{}.createMeasures(meter) - } return server } type HTTPClient struct { - duplicate bool - - // old metrics - requestBytesCounter metric.Int64Counter - responseBytesCounter metric.Int64Counter - latencyMeasure metric.Float64Histogram - - // new metrics requestBodySize httpconv.ClientRequestBodySize requestDuration httpconv.ClientRequestDuration } func NewHTTPClient(meter metric.Meter) HTTPClient { - env := strings.ToLower(os.Getenv(OTelSemConvStabilityOptIn)) - duplicate := hasOptIn(env, "http/dup") - client := HTTPClient{ - duplicate: duplicate, - } + client := HTTPClient{} var err error client.requestBodySize, err = httpconv.NewClientRequestBodySize(meter) @@ -240,29 +185,17 @@ func NewHTTPClient(meter metric.Meter) HTTPClient { ) handleErr(err) - if duplicate { - client.requestBytesCounter, client.responseBytesCounter, client.latencyMeasure = OldHTTPClient{}.createMeasures(meter) - } - return client } // RequestTraceAttrs returns attributes for an HTTP request made by a client. func (c HTTPClient) RequestTraceAttrs(req *http.Request) []attribute.KeyValue { - attrs := CurrentHTTPClient{}.RequestTraceAttrs(req) - if c.duplicate { - return OldHTTPClient{}.RequestTraceAttrs(req, attrs) - } - return attrs + return CurrentHTTPClient{}.RequestTraceAttrs(req) } // ResponseTraceAttrs returns metric attributes for an HTTP request made by a client. func (c HTTPClient) ResponseTraceAttrs(resp *http.Response) []attribute.KeyValue { - attrs := CurrentHTTPClient{}.ResponseTraceAttrs(resp) - if c.duplicate { - return OldHTTPClient{}.ResponseTraceAttrs(resp, attrs) - } - return attrs + return CurrentHTTPClient{}.ResponseTraceAttrs(resp) } func (c HTTPClient) Status(code int) (codes.Code, string) { @@ -302,42 +235,14 @@ func (c HTTPClient) MetricOptions(ma MetricAttributes) map[string]MetricOpts { addOptions: set, } - if c.duplicate { - attributes := OldHTTPClient{}.MetricAttributes(ma.Req, ma.StatusCode, ma.AdditionalAttributes) - set := metric.WithAttributeSet(attribute.NewSet(attributes...)) - opts["old"] = MetricOpts{ - measurement: set, - addOptions: set, - } - } - return opts } func (s HTTPClient) RecordMetrics(ctx context.Context, md MetricData, opts map[string]MetricOpts) { s.requestBodySize.Inst().Record(ctx, md.RequestSize, opts["new"].MeasurementOption()) s.requestDuration.Inst().Record(ctx, md.ElapsedTime/1000, opts["new"].MeasurementOption()) - - if s.duplicate { - s.requestBytesCounter.Add(ctx, md.RequestSize, opts["old"].AddOptions()) - s.latencyMeasure.Record(ctx, md.ElapsedTime, opts["old"].MeasurementOption()) - } -} - -func (s HTTPClient) RecordResponseSize(ctx context.Context, responseData int64, opts map[string]MetricOpts) { - if s.responseBytesCounter == nil { - // This will happen if an HTTPClient{} is used instead of NewHTTPClient(). - return - } - - s.responseBytesCounter.Add(ctx, responseData, opts["old"].AddOptions()) } func (s HTTPClient) TraceAttributes(host string) []attribute.KeyValue { - attrs := CurrentHTTPClient{}.TraceAttributes(host) - if s.duplicate { - return OldHTTPClient{}.TraceAttributes(host, attrs) - } - - return attrs + return CurrentHTTPClient{}.TraceAttributes(host) } diff --git a/instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconv/env_test.go b/instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconv/env_test.go index 45b520854dd..11388bb8324 100644 --- a/instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconv/env_test.go +++ b/instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconv/env_test.go @@ -54,7 +54,6 @@ func TestHTTPServerDoesNotPanic(t *testing.T) { func TestServerNetworkTransportAttr(t *testing.T) { for _, tt := range []struct { name string - optinVal string network string wantAttributes []attribute.KeyValue @@ -67,29 +66,8 @@ func TestServerNetworkTransportAttr(t *testing.T) { attribute.String("network.transport", "tcp"), }, }, - { - name: "without a dup optin", - optinVal: "http/dup", - network: "tcp", - - wantAttributes: []attribute.KeyValue{ - attribute.String("net.transport", "ip_tcp"), - attribute.String("network.transport", "tcp"), - }, - }, - { - name: "with mixed categories", - optinVal: "http/dup,database", - network: "tcp", - - wantAttributes: []attribute.KeyValue{ - attribute.String("net.transport", "ip_tcp"), - attribute.String("network.transport", "tcp"), - }, - }, } { t.Run(tt.name, func(t *testing.T) { - t.Setenv(OTelSemConvStabilityOptIn, tt.optinVal) s := NewHTTPServer(nil) assert.Equal(t, tt.wantAttributes, s.NetworkTransportAttr(tt.network)) @@ -124,7 +102,6 @@ func TestHTTPClientDoesNotPanic(t *testing.T) { Req: req, StatusCode: 200, }) - tt.client.RecordResponseSize(context.Background(), 40, opts) tt.client.RecordMetrics(context.Background(), MetricData{ RequestSize: 20, ElapsedTime: 1, @@ -137,7 +114,6 @@ func TestHTTPClientDoesNotPanic(t *testing.T) { func TestHTTPClientTraceAttributes(t *testing.T) { for _, tt := range []struct { name string - optinVal string wantAttributes []attribute.KeyValue }{ @@ -148,28 +124,8 @@ func TestHTTPClientTraceAttributes(t *testing.T) { attribute.String("server.address", "example.com"), }, }, - { - name: "with optin set to duplicate", - optinVal: "http/dup", - - wantAttributes: []attribute.KeyValue{ - attribute.String("server.address", "example.com"), - attribute.String("net.host.name", "example.com"), - }, - }, - { - name: "with mixed categories", - optinVal: "http/dup,database", - - wantAttributes: []attribute.KeyValue{ - attribute.String("server.address", "example.com"), - attribute.String("net.host.name", "example.com"), - }, - }, } { t.Run(tt.name, func(t *testing.T) { - t.Setenv(OTelSemConvStabilityOptIn, tt.optinVal) - c := NewHTTPClient(nil) a := c.TraceAttributes("example.com") assert.Equal(t, tt.wantAttributes, a) @@ -180,7 +136,6 @@ func TestHTTPClientTraceAttributes(t *testing.T) { func TestClientTraceAttributes(t *testing.T) { for _, tt := range []struct { name string - optinVal string host string wantAttributes []attribute.KeyValue @@ -193,29 +148,8 @@ func TestClientTraceAttributes(t *testing.T) { attribute.String("server.address", "example.com"), }, }, - { - name: "without a dup optin", - optinVal: "http/dup", - host: "example.com", - - wantAttributes: []attribute.KeyValue{ - attribute.String("server.address", "example.com"), - attribute.String("net.host.name", "example.com"), - }, - }, - { - name: "with mixed categories", - optinVal: "http/dup", - host: "example.com", - - wantAttributes: []attribute.KeyValue{ - attribute.String("server.address", "example.com"), - attribute.String("net.host.name", "example.com"), - }, - }, } { t.Run(tt.name, func(t *testing.T) { - t.Setenv(OTelSemConvStabilityOptIn, tt.optinVal) s := NewHTTPClient(nil) assert.Equal(t, tt.wantAttributes, s.TraceAttributes(tt.host)) diff --git a/instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconv/gen.go b/instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconv/gen.go index 9f15165d572..9fe15dfb66d 100644 --- a/instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconv/gen.go +++ b/instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconv/gen.go @@ -13,4 +13,3 @@ package semconv // import "go.opentelemetry.io/contrib/instrumentation/github.co //go:generate gotmpl --body=../../../../../../../internal/shared/semconv/httpconvtest_test.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin\" }" --out=httpconvtest_test.go //go:generate gotmpl --body=../../../../../../../internal/shared/semconv/util.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin\" }" --out=util.go //go:generate gotmpl --body=../../../../../../../internal/shared/semconv/util_test.go.tmpl "--data={}" --out=util_test.go -//go:generate gotmpl --body=../../../../../../../internal/shared/semconv/v1.20.0.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin\" }" --out=v1.20.0.go diff --git a/instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconv/httpconvtest_test.go b/instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconv/httpconvtest_test.go index 89281c4ffec..84a9d800311 100644 --- a/instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconv/httpconvtest_test.go +++ b/instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconv/httpconvtest_test.go @@ -28,7 +28,6 @@ import ( ) func TestNewTraceRequest(t *testing.T) { - t.Setenv("OTEL_SEMCONV_STABILITY_OPT_IN", "http/dup") serv := semconv.NewHTTPServer(nil) want := func(req testServerReq) []attribute.KeyValue { return []attribute.KeyValue{ @@ -42,16 +41,6 @@ func TestNewTraceRequest(t *testing.T) { attribute.String("client.address", req.clientIP), attribute.String("network.protocol.version", "1.1"), attribute.String("url.path", "/"), - attribute.String("http.method", "GET"), - attribute.String("http.scheme", "http"), - attribute.String("net.host.name", req.hostname), - attribute.Int("net.host.port", req.serverPort), - attribute.String("net.sock.peer.addr", req.peerAddr), - attribute.Int("net.sock.peer.port", req.peerPort), - attribute.String("user_agent.original", "Go-http-client/1.1"), - attribute.String("http.client_ip", req.clientIP), - attribute.String("net.protocol.version", "1.1"), - attribute.String("http.target", "/"), } } testTraceRequest(t, serv, want) @@ -174,13 +163,11 @@ func TestNewServerRecordMetrics(t *testing.T) { 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, + name: "No Meter", serverFunc: func(metric.MeterProvider) semconv.HTTPServer { return semconv.NewHTTPServer(nil) }, @@ -189,8 +176,7 @@ func TestNewServerRecordMetrics(t *testing.T) { }, }, { - name: "No environment variable set, but with Meter", - setEnv: false, + name: "With Meter", serverFunc: func(mp metric.MeterProvider) semconv.HTTPServer { return semconv.NewHTTPServer(mp.Meter("test")) }, @@ -202,36 +188,10 @@ func TestNewServerRecordMetrics(t *testing.T) { metricdatatest.AssertEqual(t, expectedCurrentScopeMetric, 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, expectedOldScopeMetric, 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)) @@ -312,7 +272,6 @@ func TestNewTraceResponse(t *testing.T) { } func TestNewTraceRequest_Client(t *testing.T) { - t.Setenv("OTEL_SEMCONV_STABILITY_OPT_IN", "http/dup") body := strings.NewReader("Hello, world!") url := "https://example.com:8888/foo/bar?stuff=morestuff" req := httptest.NewRequest("pOST", url, body) @@ -321,29 +280,22 @@ func TestNewTraceRequest_Client(t *testing.T) { want := []attribute.KeyValue{ attribute.String("http.request.method", "POST"), attribute.String("http.request.method_original", "pOST"), - attribute.String("http.method", "pOST"), attribute.String("url.full", url), - attribute.String("http.url", url), attribute.String("server.address", "example.com"), attribute.Int("server.port", 8888), attribute.String("network.protocol.version", "1.1"), - attribute.String("net.peer.name", "example.com"), - attribute.Int("net.peer.port", 8888), - attribute.String("user_agent.original", "go-test-agent"), - attribute.Int("http.request_content_length", 13), } client := semconv.NewHTTPClient(nil) assert.ElementsMatch(t, want, client.RequestTraceAttrs(req)) } func TestNewTraceResponse_Client(t *testing.T) { - t.Setenv("OTEL_SEMCONV_STABILITY_OPT_IN", "http/dup") testcases := []struct { resp http.Response want []attribute.KeyValue }{ - {resp: http.Response{StatusCode: 200, ContentLength: 123}, want: []attribute.KeyValue{attribute.Int("http.response.status_code", 200), attribute.Int("http.status_code", 200), attribute.Int("http.response_content_length", 123)}}, - {resp: http.Response{StatusCode: 404, ContentLength: 0}, want: []attribute.KeyValue{attribute.Int("http.response.status_code", 404), attribute.Int("http.status_code", 404), attribute.String("error.type", "404")}}, + {resp: http.Response{StatusCode: 200, ContentLength: 123}, want: []attribute.KeyValue{attribute.Int("http.response.status_code", 200)}}, + {resp: http.Response{StatusCode: 404, ContentLength: 0}, want: []attribute.KeyValue{attribute.Int("http.response.status_code", 404), attribute.String("error.type", "404")}}, } for _, tt := range testcases { @@ -401,12 +353,6 @@ func TestRequestErrorType(t *testing.T) { } func TestNewClientRecordMetrics(t *testing.T) { - oldAttrs := attribute.NewSet( - attribute.String("http.method", "POST"), - attribute.Int64("http.status_code", 301), - attribute.String("net.peer.name", "example.com"), - ) - currAttrs := attribute.NewSet( attribute.String("http.request.method", "POST"), attribute.Int64("http.response.status_code", 301), @@ -451,47 +397,13 @@ func TestNewClientRecordMetrics(t *testing.T) { }, } - // The OldHTTPClient version - expectedOldScopeMetric := expectedCurrentScopeMetric - expectedOldScopeMetric.Metrics = append(expectedOldScopeMetric.Metrics, []metricdata.Metrics{ - { - Name: "http.client.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.client.duration", - Description: "Measures the duration of outbound HTTP requests.", - Unit: "ms", - Data: metricdata.Histogram[float64]{ - Temporality: metricdata.CumulativeTemporality, - DataPoints: []metricdata.HistogramDataPoint[float64]{ - { - Attributes: oldAttrs, - }, - }, - }, - }, - }...) - tests := []struct { name string - setEnv bool clientFunc func(metric.MeterProvider) semconv.HTTPClient wantFunc func(t *testing.T, rm metricdata.ResourceMetrics) }{ { name: "No environment variable set, and no Meter", - setEnv: false, clientFunc: func(metric.MeterProvider) semconv.HTTPClient { return semconv.NewHTTPClient(nil) }, @@ -500,8 +412,7 @@ func TestNewClientRecordMetrics(t *testing.T) { }, }, { - name: "No environment variable set, but with Meter", - setEnv: false, + name: "With Meter", clientFunc: func(mp metric.MeterProvider) semconv.HTTPClient { return semconv.NewHTTPClient(mp.Meter("test")) }, @@ -512,36 +423,10 @@ func TestNewClientRecordMetrics(t *testing.T) { metricdatatest.AssertEqual(t, expectedCurrentScopeMetric, rm.ScopeMetrics[0], metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreValue()) }, }, - { - name: "Set environment variable, but no Meter", - setEnv: true, - clientFunc: func(metric.MeterProvider) semconv.HTTPClient { - return semconv.NewHTTPClient(nil) - }, - wantFunc: func(t *testing.T, rm metricdata.ResourceMetrics) { - assert.Empty(t, rm.ScopeMetrics) - }, - }, - { - name: "Set environment variable and Meter", - setEnv: true, - clientFunc: func(mp metric.MeterProvider) semconv.HTTPClient { - return semconv.NewHTTPClient(mp.Meter("test")) - }, - wantFunc: func(t *testing.T, rm metricdata.ResourceMetrics) { - require.Len(t, rm.ScopeMetrics, 1) - require.Len(t, rm.ScopeMetrics[0].Metrics, 4) - metricdatatest.AssertEqual(t, expectedOldScopeMetric, 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)) @@ -564,111 +449,6 @@ func TestNewClientRecordMetrics(t *testing.T) { } } -func TestClientRecordResponseSize(t *testing.T) { - oldAttrs := attribute.NewSet( - attribute.String("http.method", "POST"), - attribute.Int64("http.status_code", 301), - attribute.String("net.peer.name", "example.com"), - ) - - // The OldHTTPClient version - expectedOldScopeMetric := metricdata.ScopeMetrics{ - Scope: instrumentation.Scope{ - Name: "test", - }, - Metrics: []metricdata.Metrics{ - { - Name: "http.client.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, - }, - }, - }, - }, - }, - } - - tests := []struct { - name string - setEnv bool - clientFunc func(metric.MeterProvider) semconv.HTTPClient - wantFunc func(t *testing.T, rm metricdata.ResourceMetrics) - }{ - { - name: "No environment variable set, and no Meter", - setEnv: false, - clientFunc: func(metric.MeterProvider) semconv.HTTPClient { - return semconv.NewHTTPClient(nil) - }, - wantFunc: func(t *testing.T, rm metricdata.ResourceMetrics) { - assert.Empty(t, rm.ScopeMetrics) - }, - }, - { - name: "No environment variable set, but with Meter", - setEnv: false, - clientFunc: func(mp metric.MeterProvider) semconv.HTTPClient { - return semconv.NewHTTPClient(mp.Meter("test")) - }, - wantFunc: func(t *testing.T, rm metricdata.ResourceMetrics) { - require.Empty(t, rm.ScopeMetrics) - }, - }, - { - name: "Set environment variable, but no Meter", - setEnv: true, - clientFunc: func(metric.MeterProvider) semconv.HTTPClient { - return semconv.NewHTTPClient(nil) - }, - wantFunc: func(t *testing.T, rm metricdata.ResourceMetrics) { - assert.Empty(t, rm.ScopeMetrics) - }, - }, - { - name: "Set environment variable and Meter", - setEnv: true, - clientFunc: func(mp metric.MeterProvider) semconv.HTTPClient { - return semconv.NewHTTPClient(mp.Meter("test")) - }, - wantFunc: func(t *testing.T, rm metricdata.ResourceMetrics) { - require.Len(t, rm.ScopeMetrics, 1) - require.Len(t, rm.ScopeMetrics[0].Metrics, 1) - metricdatatest.AssertEqual(t, expectedOldScopeMetric, 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)) - - client := tt.clientFunc(mp) - req, err := http.NewRequest("POST", "http://example.com", nil) - assert.NoError(t, err) - - client.RecordResponseSize(context.Background(), 100, client.MetricOptions(semconv.MetricAttributes{ - Req: req, - StatusCode: 301, - })) - - rm := metricdata.ResourceMetrics{} - require.NoError(t, reader.Collect(context.Background(), &rm)) - tt.wantFunc(t, rm) - }) - } -} - type customError struct{} func (customError) Error() string { diff --git a/instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconv/v1.20.0.go b/instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconv/v1.20.0.go deleted file mode 100644 index d2863d3eca7..00000000000 --- a/instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconv/v1.20.0.go +++ /dev/null @@ -1,273 +0,0 @@ -// Code generated by gotmpl. DO NOT MODIFY. -// source: internal/shared/semconv/v120.0.go.tmpl - -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -package semconv // import "go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconv" - -import ( - "errors" - "io" - "net/http" - "slices" - - "go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconvutil" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/metric" - "go.opentelemetry.io/otel/metric/noop" - semconv "go.opentelemetry.io/otel/semconv/v1.20.0" -) - -type OldHTTPServer struct{} - -// RequestTraceAttrs returns trace attributes for an HTTP request received by a -// server. -// -// The server must be the primary server name if it is known. For example this -// would be the ServerName directive -// (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache -// server, and the server_name directive -// (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an -// nginx server. More generically, the primary server name would be the host -// header value that matches the default virtual host of an HTTP server. It -// should include the host identifier and if a port is used to route to the -// server that port identifier should be included as an appropriate port -// suffix. -// -// If the primary server name is not known, server should be an empty string. -// The req Host will be used to determine the server instead. -func (o OldHTTPServer) RequestTraceAttrs(server string, req *http.Request, attrs []attribute.KeyValue) []attribute.KeyValue { - return semconvutil.HTTPServerRequest(server, req, semconvutil.HTTPServerRequestOptions{}, attrs) -} - -func (o OldHTTPServer) NetworkTransportAttr(network string) attribute.KeyValue { - return semconvutil.NetTransport(network) -} - -// ResponseTraceAttrs returns trace attributes for telemetry from an HTTP response. -// -// If any of the fields in the ResponseTelemetry are not set the attribute will be omitted. -func (o OldHTTPServer) ResponseTraceAttrs(resp ResponseTelemetry, attributes []attribute.KeyValue) []attribute.KeyValue { - if resp.ReadBytes > 0 { - attributes = append(attributes, semconv.HTTPRequestContentLength(int(resp.ReadBytes))) - } - if resp.ReadError != nil && !errors.Is(resp.ReadError, io.EOF) { - // This is not in the semantic conventions, but is historically provided - attributes = append(attributes, attribute.String("http.read_error", resp.ReadError.Error())) - } - if resp.WriteBytes > 0 { - attributes = append(attributes, semconv.HTTPResponseContentLength(int(resp.WriteBytes))) - } - if resp.StatusCode > 0 { - attributes = append(attributes, semconv.HTTPStatusCode(resp.StatusCode)) - } - if resp.WriteError != nil && !errors.Is(resp.WriteError, io.EOF) { - // This is not in the semantic conventions, but is historically provided - attributes = append(attributes, attribute.String("http.write_error", resp.WriteError.Error())) - } - - return attributes -} - -// Route returns the attribute for the route. -func (o OldHTTPServer) Route(route string) attribute.KeyValue { - return semconv.HTTPRoute(route) -} - -// HTTPStatusCode returns the attribute for the HTTP status code. -// This is a temporary function needed by metrics. This will be removed when MetricsRequest is added. -func HTTPStatusCode(status int) attribute.KeyValue { - return semconv.HTTPStatusCode(status) -} - -// Server HTTP metrics. -const ( - serverRequestSize = "http.server.request.size" // Incoming request bytes total - serverResponseSize = "http.server.response.size" // Incoming response bytes total - serverDuration = "http.server.duration" // Incoming end to end duration, milliseconds -) - -func (h OldHTTPServer) createMeasures(meter metric.Meter) (metric.Int64Counter, metric.Int64Counter, metric.Float64Histogram) { - if meter == nil { - return noop.Int64Counter{}, noop.Int64Counter{}, noop.Float64Histogram{} - } - var err error - requestBytesCounter, err := meter.Int64Counter( - serverRequestSize, - metric.WithUnit("By"), - metric.WithDescription("Measures the size of HTTP request messages."), - ) - handleErr(err) - - responseBytesCounter, err := meter.Int64Counter( - serverResponseSize, - metric.WithUnit("By"), - metric.WithDescription("Measures the size of HTTP response messages."), - ) - handleErr(err) - - serverLatencyMeasure, err := meter.Float64Histogram( - serverDuration, - metric.WithUnit("ms"), - metric.WithDescription("Measures the duration of inbound HTTP requests."), - ) - handleErr(err) - - return requestBytesCounter, responseBytesCounter, serverLatencyMeasure -} - -func (o OldHTTPServer) MetricAttributes(server string, req *http.Request, statusCode int, additionalAttributes []attribute.KeyValue) []attribute.KeyValue { - n := 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 { - n++ - } - protoName, protoVersion := netProtocol(req.Proto) - if protoName != "" { - n++ - } - if protoVersion != "" { - n++ - } - - if statusCode > 0 { - n++ - } - - attributes := slices.Grow(additionalAttributes, n) - attributes = append(attributes, - semconv.HTTPMethod(standardizeHTTPMethod(req.Method)), - o.scheme(req.TLS != nil), - semconv.NetHostName(host)) - - if hostPort > 0 { - attributes = append(attributes, semconv.NetHostPort(hostPort)) - } - if protoName != "" { - attributes = append(attributes, semconv.NetProtocolName(protoName)) - } - if protoVersion != "" { - attributes = append(attributes, semconv.NetProtocolVersion(protoVersion)) - } - - if statusCode > 0 { - attributes = append(attributes, semconv.HTTPStatusCode(statusCode)) - } - return attributes -} - -func (o OldHTTPServer) scheme(https bool) attribute.KeyValue { // nolint:revive - if https { - return semconv.HTTPSchemeHTTPS - } - return semconv.HTTPSchemeHTTP -} - -type OldHTTPClient struct{} - -func (o OldHTTPClient) RequestTraceAttrs(req *http.Request, attrs []attribute.KeyValue) []attribute.KeyValue { - return semconvutil.HTTPClientRequest(req, attrs) -} - -func (o OldHTTPClient) ResponseTraceAttrs(resp *http.Response, attrs []attribute.KeyValue) []attribute.KeyValue { - return semconvutil.HTTPClientResponse(resp, attrs) -} - -func (o OldHTTPClient) MetricAttributes(req *http.Request, statusCode int, additionalAttributes []attribute.KeyValue) []attribute.KeyValue { - /* The following semantic conventions are returned if present: - http.method string - http.status_code int - net.peer.name string - net.peer.port int - */ - - n := 2 // method, peer name. - var h string - if req.URL != nil { - h = req.URL.Host - } - var requestHost string - var requestPort int - for _, hostport := range []string{h, req.Header.Get("Host")} { - requestHost, requestPort = SplitHostPort(hostport) - if requestHost != "" || requestPort > 0 { - break - } - } - - port := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", requestPort) - if port > 0 { - n++ - } - - if statusCode > 0 { - n++ - } - - attributes := slices.Grow(additionalAttributes, n) - attributes = append(attributes, - semconv.HTTPMethod(standardizeHTTPMethod(req.Method)), - semconv.NetPeerName(requestHost), - ) - - if port > 0 { - attributes = append(attributes, semconv.NetPeerPort(port)) - } - - if statusCode > 0 { - attributes = append(attributes, semconv.HTTPStatusCode(statusCode)) - } - return attributes -} - -// Client HTTP metrics. -const ( - clientRequestSize = "http.client.request.size" // Incoming request bytes total - clientResponseSize = "http.client.response.size" // Incoming response bytes total - clientDuration = "http.client.duration" // Incoming end to end duration, milliseconds -) - -func (o OldHTTPClient) createMeasures(meter metric.Meter) (metric.Int64Counter, metric.Int64Counter, metric.Float64Histogram) { - if meter == nil { - return noop.Int64Counter{}, noop.Int64Counter{}, noop.Float64Histogram{} - } - requestBytesCounter, err := meter.Int64Counter( - clientRequestSize, - metric.WithUnit("By"), - metric.WithDescription("Measures the size of HTTP request messages."), - ) - handleErr(err) - - responseBytesCounter, err := meter.Int64Counter( - clientResponseSize, - metric.WithUnit("By"), - metric.WithDescription("Measures the size of HTTP response messages."), - ) - handleErr(err) - - latencyMeasure, err := meter.Float64Histogram( - clientDuration, - metric.WithUnit("ms"), - metric.WithDescription("Measures the duration of outbound HTTP requests."), - ) - handleErr(err) - - return requestBytesCounter, responseBytesCounter, latencyMeasure -} - -// TraceAttributes returns attributes for httptrace. -func (c OldHTTPClient) TraceAttributes(host string, attrs []attribute.KeyValue) []attribute.KeyValue { - return append(attrs, semconv.NetHostName(host)) -} diff --git a/instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconvutil/gen.go b/instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconvutil/gen.go deleted file mode 100644 index 9d25daee70c..00000000000 --- a/instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconvutil/gen.go +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -package semconvutil // import "go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconvutil" - -// Generate semconvutil package: -//go:generate gotmpl --body=../../../../../../../internal/shared/semconvutil/httpconv_test.go.tmpl "--data={}" --out=httpconv_test.go -//go:generate gotmpl --body=../../../../../../../internal/shared/semconvutil/httpconv.go.tmpl "--data={}" --out=httpconv.go -//go:generate gotmpl --body=../../../../../../../internal/shared/semconvutil/netconv_test.go.tmpl "--data={}" --out=netconv_test.go -//go:generate gotmpl --body=../../../../../../../internal/shared/semconvutil/netconv.go.tmpl "--data={}" --out=netconv.go diff --git a/instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconvutil/httpconv.go b/instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconvutil/httpconv.go deleted file mode 100644 index 60745e4eaf1..00000000000 --- a/instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconvutil/httpconv.go +++ /dev/null @@ -1,594 +0,0 @@ -// Code generated by gotmpl. DO NOT MODIFY. -// source: internal/shared/semconvutil/httpconv.go.tmpl - -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -// Package semconvutil provides OpenTelemetry semantic convention utilities. -package semconvutil // import "go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconvutil" - -import ( - "fmt" - "net/http" - "slices" - "strings" - - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/codes" - semconv "go.opentelemetry.io/otel/semconv/v1.20.0" -) - -type HTTPServerRequestOptions struct { - // If set, this is used as value for the "http.client_ip" attribute. - HTTPClientIP string -} - -// HTTPClientResponse returns trace attributes for an HTTP response received by a -// client from a server. It will return the following attributes if the related -// values are defined in resp: "http.status.code", -// "http.response_content_length". -// -// This does not add all OpenTelemetry required attributes for an HTTP event, -// it assumes ClientRequest was used to create the span with a complete set of -// attributes. If a complete set of attributes can be generated using the -// request contained in resp. For example: -// -// HTTPClientResponse(resp, ClientRequest(resp.Request))) -func HTTPClientResponse(resp *http.Response, attrs []attribute.KeyValue) []attribute.KeyValue { - return hc.ClientResponse(resp, attrs) -} - -// HTTPClientRequest returns trace attributes for an HTTP request made by a client. -// The following attributes are always returned: "http.url", "http.method", -// "net.peer.name". The following attributes are returned if the related values -// are defined in req: "net.peer.port", "user_agent.original", -// "http.request_content_length". -func HTTPClientRequest(req *http.Request, attrs []attribute.KeyValue) []attribute.KeyValue { - return hc.ClientRequest(req, attrs) -} - -// HTTPClientRequestMetrics returns metric attributes for an HTTP request made by a client. -// The following attributes are always returned: "http.method", "net.peer.name". -// The following attributes are returned if the -// related values are defined in req: "net.peer.port". -func HTTPClientRequestMetrics(req *http.Request) []attribute.KeyValue { - return hc.ClientRequestMetrics(req) -} - -// HTTPClientStatus returns a span status code and message for an HTTP status code -// value received by a client. -func HTTPClientStatus(code int) (codes.Code, string) { - return hc.ClientStatus(code) -} - -// HTTPServerRequest returns trace attributes for an HTTP request received by a -// server. -// -// The server must be the primary server name if it is known. For example this -// would be the ServerName directive -// (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache -// server, and the server_name directive -// (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an -// nginx server. More generically, the primary server name would be the host -// header value that matches the default virtual host of an HTTP server. It -// should include the host identifier and if a port is used to route to the -// server that port identifier should be included as an appropriate port -// suffix. -// -// If the primary server name is not known, server should be an empty string. -// The req Host will be used to determine the server instead. -// -// The following attributes are always returned: "http.method", "http.scheme", -// "http.target", "net.host.name". The following attributes are returned if -// they related values are defined in req: "net.host.port", "net.sock.peer.addr", -// "net.sock.peer.port", "user_agent.original", "http.client_ip". -func HTTPServerRequest(server string, req *http.Request, opts HTTPServerRequestOptions, attrs []attribute.KeyValue) []attribute.KeyValue { - return hc.ServerRequest(server, req, opts, attrs) -} - -// HTTPServerRequestMetrics returns metric attributes for an HTTP request received by a -// server. -// -// The server must be the primary server name if it is known. For example this -// would be the ServerName directive -// (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache -// server, and the server_name directive -// (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an -// nginx server. More generically, the primary server name would be the host -// header value that matches the default virtual host of an HTTP server. It -// should include the host identifier and if a port is used to route to the -// server that port identifier should be included as an appropriate port -// suffix. -// -// If the primary server name is not known, server should be an empty string. -// The req Host will be used to determine the server instead. -// -// The following attributes are always returned: "http.method", "http.scheme", -// "net.host.name". The following attributes are returned if they related -// values are defined in req: "net.host.port". -func HTTPServerRequestMetrics(server string, req *http.Request) []attribute.KeyValue { - return hc.ServerRequestMetrics(server, req) -} - -// HTTPServerStatus returns a span status code and message for an HTTP status code -// value returned by a server. Status codes in the 400-499 range are not -// returned as errors. -func HTTPServerStatus(code int) (codes.Code, string) { - return hc.ServerStatus(code) -} - -// httpConv are the HTTP semantic convention attributes defined for a version -// of the OpenTelemetry specification. -type httpConv struct { - NetConv *netConv - - HTTPClientIPKey attribute.Key - HTTPMethodKey attribute.Key - HTTPRequestContentLengthKey attribute.Key - HTTPResponseContentLengthKey attribute.Key - HTTPRouteKey attribute.Key - HTTPSchemeHTTP attribute.KeyValue - HTTPSchemeHTTPS attribute.KeyValue - HTTPStatusCodeKey attribute.Key - HTTPTargetKey attribute.Key - HTTPURLKey attribute.Key - UserAgentOriginalKey attribute.Key -} - -var hc = &httpConv{ - NetConv: nc, - - HTTPClientIPKey: semconv.HTTPClientIPKey, - HTTPMethodKey: semconv.HTTPMethodKey, - HTTPRequestContentLengthKey: semconv.HTTPRequestContentLengthKey, - HTTPResponseContentLengthKey: semconv.HTTPResponseContentLengthKey, - HTTPRouteKey: semconv.HTTPRouteKey, - HTTPSchemeHTTP: semconv.HTTPSchemeHTTP, - HTTPSchemeHTTPS: semconv.HTTPSchemeHTTPS, - HTTPStatusCodeKey: semconv.HTTPStatusCodeKey, - HTTPTargetKey: semconv.HTTPTargetKey, - HTTPURLKey: semconv.HTTPURLKey, - UserAgentOriginalKey: semconv.UserAgentOriginalKey, -} - -// ClientResponse returns attributes for an HTTP response received by a client -// from a server. The following attributes are returned if the related values -// are defined in resp: "http.status.code", "http.response_content_length". -// -// This does not add all OpenTelemetry required attributes for an HTTP event, -// it assumes ClientRequest was used to create the span with a complete set of -// attributes. If a complete set of attributes can be generated using the -// request contained in resp. For example: -// -// ClientResponse(resp, ClientRequest(resp.Request)) -func (c *httpConv) ClientResponse(resp *http.Response, attrs []attribute.KeyValue) []attribute.KeyValue { - /* The following semantic conventions are returned if present: - http.status_code int - http.response_content_length int - */ - var n int - if resp.StatusCode > 0 { - n++ - } - if resp.ContentLength > 0 { - n++ - } - if n == 0 { - return attrs - } - - attrs = slices.Grow(attrs, n) - if resp.StatusCode > 0 { - attrs = append(attrs, c.HTTPStatusCodeKey.Int(resp.StatusCode)) - } - if resp.ContentLength > 0 { - attrs = append(attrs, c.HTTPResponseContentLengthKey.Int(int(resp.ContentLength))) - } - return attrs -} - -// ClientRequest returns attributes for an HTTP request made by a client. The -// following attributes are always returned: "http.url", "http.method", -// "net.peer.name". The following attributes are returned if the related values -// are defined in req: "net.peer.port", "user_agent.original", -// "http.request_content_length", "user_agent.original". -func (c *httpConv) ClientRequest(req *http.Request, attrs []attribute.KeyValue) []attribute.KeyValue { - /* The following semantic conventions are returned if present: - http.method string - user_agent.original string - http.url string - net.peer.name string - net.peer.port int - http.request_content_length int - */ - - /* The following semantic conventions are not returned: - http.status_code This requires the response. See ClientResponse. - http.response_content_length This requires the response. See ClientResponse. - net.sock.family This requires the socket used. - net.sock.peer.addr This requires the socket used. - net.sock.peer.name This requires the socket used. - net.sock.peer.port This requires the socket used. - http.resend_count This is something outside of a single request. - net.protocol.name The value is the Request is ignored, and the go client will always use "http". - net.protocol.version The value in the Request is ignored, and the go client will always use 1.1 or 2.0. - */ - n := 3 // URL, peer name, proto, and method. - var h string - if req.URL != nil { - h = req.URL.Host - } - peer, p := firstHostPort(h, req.Header.Get("Host")) - port := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", p) - if port > 0 { - n++ - } - useragent := req.UserAgent() - if useragent != "" { - n++ - } - if req.ContentLength > 0 { - n++ - } - - attrs = slices.Grow(attrs, n) - attrs = append(attrs, c.method(req.Method)) - - var u string - if req.URL != nil { - // Remove any username/password info that may be in the URL. - userinfo := req.URL.User - req.URL.User = nil - u = req.URL.String() - // Restore any username/password info that was removed. - req.URL.User = userinfo - } - attrs = append(attrs, c.HTTPURLKey.String(u)) - - attrs = append(attrs, c.NetConv.PeerName(peer)) - if port > 0 { - attrs = append(attrs, c.NetConv.PeerPort(port)) - } - - if useragent != "" { - attrs = append(attrs, c.UserAgentOriginalKey.String(useragent)) - } - - if l := req.ContentLength; l > 0 { - attrs = append(attrs, c.HTTPRequestContentLengthKey.Int64(l)) - } - - return attrs -} - -// ClientRequestMetrics returns metric attributes for an HTTP request made by a client. The -// following attributes are always returned: "http.method", "net.peer.name". -// The following attributes are returned if the related values -// are defined in req: "net.peer.port". -func (c *httpConv) ClientRequestMetrics(req *http.Request) []attribute.KeyValue { - /* The following semantic conventions are returned if present: - http.method string - net.peer.name string - net.peer.port int - */ - - n := 2 // method, peer name. - var h string - if req.URL != nil { - h = req.URL.Host - } - peer, p := firstHostPort(h, req.Header.Get("Host")) - port := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", p) - if port > 0 { - n++ - } - - attrs := make([]attribute.KeyValue, 0, n) - attrs = append(attrs, c.method(req.Method), c.NetConv.PeerName(peer)) - - if port > 0 { - attrs = append(attrs, c.NetConv.PeerPort(port)) - } - - return attrs -} - -// ServerRequest returns attributes for an HTTP request received by a server. -// -// The server must be the primary server name if it is known. For example this -// would be the ServerName directive -// (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache -// server, and the server_name directive -// (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an -// nginx server. More generically, the primary server name would be the host -// header value that matches the default virtual host of an HTTP server. It -// should include the host identifier and if a port is used to route to the -// server that port identifier should be included as an appropriate port -// suffix. -// -// If the primary server name is not known, server should be an empty string. -// The req Host will be used to determine the server instead. -// -// The following attributes are always returned: "http.method", "http.scheme", -// "http.target", "net.host.name". The following attributes are returned if they -// related values are defined in req: "net.host.port", "net.sock.peer.addr", -// "net.sock.peer.port", "user_agent.original", "http.client_ip", -// "net.protocol.name", "net.protocol.version". -func (c *httpConv) ServerRequest(server string, req *http.Request, opts HTTPServerRequestOptions, attrs []attribute.KeyValue) []attribute.KeyValue { - /* The following semantic conventions are returned if present: - http.method string - http.scheme string - net.host.name string - net.host.port int - net.sock.peer.addr string - net.sock.peer.port int - user_agent.original string - http.client_ip string - net.protocol.name string Note: not set if the value is "http". - net.protocol.version string - http.target string Note: doesn't include the query parameter. - */ - - /* The following semantic conventions are not returned: - http.status_code This requires the response. - http.request_content_length This requires the len() of body, which can mutate it. - http.response_content_length This requires the response. - http.route This is not available. - net.sock.peer.name This would require a DNS lookup. - net.sock.host.addr The request doesn't have access to the underlying socket. - net.sock.host.port The request doesn't have access to the underlying socket. - - */ - n := 4 // Method, scheme, proto, and host name. - 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 { - n++ - } - peer, peerPort := splitHostPort(req.RemoteAddr) - if peer != "" { - n++ - if peerPort > 0 { - n++ - } - } - useragent := req.UserAgent() - if useragent != "" { - n++ - } - - // For client IP, use, in order: - // 1. The value passed in the options - // 2. The value in the X-Forwarded-For header - // 3. The peer address - clientIP := opts.HTTPClientIP - if clientIP == "" { - clientIP = serverClientIP(req.Header.Get("X-Forwarded-For")) - if clientIP == "" { - clientIP = peer - } - } - if clientIP != "" { - n++ - } - - var target string - if req.URL != nil { - target = req.URL.Path - if target != "" { - n++ - } - } - protoName, protoVersion := netProtocol(req.Proto) - if protoName != "" && protoName != "http" { - n++ - } - if protoVersion != "" { - n++ - } - - attrs = slices.Grow(attrs, n) - - attrs = append(attrs, c.method(req.Method)) - attrs = append(attrs, c.scheme(req.TLS != nil)) - attrs = append(attrs, c.NetConv.HostName(host)) - - if hostPort > 0 { - attrs = append(attrs, c.NetConv.HostPort(hostPort)) - } - - if peer != "" { - // The Go HTTP server sets RemoteAddr to "IP:port", this will not be a - // file-path that would be interpreted with a sock family. - attrs = append(attrs, c.NetConv.SockPeerAddr(peer)) - if peerPort > 0 { - attrs = append(attrs, c.NetConv.SockPeerPort(peerPort)) - } - } - - if useragent != "" { - attrs = append(attrs, c.UserAgentOriginalKey.String(useragent)) - } - - if clientIP != "" { - attrs = append(attrs, c.HTTPClientIPKey.String(clientIP)) - } - - if target != "" { - attrs = append(attrs, c.HTTPTargetKey.String(target)) - } - - if protoName != "" && protoName != "http" { - attrs = append(attrs, c.NetConv.NetProtocolName.String(protoName)) - } - if protoVersion != "" { - attrs = append(attrs, c.NetConv.NetProtocolVersion.String(protoVersion)) - } - - return attrs -} - -// ServerRequestMetrics returns metric attributes for an HTTP request received -// by a server. -// -// The server must be the primary server name if it is known. For example this -// would be the ServerName directive -// (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache -// server, and the server_name directive -// (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an -// nginx server. More generically, the primary server name would be the host -// header value that matches the default virtual host of an HTTP server. It -// should include the host identifier and if a port is used to route to the -// server that port identifier should be included as an appropriate port -// suffix. -// -// If the primary server name is not known, server should be an empty string. -// The req Host will be used to determine the server instead. -// -// The following attributes are always returned: "http.method", "http.scheme", -// "net.host.name". The following attributes are returned if they related -// values are defined in req: "net.host.port". -func (c *httpConv) ServerRequestMetrics(server string, req *http.Request) []attribute.KeyValue { - /* The following semantic conventions are returned if present: - http.scheme string - http.route string - http.method string - http.status_code int - net.host.name string - net.host.port int - net.protocol.name string Note: not set if the value is "http". - net.protocol.version string - */ - - n := 3 // Method, scheme, and host name. - 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 { - n++ - } - protoName, protoVersion := netProtocol(req.Proto) - if protoName != "" { - n++ - } - if protoVersion != "" { - n++ - } - - attrs := make([]attribute.KeyValue, 0, n) - - attrs = append(attrs, c.methodMetric(req.Method)) - attrs = append(attrs, c.scheme(req.TLS != nil)) - attrs = append(attrs, c.NetConv.HostName(host)) - - if hostPort > 0 { - attrs = append(attrs, c.NetConv.HostPort(hostPort)) - } - if protoName != "" { - attrs = append(attrs, c.NetConv.NetProtocolName.String(protoName)) - } - if protoVersion != "" { - attrs = append(attrs, c.NetConv.NetProtocolVersion.String(protoVersion)) - } - - return attrs -} - -func (c *httpConv) method(method string) attribute.KeyValue { - if method == "" { - return c.HTTPMethodKey.String(http.MethodGet) - } - return c.HTTPMethodKey.String(method) -} - -func (c *httpConv) methodMetric(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 c.HTTPMethodKey.String(method) -} - -func (c *httpConv) scheme(https bool) attribute.KeyValue { // nolint:revive - if https { - return c.HTTPSchemeHTTPS - } - return c.HTTPSchemeHTTP -} - -func serverClientIP(xForwardedFor string) string { - if idx := strings.Index(xForwardedFor, ","); idx >= 0 { - xForwardedFor = xForwardedFor[:idx] - } - return xForwardedFor -} - -func requiredHTTPPort(https bool, port int) int { // nolint:revive - if https { - if port > 0 && port != 443 { - return port - } - } else { - if port > 0 && port != 80 { - return port - } - } - return -1 -} - -// Return the request host and port from the first non-empty source. -func firstHostPort(source ...string) (host string, port int) { - for _, hostport := range source { - host, port = splitHostPort(hostport) - if host != "" || port > 0 { - break - } - } - return -} - -// ClientStatus returns a span status code and message for an HTTP status code -// value received by a client. -func (c *httpConv) ClientStatus(code int) (codes.Code, string) { - if code < 100 || code >= 600 { - return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code) - } - if code >= 400 { - return codes.Error, "" - } - return codes.Unset, "" -} - -// ServerStatus returns a span status code and message for an HTTP status code -// value returned by a server. Status codes in the 400-499 range are not -// returned as errors. -func (c *httpConv) ServerStatus(code int) (codes.Code, string) { - if code < 100 || code >= 600 { - return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code) - } - if code >= 500 { - return codes.Error, "" - } - return codes.Unset, "" -} diff --git a/instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconvutil/httpconv_test.go b/instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconvutil/httpconv_test.go deleted file mode 100644 index ce3a4eb3319..00000000000 --- a/instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconvutil/httpconv_test.go +++ /dev/null @@ -1,561 +0,0 @@ -// Code generated by gotmpl. DO NOT MODIFY. -// source: internal/shared/semconvutil/httpconv_test.go.tmpl - -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -package semconvutil - -import ( - "net/http" - "net/http/httptest" - "net/url" - "strconv" - "testing" - "time" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/codes" -) - -func TestHTTPClientResponse(t *testing.T) { - const stat, n = 201, 397 - resp := &http.Response{ - StatusCode: stat, - ContentLength: n, - } - got := HTTPClientResponse(resp, nil) - assert.Equal(t, 2, cap(got), "slice capacity") - assert.ElementsMatch(t, []attribute.KeyValue{ - attribute.Key("http.status_code").Int(stat), - attribute.Key("http.response_content_length").Int(n), - }, got) -} - -func TestHTTPSClientRequest(t *testing.T) { - req := &http.Request{ - Method: http.MethodGet, - URL: &url.URL{ - Scheme: "https", - Host: "127.0.0.1:443", - Path: "/resource", - }, - Proto: "HTTP/1.0", - ProtoMajor: 1, - ProtoMinor: 0, - } - - assert.ElementsMatch( - t, - []attribute.KeyValue{ - attribute.String("http.method", "GET"), - attribute.String("http.url", "https://127.0.0.1:443/resource"), - attribute.String("net.peer.name", "127.0.0.1"), - }, - HTTPClientRequest(req, nil), - ) -} - -func TestHTTPSClientRequestMetrics(t *testing.T) { - req := &http.Request{ - Method: http.MethodGet, - URL: &url.URL{ - Scheme: "https", - Host: "127.0.0.1:443", - Path: "/resource", - }, - Proto: "HTTP/1.0", - ProtoMajor: 1, - ProtoMinor: 0, - } - - assert.ElementsMatch( - t, - []attribute.KeyValue{ - attribute.String("http.method", "GET"), - attribute.String("net.peer.name", "127.0.0.1"), - }, - HTTPClientRequestMetrics(req), - ) -} - -func TestHTTPClientRequest(t *testing.T) { - const ( - user = "alice" - n = 128 - agent = "Go-http-client/1.1" - ) - req := &http.Request{ - Method: http.MethodGet, - URL: &url.URL{ - Scheme: "http", - Host: "127.0.0.1:8080", - Path: "/resource", - }, - Proto: "HTTP/1.0", - ProtoMajor: 1, - ProtoMinor: 0, - Header: http.Header{ - "User-Agent": []string{agent}, - }, - ContentLength: n, - } - req.SetBasicAuth(user, "pswrd") - - assert.ElementsMatch( - t, - []attribute.KeyValue{ - attribute.String("http.method", "GET"), - attribute.String("http.url", "http://127.0.0.1:8080/resource"), - attribute.String("net.peer.name", "127.0.0.1"), - attribute.Int("net.peer.port", 8080), - attribute.String("user_agent.original", agent), - attribute.Int("http.request_content_length", n), - }, - HTTPClientRequest(req, nil), - ) -} - -func TestHTTPClientRequestMetrics(t *testing.T) { - const ( - user = "alice" - n = 128 - agent = "Go-http-client/1.1" - ) - req := &http.Request{ - Method: http.MethodGet, - URL: &url.URL{ - Scheme: "http", - Host: "127.0.0.1:8080", - Path: "/resource", - }, - Proto: "HTTP/1.0", - ProtoMajor: 1, - ProtoMinor: 0, - Header: http.Header{ - "User-Agent": []string{agent}, - }, - ContentLength: n, - } - req.SetBasicAuth(user, "pswrd") - - assert.ElementsMatch( - t, - []attribute.KeyValue{ - attribute.String("http.method", "GET"), - attribute.String("net.peer.name", "127.0.0.1"), - attribute.Int("net.peer.port", 8080), - }, - HTTPClientRequestMetrics(req), - ) -} - -func TestHTTPClientRequestRequired(t *testing.T) { - req := new(http.Request) - var got []attribute.KeyValue - assert.NotPanics(t, func() { got = HTTPClientRequest(req, nil) }) - want := []attribute.KeyValue{ - attribute.String("http.method", "GET"), - attribute.String("http.url", ""), - attribute.String("net.peer.name", ""), - } - assert.Equal(t, want, got) -} - -func TestHTTPServerRequest(t *testing.T) { - for _, tt := range []struct { - name string - requestModifierFn func(r *http.Request) - httpServerRequestOpts HTTPServerRequestOptions - - wantClientIP string - }{ - { - name: "with a client IP from the network", - wantClientIP: "1.2.3.4", - }, - { - name: "with a client IP from x-forwarded-for header", - requestModifierFn: func(r *http.Request) { - r.Header.Add("X-Forwarded-For", "5.6.7.8") - }, - wantClientIP: "5.6.7.8", - }, - { - name: "with a client IP in options", - requestModifierFn: func(r *http.Request) { - r.Header.Add("X-Forwarded-For", "5.6.7.8") - }, - httpServerRequestOpts: HTTPServerRequestOptions{ - HTTPClientIP: "9.8.7.6", - }, - wantClientIP: "9.8.7.6", - }, - } { - t.Run(tt.name, func(t *testing.T) { - reqCh := make(chan *http.Request, 1) - handler := func(w http.ResponseWriter, r *http.Request) { - r.RemoteAddr = "1.2.3.4:5678" - reqCh <- r - w.WriteHeader(http.StatusOK) - } - - srv := httptest.NewServer(http.HandlerFunc(handler)) - defer srv.Close() - - srvURL, err := url.Parse(srv.URL) - require.NoError(t, err) - srvPort, err := strconv.ParseInt(srvURL.Port(), 10, 32) - require.NoError(t, err) - - req, err := http.NewRequest(http.MethodGet, srv.URL, nil) - require.NoError(t, err) - - if tt.requestModifierFn != nil { - tt.requestModifierFn(req) - } - - resp, err := srv.Client().Do(req) - require.NoError(t, err) - require.NoError(t, resp.Body.Close()) - - var got *http.Request - select { - case got = <-reqCh: - // All good - case <-time.After(5 * time.Second): - t.Fatal("Did not receive a signal in 5s") - } - - peer, peerPort := splitHostPort(got.RemoteAddr) - - const user = "alice" - got.SetBasicAuth(user, "pswrd") - - assert.ElementsMatch(t, - []attribute.KeyValue{ - attribute.String("http.method", "GET"), - attribute.String("http.scheme", "http"), - attribute.String("net.host.name", srvURL.Hostname()), - attribute.Int("net.host.port", int(srvPort)), - attribute.String("net.sock.peer.addr", peer), - attribute.Int("net.sock.peer.port", peerPort), - attribute.String("user_agent.original", "Go-http-client/1.1"), - attribute.String("http.client_ip", tt.wantClientIP), - attribute.String("net.protocol.version", "1.1"), - attribute.String("http.target", "/"), - }, - HTTPServerRequest("", got, tt.httpServerRequestOpts, nil)) - }) - } -} - -func TestHTTPServerRequestMetrics(t *testing.T) { - got := make(chan *http.Request, 1) - handler := func(w http.ResponseWriter, r *http.Request) { - got <- r - w.WriteHeader(http.StatusOK) - } - - srv := httptest.NewServer(http.HandlerFunc(handler)) - defer srv.Close() - - srvURL, err := url.Parse(srv.URL) - require.NoError(t, err) - srvPort, err := strconv.ParseInt(srvURL.Port(), 10, 32) - require.NoError(t, err) - - resp, err := srv.Client().Get(srv.URL) - require.NoError(t, err) - require.NoError(t, resp.Body.Close()) - - var req *http.Request - select { - case req = <-got: - // All good - case <-time.After(5 * time.Second): - t.Fatal("did not receive a signal in 5s") - } - - assert.ElementsMatch(t, - []attribute.KeyValue{ - attribute.String("http.method", "GET"), - attribute.String("http.scheme", "http"), - attribute.String("net.host.name", srvURL.Hostname()), - attribute.Int("net.host.port", int(srvPort)), - attribute.String("net.protocol.name", "http"), - attribute.String("net.protocol.version", "1.1"), - }, - HTTPServerRequestMetrics("", req)) -} - -func TestHTTPServerName(t *testing.T) { - req := new(http.Request) - var got []attribute.KeyValue - const ( - host = "test.semconv.server" - port = 8080 - ) - portStr := strconv.Itoa(port) - server := host + ":" + portStr - assert.NotPanics(t, func() { got = HTTPServerRequest(server, req, HTTPServerRequestOptions{}, nil) }) - assert.Contains(t, got, attribute.String("net.host.name", host)) - assert.Contains(t, got, attribute.Int("net.host.port", port)) - - req = &http.Request{Host: "alt.host.name:" + portStr} - // The server parameter does not include a port, ServerRequest should use - // the port in the request Host field. - assert.NotPanics(t, func() { got = HTTPServerRequest(host, req, HTTPServerRequestOptions{}, nil) }) - assert.Contains(t, got, attribute.String("net.host.name", host)) - assert.Contains(t, got, attribute.Int("net.host.port", port)) -} - -func TestHTTPServerRequestFailsGracefully(t *testing.T) { - req := new(http.Request) - var got []attribute.KeyValue - assert.NotPanics(t, func() { got = HTTPServerRequest("", req, HTTPServerRequestOptions{}, nil) }) - want := []attribute.KeyValue{ - attribute.String("http.method", "GET"), - attribute.String("http.scheme", "http"), - attribute.String("net.host.name", ""), - } - assert.ElementsMatch(t, want, got) -} - -func TestHTTPMethod(t *testing.T) { - assert.Equal(t, attribute.String("http.method", "POST"), hc.method("POST")) - assert.Equal(t, attribute.String("http.method", "GET"), hc.method("")) - assert.Equal(t, attribute.String("http.method", "garbage"), hc.method("garbage")) -} - -func TestHTTPScheme(t *testing.T) { - assert.Equal(t, attribute.String("http.scheme", "http"), hc.scheme(false)) - assert.Equal(t, attribute.String("http.scheme", "https"), hc.scheme(true)) -} - -func TestHTTPServerClientIP(t *testing.T) { - tests := []struct { - xForwardedFor string - want string - }{ - {"", ""}, - {"127.0.0.1", "127.0.0.1"}, - {"127.0.0.1,127.0.0.5", "127.0.0.1"}, - } - for _, test := range tests { - got := serverClientIP(test.xForwardedFor) - assert.Equal(t, test.want, got, test.xForwardedFor) - } -} - -func TestRequiredHTTPPort(t *testing.T) { - tests := []struct { - https bool - port int - want int - }{ - {true, 443, -1}, - {true, 80, 80}, - {true, 8081, 8081}, - {false, 443, 443}, - {false, 80, -1}, - {false, 8080, 8080}, - } - for _, test := range tests { - got := requiredHTTPPort(test.https, test.port) - assert.Equalf(t, test.want, got, "HTTPS: %t, Port: %d", test.https, test.port) - } -} - -func TestFirstHostPort(t *testing.T) { - host, port := "127.0.0.1", 8080 - hostport := "127.0.0.1:8080" - sources := [][]string{ - {hostport}, - {"", hostport}, - {"", "", hostport}, - {"", "", hostport, ""}, - {"", "", hostport, "127.0.0.3:80"}, - } - - for _, src := range sources { - h, p := firstHostPort(src...) - assert.Equal(t, host, h, "%+v", src) - assert.Equal(t, port, p, "%+v", src) - } -} - -func TestHTTPClientStatus(t *testing.T) { - tests := []struct { - code int - stat codes.Code - msg bool - }{ - {0, codes.Error, true}, - {http.StatusContinue, codes.Unset, false}, - {http.StatusSwitchingProtocols, codes.Unset, false}, - {http.StatusProcessing, codes.Unset, false}, - {http.StatusEarlyHints, codes.Unset, false}, - {http.StatusOK, codes.Unset, false}, - {http.StatusCreated, codes.Unset, false}, - {http.StatusAccepted, codes.Unset, false}, - {http.StatusNonAuthoritativeInfo, codes.Unset, false}, - {http.StatusNoContent, codes.Unset, false}, - {http.StatusResetContent, codes.Unset, false}, - {http.StatusPartialContent, codes.Unset, false}, - {http.StatusMultiStatus, codes.Unset, false}, - {http.StatusAlreadyReported, codes.Unset, false}, - {http.StatusIMUsed, codes.Unset, false}, - {http.StatusMultipleChoices, codes.Unset, false}, - {http.StatusMovedPermanently, codes.Unset, false}, - {http.StatusFound, codes.Unset, false}, - {http.StatusSeeOther, codes.Unset, false}, - {http.StatusNotModified, codes.Unset, false}, - {http.StatusUseProxy, codes.Unset, false}, - {306, codes.Unset, false}, - {http.StatusTemporaryRedirect, codes.Unset, false}, - {http.StatusPermanentRedirect, codes.Unset, false}, - {http.StatusBadRequest, codes.Error, false}, - {http.StatusUnauthorized, codes.Error, false}, - {http.StatusPaymentRequired, codes.Error, false}, - {http.StatusForbidden, codes.Error, false}, - {http.StatusNotFound, codes.Error, false}, - {http.StatusMethodNotAllowed, codes.Error, false}, - {http.StatusNotAcceptable, codes.Error, false}, - {http.StatusProxyAuthRequired, codes.Error, false}, - {http.StatusRequestTimeout, codes.Error, false}, - {http.StatusConflict, codes.Error, false}, - {http.StatusGone, codes.Error, false}, - {http.StatusLengthRequired, codes.Error, false}, - {http.StatusPreconditionFailed, codes.Error, false}, - {http.StatusRequestEntityTooLarge, codes.Error, false}, - {http.StatusRequestURITooLong, codes.Error, false}, - {http.StatusUnsupportedMediaType, codes.Error, false}, - {http.StatusRequestedRangeNotSatisfiable, codes.Error, false}, - {http.StatusExpectationFailed, codes.Error, false}, - {http.StatusTeapot, codes.Error, false}, - {http.StatusMisdirectedRequest, codes.Error, false}, - {http.StatusUnprocessableEntity, codes.Error, false}, - {http.StatusLocked, codes.Error, false}, - {http.StatusFailedDependency, codes.Error, false}, - {http.StatusTooEarly, codes.Error, false}, - {http.StatusUpgradeRequired, codes.Error, false}, - {http.StatusPreconditionRequired, codes.Error, false}, - {http.StatusTooManyRequests, codes.Error, false}, - {http.StatusRequestHeaderFieldsTooLarge, codes.Error, false}, - {http.StatusUnavailableForLegalReasons, codes.Error, false}, - {499, codes.Error, false}, - {http.StatusInternalServerError, codes.Error, false}, - {http.StatusNotImplemented, codes.Error, false}, - {http.StatusBadGateway, codes.Error, false}, - {http.StatusServiceUnavailable, codes.Error, false}, - {http.StatusGatewayTimeout, codes.Error, false}, - {http.StatusHTTPVersionNotSupported, codes.Error, false}, - {http.StatusVariantAlsoNegotiates, codes.Error, false}, - {http.StatusInsufficientStorage, codes.Error, false}, - {http.StatusLoopDetected, codes.Error, false}, - {http.StatusNotExtended, codes.Error, false}, - {http.StatusNetworkAuthenticationRequired, codes.Error, false}, - {600, codes.Error, true}, - } - - for _, test := range tests { - t.Run(strconv.Itoa(test.code), func(t *testing.T) { - c, msg := HTTPClientStatus(test.code) - assert.Equal(t, test.stat, c) - if test.msg && msg == "" { - t.Errorf("expected non-empty message for %d", test.code) - } else if !test.msg && msg != "" { - t.Errorf("expected empty message for %d, got: %s", test.code, msg) - } - }) - } -} - -func TestHTTPServerStatus(t *testing.T) { - tests := []struct { - code int - stat codes.Code - msg bool - }{ - {0, codes.Error, true}, - {http.StatusContinue, codes.Unset, false}, - {http.StatusSwitchingProtocols, codes.Unset, false}, - {http.StatusProcessing, codes.Unset, false}, - {http.StatusEarlyHints, codes.Unset, false}, - {http.StatusOK, codes.Unset, false}, - {http.StatusCreated, codes.Unset, false}, - {http.StatusAccepted, codes.Unset, false}, - {http.StatusNonAuthoritativeInfo, codes.Unset, false}, - {http.StatusNoContent, codes.Unset, false}, - {http.StatusResetContent, codes.Unset, false}, - {http.StatusPartialContent, codes.Unset, false}, - {http.StatusMultiStatus, codes.Unset, false}, - {http.StatusAlreadyReported, codes.Unset, false}, - {http.StatusIMUsed, codes.Unset, false}, - {http.StatusMultipleChoices, codes.Unset, false}, - {http.StatusMovedPermanently, codes.Unset, false}, - {http.StatusFound, codes.Unset, false}, - {http.StatusSeeOther, codes.Unset, false}, - {http.StatusNotModified, codes.Unset, false}, - {http.StatusUseProxy, codes.Unset, false}, - {306, codes.Unset, false}, - {http.StatusTemporaryRedirect, codes.Unset, false}, - {http.StatusPermanentRedirect, codes.Unset, false}, - {http.StatusBadRequest, codes.Unset, false}, - {http.StatusUnauthorized, codes.Unset, false}, - {http.StatusPaymentRequired, codes.Unset, false}, - {http.StatusForbidden, codes.Unset, false}, - {http.StatusNotFound, codes.Unset, false}, - {http.StatusMethodNotAllowed, codes.Unset, false}, - {http.StatusNotAcceptable, codes.Unset, false}, - {http.StatusProxyAuthRequired, codes.Unset, false}, - {http.StatusRequestTimeout, codes.Unset, false}, - {http.StatusConflict, codes.Unset, false}, - {http.StatusGone, codes.Unset, false}, - {http.StatusLengthRequired, codes.Unset, false}, - {http.StatusPreconditionFailed, codes.Unset, false}, - {http.StatusRequestEntityTooLarge, codes.Unset, false}, - {http.StatusRequestURITooLong, codes.Unset, false}, - {http.StatusUnsupportedMediaType, codes.Unset, false}, - {http.StatusRequestedRangeNotSatisfiable, codes.Unset, false}, - {http.StatusExpectationFailed, codes.Unset, false}, - {http.StatusTeapot, codes.Unset, false}, - {http.StatusMisdirectedRequest, codes.Unset, false}, - {http.StatusUnprocessableEntity, codes.Unset, false}, - {http.StatusLocked, codes.Unset, false}, - {http.StatusFailedDependency, codes.Unset, false}, - {http.StatusTooEarly, codes.Unset, false}, - {http.StatusUpgradeRequired, codes.Unset, false}, - {http.StatusPreconditionRequired, codes.Unset, false}, - {http.StatusTooManyRequests, codes.Unset, false}, - {http.StatusRequestHeaderFieldsTooLarge, codes.Unset, false}, - {http.StatusUnavailableForLegalReasons, codes.Unset, false}, - {499, codes.Unset, false}, - {http.StatusInternalServerError, codes.Error, false}, - {http.StatusNotImplemented, codes.Error, false}, - {http.StatusBadGateway, codes.Error, false}, - {http.StatusServiceUnavailable, codes.Error, false}, - {http.StatusGatewayTimeout, codes.Error, false}, - {http.StatusHTTPVersionNotSupported, codes.Error, false}, - {http.StatusVariantAlsoNegotiates, codes.Error, false}, - {http.StatusInsufficientStorage, codes.Error, false}, - {http.StatusLoopDetected, codes.Error, false}, - {http.StatusNotExtended, codes.Error, false}, - {http.StatusNetworkAuthenticationRequired, codes.Error, false}, - {600, codes.Error, true}, - } - - for _, test := range tests { - c, msg := HTTPServerStatus(test.code) - assert.Equal(t, test.stat, c) - if test.msg && msg == "" { - t.Errorf("expected non-empty message for %d", test.code) - } else if !test.msg && msg != "" { - t.Errorf("expected empty message for %d, got: %s", test.code, msg) - } - } -} diff --git a/instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconvutil/netconv.go b/instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconvutil/netconv.go deleted file mode 100644 index 43337380a15..00000000000 --- a/instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconvutil/netconv.go +++ /dev/null @@ -1,214 +0,0 @@ -// Code generated by gotmpl. DO NOT MODIFY. -// source: internal/shared/semconvutil/netconv.go.tmpl - -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -package semconvutil // import "go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconvutil" - -import ( - "net" - "strconv" - "strings" - - "go.opentelemetry.io/otel/attribute" - semconv "go.opentelemetry.io/otel/semconv/v1.20.0" -) - -// NetTransport returns a trace attribute describing the transport protocol of the -// passed network. See the net.Dial for information about acceptable network -// values. -func NetTransport(network string) attribute.KeyValue { - return nc.Transport(network) -} - -// netConv are the network semantic convention attributes defined for a version -// of the OpenTelemetry specification. -type netConv struct { - NetHostNameKey attribute.Key - NetHostPortKey attribute.Key - NetPeerNameKey attribute.Key - NetPeerPortKey attribute.Key - NetProtocolName attribute.Key - NetProtocolVersion attribute.Key - NetSockFamilyKey attribute.Key - NetSockPeerAddrKey attribute.Key - NetSockPeerPortKey attribute.Key - NetSockHostAddrKey attribute.Key - NetSockHostPortKey attribute.Key - NetTransportOther attribute.KeyValue - NetTransportTCP attribute.KeyValue - NetTransportUDP attribute.KeyValue - NetTransportInProc attribute.KeyValue -} - -var nc = &netConv{ - NetHostNameKey: semconv.NetHostNameKey, - NetHostPortKey: semconv.NetHostPortKey, - NetPeerNameKey: semconv.NetPeerNameKey, - NetPeerPortKey: semconv.NetPeerPortKey, - NetProtocolName: semconv.NetProtocolNameKey, - NetProtocolVersion: semconv.NetProtocolVersionKey, - NetSockFamilyKey: semconv.NetSockFamilyKey, - NetSockPeerAddrKey: semconv.NetSockPeerAddrKey, - NetSockPeerPortKey: semconv.NetSockPeerPortKey, - NetSockHostAddrKey: semconv.NetSockHostAddrKey, - NetSockHostPortKey: semconv.NetSockHostPortKey, - NetTransportOther: semconv.NetTransportOther, - NetTransportTCP: semconv.NetTransportTCP, - NetTransportUDP: semconv.NetTransportUDP, - NetTransportInProc: semconv.NetTransportInProc, -} - -func (c *netConv) Transport(network string) attribute.KeyValue { - switch network { - case "tcp", "tcp4", "tcp6": - return c.NetTransportTCP - case "udp", "udp4", "udp6": - return c.NetTransportUDP - case "unix", "unixgram", "unixpacket": - return c.NetTransportInProc - default: - // "ip:*", "ip4:*", and "ip6:*" all are considered other. - return c.NetTransportOther - } -} - -// Host returns attributes for a network host address. -func (c *netConv) Host(address string) []attribute.KeyValue { - h, p := splitHostPort(address) - var n int - if h != "" { - n++ - if p > 0 { - n++ - } - } - - if n == 0 { - return nil - } - - attrs := make([]attribute.KeyValue, 0, n) - attrs = append(attrs, c.HostName(h)) - if p > 0 { - attrs = append(attrs, c.HostPort(p)) - } - return attrs -} - -func (c *netConv) HostName(name string) attribute.KeyValue { - return c.NetHostNameKey.String(name) -} - -func (c *netConv) HostPort(port int) attribute.KeyValue { - return c.NetHostPortKey.Int(port) -} - -func family(network, address string) string { - switch network { - case "unix", "unixgram", "unixpacket": - return "unix" - default: - if ip := net.ParseIP(address); ip != nil { - if ip.To4() == nil { - return "inet6" - } - return "inet" - } - } - return "" -} - -// Peer returns attributes for a network peer address. -func (c *netConv) Peer(address string) []attribute.KeyValue { - h, p := splitHostPort(address) - var n int - if h != "" { - n++ - if p > 0 { - n++ - } - } - - if n == 0 { - return nil - } - - attrs := make([]attribute.KeyValue, 0, n) - attrs = append(attrs, c.PeerName(h)) - if p > 0 { - attrs = append(attrs, c.PeerPort(p)) - } - return attrs -} - -func (c *netConv) PeerName(name string) attribute.KeyValue { - return c.NetPeerNameKey.String(name) -} - -func (c *netConv) PeerPort(port int) attribute.KeyValue { - return c.NetPeerPortKey.Int(port) -} - -func (c *netConv) SockPeerAddr(addr string) attribute.KeyValue { - return c.NetSockPeerAddrKey.String(addr) -} - -func (c *netConv) SockPeerPort(port int) attribute.KeyValue { - return c.NetSockPeerPortKey.Int(port) -} - -// splitHostPort splits a network address hostport of the form "host", -// "host%zone", "[host]", "[host%zone], "host:port", "host%zone:port", -// "[host]:port", "[host%zone]:port", or ":port" into host or host%zone and -// port. -// -// An empty host is returned if it is not provided or unparsable. A negative -// port is returned if it is not provided or unparsable. -func splitHostPort(hostport string) (host string, port int) { - port = -1 - - if strings.HasPrefix(hostport, "[") { - addrEnd := strings.LastIndex(hostport, "]") - if addrEnd < 0 { - // Invalid hostport. - return - } - if i := strings.LastIndex(hostport[addrEnd:], ":"); i < 0 { - host = hostport[1:addrEnd] - return - } - } else { - if i := strings.LastIndex(hostport, ":"); i < 0 { - host = hostport - return - } - } - - host, pStr, err := net.SplitHostPort(hostport) - if err != nil { - return - } - - p, err := strconv.ParseUint(pStr, 10, 16) - if err != nil { - return - } - return host, int(p) // nolint: gosec // Bitsize checked to be 16 above. -} - -func netProtocol(proto string) (name string, version string) { - name, version, _ = strings.Cut(proto, "/") - switch name { - case "HTTP": - name = "http" - case "QUIC": - name = "quic" - case "SPDY": - name = "spdy" - default: - name = strings.ToLower(name) - } - return name, version -} diff --git a/instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconvutil/netconv_test.go b/instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconvutil/netconv_test.go deleted file mode 100644 index 4f3c1540c13..00000000000 --- a/instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconvutil/netconv_test.go +++ /dev/null @@ -1,200 +0,0 @@ -// Code generated by gotmpl. DO NOT MODIFY. -// source: internal/shared/semconvutil/netconv_test.go.tmpl - -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -package semconvutil - -import ( - "testing" - - "github.com/stretchr/testify/assert" - - "go.opentelemetry.io/otel/attribute" -) - -const ( - addr = "127.0.0.1" - port = 1834 -) - -func TestNetTransport(t *testing.T) { - transports := map[string]attribute.KeyValue{ - "tcp": attribute.String("net.transport", "ip_tcp"), - "tcp4": attribute.String("net.transport", "ip_tcp"), - "tcp6": attribute.String("net.transport", "ip_tcp"), - "udp": attribute.String("net.transport", "ip_udp"), - "udp4": attribute.String("net.transport", "ip_udp"), - "udp6": attribute.String("net.transport", "ip_udp"), - "unix": attribute.String("net.transport", "inproc"), - "unixgram": attribute.String("net.transport", "inproc"), - "unixpacket": attribute.String("net.transport", "inproc"), - "ip:1": attribute.String("net.transport", "other"), - "ip:icmp": attribute.String("net.transport", "other"), - "ip4:proto": attribute.String("net.transport", "other"), - "ip6:proto": attribute.String("net.transport", "other"), - } - - for network, want := range transports { - assert.Equal(t, want, NetTransport(network)) - } -} - -func TestNetHost(t *testing.T) { - testAddrs(t, []addrTest{ - {address: "", expected: nil}, - {address: "192.0.0.1", expected: []attribute.KeyValue{ - nc.HostName("192.0.0.1"), - }}, - {address: "192.0.0.1:9090", expected: []attribute.KeyValue{ - nc.HostName("192.0.0.1"), - nc.HostPort(9090), - }}, - }, nc.Host) -} - -func TestNetHostName(t *testing.T) { - expected := attribute.Key("net.host.name").String(addr) - assert.Equal(t, expected, nc.HostName(addr)) -} - -func TestNetHostPort(t *testing.T) { - expected := attribute.Key("net.host.port").Int(port) - assert.Equal(t, expected, nc.HostPort(port)) -} - -func TestNetPeer(t *testing.T) { - testAddrs(t, []addrTest{ - {address: "", expected: nil}, - {address: "example.com", expected: []attribute.KeyValue{ - nc.PeerName("example.com"), - }}, - {address: "/tmp/file", expected: []attribute.KeyValue{ - nc.PeerName("/tmp/file"), - }}, - {address: "192.0.0.1", expected: []attribute.KeyValue{ - nc.PeerName("192.0.0.1"), - }}, - {address: ":9090", expected: nil}, - {address: "192.0.0.1:9090", expected: []attribute.KeyValue{ - nc.PeerName("192.0.0.1"), - nc.PeerPort(9090), - }}, - }, nc.Peer) -} - -func TestNetPeerName(t *testing.T) { - expected := attribute.Key("net.peer.name").String(addr) - assert.Equal(t, expected, nc.PeerName(addr)) -} - -func TestNetPeerPort(t *testing.T) { - expected := attribute.Key("net.peer.port").Int(port) - assert.Equal(t, expected, nc.PeerPort(port)) -} - -func TestNetSockPeerName(t *testing.T) { - expected := attribute.Key("net.sock.peer.addr").String(addr) - assert.Equal(t, expected, nc.SockPeerAddr(addr)) -} - -func TestNetSockPeerPort(t *testing.T) { - expected := attribute.Key("net.sock.peer.port").Int(port) - assert.Equal(t, expected, nc.SockPeerPort(port)) -} - -func TestNetFamily(t *testing.T) { - tests := []struct { - network string - address string - expect string - }{ - {"", "", ""}, - {"unix", "", "unix"}, - {"unix", "gibberish", "unix"}, - {"unixgram", "", "unix"}, - {"unixgram", "gibberish", "unix"}, - {"unixpacket", "gibberish", "unix"}, - {"tcp", "123.0.2.8", "inet"}, - {"tcp", "gibberish", ""}, - {"", "123.0.2.8", "inet"}, - {"", "gibberish", ""}, - {"tcp", "fe80::1", "inet6"}, - {"", "fe80::1", "inet6"}, - } - - for _, test := range tests { - got := family(test.network, test.address) - assert.Equal(t, test.expect, got, test.network+"/"+test.address) - } -} - -func TestSplitHostPort(t *testing.T) { - tests := []struct { - hostport string - host string - port int - }{ - {"", "", -1}, - {":8080", "", 8080}, - {"127.0.0.1", "127.0.0.1", -1}, - {"www.example.com", "www.example.com", -1}, - {"127.0.0.1%25en0", "127.0.0.1%25en0", -1}, - {"[]", "", -1}, // Ensure this doesn't panic. - {"[fe80::1", "", -1}, - {"[fe80::1]", "fe80::1", -1}, - {"[fe80::1%25en0]", "fe80::1%25en0", -1}, - {"[fe80::1]:8080", "fe80::1", 8080}, - {"[fe80::1]::", "", -1}, // Too many colons. - {"127.0.0.1:", "127.0.0.1", -1}, - {"127.0.0.1:port", "127.0.0.1", -1}, - {"127.0.0.1:8080", "127.0.0.1", 8080}, - {"www.example.com:8080", "www.example.com", 8080}, - {"127.0.0.1%25en0:8080", "127.0.0.1%25en0", 8080}, - } - - for _, test := range tests { - h, p := splitHostPort(test.hostport) - assert.Equal(t, test.host, h, test.hostport) - assert.Equal(t, test.port, p, test.hostport) - } -} - -type addrTest struct { - address string - expected []attribute.KeyValue -} - -func testAddrs(t *testing.T, tests []addrTest, f func(string) []attribute.KeyValue) { - t.Helper() - - for _, test := range tests { - got := f(test.address) - assert.Equal(t, cap(test.expected), cap(got), "slice capacity") - assert.ElementsMatch(t, test.expected, got, test.address) - } -} - -func TestNetProtocol(t *testing.T) { - type testCase struct { - name, version string - } - tests := map[string]testCase{ - "HTTP/1.0": {name: "http", version: "1.0"}, - "HTTP/1.1": {name: "http", version: "1.1"}, - "HTTP/2": {name: "http", version: "2"}, - "HTTP/3": {name: "http", version: "3"}, - "SPDY": {name: "spdy"}, - "SPDY/2": {name: "spdy", version: "2"}, - "QUIC": {name: "quic"}, - "unknown/proto/2": {name: "unknown", version: "proto/2"}, - "other": {name: "other"}, - } - - for proto, want := range tests { - name, version := netProtocol(proto) - assert.Equal(t, want.name, name) - assert.Equal(t, want.version, version) - } -} diff --git a/instrumentation/github.com/gorilla/mux/otelmux/internal/semconv/env.go b/instrumentation/github.com/gorilla/mux/otelmux/internal/semconv/env.go index 5c74441821e..4271bd888ea 100644 --- a/instrumentation/github.com/gorilla/mux/otelmux/internal/semconv/env.go +++ b/instrumentation/github.com/gorilla/mux/otelmux/internal/semconv/env.go @@ -10,7 +10,6 @@ import ( "context" "fmt" "net/http" - "os" "strings" "sync" @@ -33,14 +32,6 @@ type ResponseTelemetry struct { } type HTTPServer struct { - duplicate bool - - // Old metrics - requestBytesCounter metric.Int64Counter - responseBytesCounter metric.Int64Counter - serverLatencyMeasure metric.Float64Histogram - - // New metrics requestBodySizeHistogram httpconv.ServerRequestBodySize responseBodySizeHistogram httpconv.ServerResponseBodySize requestDurationHistogram httpconv.ServerRequestDuration @@ -63,20 +54,10 @@ type HTTPServer struct { // If the primary server name is not known, server should be an empty string. // The req Host will be used to determine the server instead. func (s HTTPServer) RequestTraceAttrs(server string, req *http.Request, opts RequestTraceAttrsOpts) []attribute.KeyValue { - attrs := CurrentHTTPServer{}.RequestTraceAttrs(server, req, opts) - if s.duplicate { - return OldHTTPServer{}.RequestTraceAttrs(server, req, attrs) - } - return attrs + return CurrentHTTPServer{}.RequestTraceAttrs(server, req, opts) } func (s HTTPServer) NetworkTransportAttr(network string) []attribute.KeyValue { - if s.duplicate { - return []attribute.KeyValue{ - OldHTTPServer{}.NetworkTransportAttr(network), - CurrentHTTPServer{}.NetworkTransportAttr(network), - } - } return []attribute.KeyValue{ CurrentHTTPServer{}.NetworkTransportAttr(network), } @@ -86,11 +67,7 @@ func (s HTTPServer) NetworkTransportAttr(network string) []attribute.KeyValue { // // If any of the fields in the ResponseTelemetry are not set the attribute will be omitted. func (s HTTPServer) ResponseTraceAttrs(resp ResponseTelemetry) []attribute.KeyValue { - attrs := CurrentHTTPServer{}.ResponseTraceAttrs(resp) - if s.duplicate { - return OldHTTPServer{}.ResponseTraceAttrs(resp, attrs) - } - return attrs + return CurrentHTTPServer{}.ResponseTraceAttrs(resp) } // Route returns the attribute for the route. @@ -156,18 +133,6 @@ func (s HTTPServer) RecordMetrics(ctx context.Context, md ServerMetricData) { s.requestDurationHistogram.Inst().Record(ctx, md.ElapsedTime/1000.0, o) *recordOpts = (*recordOpts)[:0] metricRecordOptionPool.Put(recordOpts) - - if s.duplicate && 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) - } } // hasOptIn returns true if the comma-separated version string contains the @@ -182,11 +147,7 @@ func hasOptIn(version, optIn string) bool { } func NewHTTPServer(meter metric.Meter) HTTPServer { - env := strings.ToLower(os.Getenv(OTelSemConvStabilityOptIn)) - duplicate := hasOptIn(env, "http/dup") - server := HTTPServer{ - duplicate: duplicate, - } + server := HTTPServer{} var err error server.requestBodySizeHistogram, err = httpconv.NewServerRequestBodySize(meter) @@ -203,32 +164,16 @@ func NewHTTPServer(meter metric.Meter) HTTPServer { ), ) handleErr(err) - - if duplicate { - server.requestBytesCounter, server.responseBytesCounter, server.serverLatencyMeasure = OldHTTPServer{}.createMeasures(meter) - } return server } type HTTPClient struct { - duplicate bool - - // old metrics - requestBytesCounter metric.Int64Counter - responseBytesCounter metric.Int64Counter - latencyMeasure metric.Float64Histogram - - // new metrics requestBodySize httpconv.ClientRequestBodySize requestDuration httpconv.ClientRequestDuration } func NewHTTPClient(meter metric.Meter) HTTPClient { - env := strings.ToLower(os.Getenv(OTelSemConvStabilityOptIn)) - duplicate := hasOptIn(env, "http/dup") - client := HTTPClient{ - duplicate: duplicate, - } + client := HTTPClient{} var err error client.requestBodySize, err = httpconv.NewClientRequestBodySize(meter) @@ -240,29 +185,17 @@ func NewHTTPClient(meter metric.Meter) HTTPClient { ) handleErr(err) - if duplicate { - client.requestBytesCounter, client.responseBytesCounter, client.latencyMeasure = OldHTTPClient{}.createMeasures(meter) - } - return client } // RequestTraceAttrs returns attributes for an HTTP request made by a client. func (c HTTPClient) RequestTraceAttrs(req *http.Request) []attribute.KeyValue { - attrs := CurrentHTTPClient{}.RequestTraceAttrs(req) - if c.duplicate { - return OldHTTPClient{}.RequestTraceAttrs(req, attrs) - } - return attrs + return CurrentHTTPClient{}.RequestTraceAttrs(req) } // ResponseTraceAttrs returns metric attributes for an HTTP request made by a client. func (c HTTPClient) ResponseTraceAttrs(resp *http.Response) []attribute.KeyValue { - attrs := CurrentHTTPClient{}.ResponseTraceAttrs(resp) - if c.duplicate { - return OldHTTPClient{}.ResponseTraceAttrs(resp, attrs) - } - return attrs + return CurrentHTTPClient{}.ResponseTraceAttrs(resp) } func (c HTTPClient) Status(code int) (codes.Code, string) { @@ -302,42 +235,14 @@ func (c HTTPClient) MetricOptions(ma MetricAttributes) map[string]MetricOpts { addOptions: set, } - if c.duplicate { - attributes := OldHTTPClient{}.MetricAttributes(ma.Req, ma.StatusCode, ma.AdditionalAttributes) - set := metric.WithAttributeSet(attribute.NewSet(attributes...)) - opts["old"] = MetricOpts{ - measurement: set, - addOptions: set, - } - } - return opts } func (s HTTPClient) RecordMetrics(ctx context.Context, md MetricData, opts map[string]MetricOpts) { s.requestBodySize.Inst().Record(ctx, md.RequestSize, opts["new"].MeasurementOption()) s.requestDuration.Inst().Record(ctx, md.ElapsedTime/1000, opts["new"].MeasurementOption()) - - if s.duplicate { - s.requestBytesCounter.Add(ctx, md.RequestSize, opts["old"].AddOptions()) - s.latencyMeasure.Record(ctx, md.ElapsedTime, opts["old"].MeasurementOption()) - } -} - -func (s HTTPClient) RecordResponseSize(ctx context.Context, responseData int64, opts map[string]MetricOpts) { - if s.responseBytesCounter == nil { - // This will happen if an HTTPClient{} is used instead of NewHTTPClient(). - return - } - - s.responseBytesCounter.Add(ctx, responseData, opts["old"].AddOptions()) } func (s HTTPClient) TraceAttributes(host string) []attribute.KeyValue { - attrs := CurrentHTTPClient{}.TraceAttributes(host) - if s.duplicate { - return OldHTTPClient{}.TraceAttributes(host, attrs) - } - - return attrs + return CurrentHTTPClient{}.TraceAttributes(host) } diff --git a/instrumentation/github.com/gorilla/mux/otelmux/internal/semconv/env_test.go b/instrumentation/github.com/gorilla/mux/otelmux/internal/semconv/env_test.go index 45b520854dd..11388bb8324 100644 --- a/instrumentation/github.com/gorilla/mux/otelmux/internal/semconv/env_test.go +++ b/instrumentation/github.com/gorilla/mux/otelmux/internal/semconv/env_test.go @@ -54,7 +54,6 @@ func TestHTTPServerDoesNotPanic(t *testing.T) { func TestServerNetworkTransportAttr(t *testing.T) { for _, tt := range []struct { name string - optinVal string network string wantAttributes []attribute.KeyValue @@ -67,29 +66,8 @@ func TestServerNetworkTransportAttr(t *testing.T) { attribute.String("network.transport", "tcp"), }, }, - { - name: "without a dup optin", - optinVal: "http/dup", - network: "tcp", - - wantAttributes: []attribute.KeyValue{ - attribute.String("net.transport", "ip_tcp"), - attribute.String("network.transport", "tcp"), - }, - }, - { - name: "with mixed categories", - optinVal: "http/dup,database", - network: "tcp", - - wantAttributes: []attribute.KeyValue{ - attribute.String("net.transport", "ip_tcp"), - attribute.String("network.transport", "tcp"), - }, - }, } { t.Run(tt.name, func(t *testing.T) { - t.Setenv(OTelSemConvStabilityOptIn, tt.optinVal) s := NewHTTPServer(nil) assert.Equal(t, tt.wantAttributes, s.NetworkTransportAttr(tt.network)) @@ -124,7 +102,6 @@ func TestHTTPClientDoesNotPanic(t *testing.T) { Req: req, StatusCode: 200, }) - tt.client.RecordResponseSize(context.Background(), 40, opts) tt.client.RecordMetrics(context.Background(), MetricData{ RequestSize: 20, ElapsedTime: 1, @@ -137,7 +114,6 @@ func TestHTTPClientDoesNotPanic(t *testing.T) { func TestHTTPClientTraceAttributes(t *testing.T) { for _, tt := range []struct { name string - optinVal string wantAttributes []attribute.KeyValue }{ @@ -148,28 +124,8 @@ func TestHTTPClientTraceAttributes(t *testing.T) { attribute.String("server.address", "example.com"), }, }, - { - name: "with optin set to duplicate", - optinVal: "http/dup", - - wantAttributes: []attribute.KeyValue{ - attribute.String("server.address", "example.com"), - attribute.String("net.host.name", "example.com"), - }, - }, - { - name: "with mixed categories", - optinVal: "http/dup,database", - - wantAttributes: []attribute.KeyValue{ - attribute.String("server.address", "example.com"), - attribute.String("net.host.name", "example.com"), - }, - }, } { t.Run(tt.name, func(t *testing.T) { - t.Setenv(OTelSemConvStabilityOptIn, tt.optinVal) - c := NewHTTPClient(nil) a := c.TraceAttributes("example.com") assert.Equal(t, tt.wantAttributes, a) @@ -180,7 +136,6 @@ func TestHTTPClientTraceAttributes(t *testing.T) { func TestClientTraceAttributes(t *testing.T) { for _, tt := range []struct { name string - optinVal string host string wantAttributes []attribute.KeyValue @@ -193,29 +148,8 @@ func TestClientTraceAttributes(t *testing.T) { attribute.String("server.address", "example.com"), }, }, - { - name: "without a dup optin", - optinVal: "http/dup", - host: "example.com", - - wantAttributes: []attribute.KeyValue{ - attribute.String("server.address", "example.com"), - attribute.String("net.host.name", "example.com"), - }, - }, - { - name: "with mixed categories", - optinVal: "http/dup", - host: "example.com", - - wantAttributes: []attribute.KeyValue{ - attribute.String("server.address", "example.com"), - attribute.String("net.host.name", "example.com"), - }, - }, } { t.Run(tt.name, func(t *testing.T) { - t.Setenv(OTelSemConvStabilityOptIn, tt.optinVal) s := NewHTTPClient(nil) assert.Equal(t, tt.wantAttributes, s.TraceAttributes(tt.host)) diff --git a/instrumentation/github.com/gorilla/mux/otelmux/internal/semconv/gen.go b/instrumentation/github.com/gorilla/mux/otelmux/internal/semconv/gen.go index e515a939e0b..020868407e9 100644 --- a/instrumentation/github.com/gorilla/mux/otelmux/internal/semconv/gen.go +++ b/instrumentation/github.com/gorilla/mux/otelmux/internal/semconv/gen.go @@ -13,4 +13,3 @@ package semconv // import "go.opentelemetry.io/contrib/instrumentation/github.co //go:generate gotmpl --body=../../../../../../../internal/shared/semconv/httpconvtest_test.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux\" }" --out=httpconvtest_test.go //go:generate gotmpl --body=../../../../../../../internal/shared/semconv/util.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux\" }" --out=util.go //go:generate gotmpl --body=../../../../../../../internal/shared/semconv/util_test.go.tmpl "--data={}" --out=util_test.go -//go:generate gotmpl --body=../../../../../../../internal/shared/semconv/v1.20.0.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux\" }" --out=v1.20.0.go diff --git a/instrumentation/github.com/gorilla/mux/otelmux/internal/semconv/httpconvtest_test.go b/instrumentation/github.com/gorilla/mux/otelmux/internal/semconv/httpconvtest_test.go index f2a170bd561..4cf8821840d 100644 --- a/instrumentation/github.com/gorilla/mux/otelmux/internal/semconv/httpconvtest_test.go +++ b/instrumentation/github.com/gorilla/mux/otelmux/internal/semconv/httpconvtest_test.go @@ -28,7 +28,6 @@ import ( ) func TestNewTraceRequest(t *testing.T) { - t.Setenv("OTEL_SEMCONV_STABILITY_OPT_IN", "http/dup") serv := semconv.NewHTTPServer(nil) want := func(req testServerReq) []attribute.KeyValue { return []attribute.KeyValue{ @@ -42,16 +41,6 @@ func TestNewTraceRequest(t *testing.T) { attribute.String("client.address", req.clientIP), attribute.String("network.protocol.version", "1.1"), attribute.String("url.path", "/"), - attribute.String("http.method", "GET"), - attribute.String("http.scheme", "http"), - attribute.String("net.host.name", req.hostname), - attribute.Int("net.host.port", req.serverPort), - attribute.String("net.sock.peer.addr", req.peerAddr), - attribute.Int("net.sock.peer.port", req.peerPort), - attribute.String("user_agent.original", "Go-http-client/1.1"), - attribute.String("http.client_ip", req.clientIP), - attribute.String("net.protocol.version", "1.1"), - attribute.String("http.target", "/"), } } testTraceRequest(t, serv, want) @@ -174,13 +163,11 @@ func TestNewServerRecordMetrics(t *testing.T) { 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, + name: "No Meter", serverFunc: func(metric.MeterProvider) semconv.HTTPServer { return semconv.NewHTTPServer(nil) }, @@ -189,8 +176,7 @@ func TestNewServerRecordMetrics(t *testing.T) { }, }, { - name: "No environment variable set, but with Meter", - setEnv: false, + name: "With Meter", serverFunc: func(mp metric.MeterProvider) semconv.HTTPServer { return semconv.NewHTTPServer(mp.Meter("test")) }, @@ -202,36 +188,10 @@ func TestNewServerRecordMetrics(t *testing.T) { metricdatatest.AssertEqual(t, expectedCurrentScopeMetric, 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, expectedOldScopeMetric, 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)) @@ -312,7 +272,6 @@ func TestNewTraceResponse(t *testing.T) { } func TestNewTraceRequest_Client(t *testing.T) { - t.Setenv("OTEL_SEMCONV_STABILITY_OPT_IN", "http/dup") body := strings.NewReader("Hello, world!") url := "https://example.com:8888/foo/bar?stuff=morestuff" req := httptest.NewRequest("pOST", url, body) @@ -321,29 +280,22 @@ func TestNewTraceRequest_Client(t *testing.T) { want := []attribute.KeyValue{ attribute.String("http.request.method", "POST"), attribute.String("http.request.method_original", "pOST"), - attribute.String("http.method", "pOST"), attribute.String("url.full", url), - attribute.String("http.url", url), attribute.String("server.address", "example.com"), attribute.Int("server.port", 8888), attribute.String("network.protocol.version", "1.1"), - attribute.String("net.peer.name", "example.com"), - attribute.Int("net.peer.port", 8888), - attribute.String("user_agent.original", "go-test-agent"), - attribute.Int("http.request_content_length", 13), } client := semconv.NewHTTPClient(nil) assert.ElementsMatch(t, want, client.RequestTraceAttrs(req)) } func TestNewTraceResponse_Client(t *testing.T) { - t.Setenv("OTEL_SEMCONV_STABILITY_OPT_IN", "http/dup") testcases := []struct { resp http.Response want []attribute.KeyValue }{ - {resp: http.Response{StatusCode: 200, ContentLength: 123}, want: []attribute.KeyValue{attribute.Int("http.response.status_code", 200), attribute.Int("http.status_code", 200), attribute.Int("http.response_content_length", 123)}}, - {resp: http.Response{StatusCode: 404, ContentLength: 0}, want: []attribute.KeyValue{attribute.Int("http.response.status_code", 404), attribute.Int("http.status_code", 404), attribute.String("error.type", "404")}}, + {resp: http.Response{StatusCode: 200, ContentLength: 123}, want: []attribute.KeyValue{attribute.Int("http.response.status_code", 200)}}, + {resp: http.Response{StatusCode: 404, ContentLength: 0}, want: []attribute.KeyValue{attribute.Int("http.response.status_code", 404), attribute.String("error.type", "404")}}, } for _, tt := range testcases { @@ -401,12 +353,6 @@ func TestRequestErrorType(t *testing.T) { } func TestNewClientRecordMetrics(t *testing.T) { - oldAttrs := attribute.NewSet( - attribute.String("http.method", "POST"), - attribute.Int64("http.status_code", 301), - attribute.String("net.peer.name", "example.com"), - ) - currAttrs := attribute.NewSet( attribute.String("http.request.method", "POST"), attribute.Int64("http.response.status_code", 301), @@ -451,47 +397,13 @@ func TestNewClientRecordMetrics(t *testing.T) { }, } - // The OldHTTPClient version - expectedOldScopeMetric := expectedCurrentScopeMetric - expectedOldScopeMetric.Metrics = append(expectedOldScopeMetric.Metrics, []metricdata.Metrics{ - { - Name: "http.client.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.client.duration", - Description: "Measures the duration of outbound HTTP requests.", - Unit: "ms", - Data: metricdata.Histogram[float64]{ - Temporality: metricdata.CumulativeTemporality, - DataPoints: []metricdata.HistogramDataPoint[float64]{ - { - Attributes: oldAttrs, - }, - }, - }, - }, - }...) - tests := []struct { name string - setEnv bool clientFunc func(metric.MeterProvider) semconv.HTTPClient wantFunc func(t *testing.T, rm metricdata.ResourceMetrics) }{ { name: "No environment variable set, and no Meter", - setEnv: false, clientFunc: func(metric.MeterProvider) semconv.HTTPClient { return semconv.NewHTTPClient(nil) }, @@ -500,8 +412,7 @@ func TestNewClientRecordMetrics(t *testing.T) { }, }, { - name: "No environment variable set, but with Meter", - setEnv: false, + name: "With Meter", clientFunc: func(mp metric.MeterProvider) semconv.HTTPClient { return semconv.NewHTTPClient(mp.Meter("test")) }, @@ -512,36 +423,10 @@ func TestNewClientRecordMetrics(t *testing.T) { metricdatatest.AssertEqual(t, expectedCurrentScopeMetric, rm.ScopeMetrics[0], metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreValue()) }, }, - { - name: "Set environment variable, but no Meter", - setEnv: true, - clientFunc: func(metric.MeterProvider) semconv.HTTPClient { - return semconv.NewHTTPClient(nil) - }, - wantFunc: func(t *testing.T, rm metricdata.ResourceMetrics) { - assert.Empty(t, rm.ScopeMetrics) - }, - }, - { - name: "Set environment variable and Meter", - setEnv: true, - clientFunc: func(mp metric.MeterProvider) semconv.HTTPClient { - return semconv.NewHTTPClient(mp.Meter("test")) - }, - wantFunc: func(t *testing.T, rm metricdata.ResourceMetrics) { - require.Len(t, rm.ScopeMetrics, 1) - require.Len(t, rm.ScopeMetrics[0].Metrics, 4) - metricdatatest.AssertEqual(t, expectedOldScopeMetric, 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)) @@ -564,111 +449,6 @@ func TestNewClientRecordMetrics(t *testing.T) { } } -func TestClientRecordResponseSize(t *testing.T) { - oldAttrs := attribute.NewSet( - attribute.String("http.method", "POST"), - attribute.Int64("http.status_code", 301), - attribute.String("net.peer.name", "example.com"), - ) - - // The OldHTTPClient version - expectedOldScopeMetric := metricdata.ScopeMetrics{ - Scope: instrumentation.Scope{ - Name: "test", - }, - Metrics: []metricdata.Metrics{ - { - Name: "http.client.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, - }, - }, - }, - }, - }, - } - - tests := []struct { - name string - setEnv bool - clientFunc func(metric.MeterProvider) semconv.HTTPClient - wantFunc func(t *testing.T, rm metricdata.ResourceMetrics) - }{ - { - name: "No environment variable set, and no Meter", - setEnv: false, - clientFunc: func(metric.MeterProvider) semconv.HTTPClient { - return semconv.NewHTTPClient(nil) - }, - wantFunc: func(t *testing.T, rm metricdata.ResourceMetrics) { - assert.Empty(t, rm.ScopeMetrics) - }, - }, - { - name: "No environment variable set, but with Meter", - setEnv: false, - clientFunc: func(mp metric.MeterProvider) semconv.HTTPClient { - return semconv.NewHTTPClient(mp.Meter("test")) - }, - wantFunc: func(t *testing.T, rm metricdata.ResourceMetrics) { - require.Empty(t, rm.ScopeMetrics) - }, - }, - { - name: "Set environment variable, but no Meter", - setEnv: true, - clientFunc: func(metric.MeterProvider) semconv.HTTPClient { - return semconv.NewHTTPClient(nil) - }, - wantFunc: func(t *testing.T, rm metricdata.ResourceMetrics) { - assert.Empty(t, rm.ScopeMetrics) - }, - }, - { - name: "Set environment variable and Meter", - setEnv: true, - clientFunc: func(mp metric.MeterProvider) semconv.HTTPClient { - return semconv.NewHTTPClient(mp.Meter("test")) - }, - wantFunc: func(t *testing.T, rm metricdata.ResourceMetrics) { - require.Len(t, rm.ScopeMetrics, 1) - require.Len(t, rm.ScopeMetrics[0].Metrics, 1) - metricdatatest.AssertEqual(t, expectedOldScopeMetric, 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)) - - client := tt.clientFunc(mp) - req, err := http.NewRequest("POST", "http://example.com", nil) - assert.NoError(t, err) - - client.RecordResponseSize(context.Background(), 100, client.MetricOptions(semconv.MetricAttributes{ - Req: req, - StatusCode: 301, - })) - - rm := metricdata.ResourceMetrics{} - require.NoError(t, reader.Collect(context.Background(), &rm)) - tt.wantFunc(t, rm) - }) - } -} - type customError struct{} func (customError) Error() string { diff --git a/instrumentation/github.com/gorilla/mux/otelmux/internal/semconv/v1.20.0.go b/instrumentation/github.com/gorilla/mux/otelmux/internal/semconv/v1.20.0.go deleted file mode 100644 index 35bddbde787..00000000000 --- a/instrumentation/github.com/gorilla/mux/otelmux/internal/semconv/v1.20.0.go +++ /dev/null @@ -1,273 +0,0 @@ -// Code generated by gotmpl. DO NOT MODIFY. -// source: internal/shared/semconv/v120.0.go.tmpl - -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -package semconv // import "go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux/internal/semconv" - -import ( - "errors" - "io" - "net/http" - "slices" - - "go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux/internal/semconvutil" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/metric" - "go.opentelemetry.io/otel/metric/noop" - semconv "go.opentelemetry.io/otel/semconv/v1.20.0" -) - -type OldHTTPServer struct{} - -// RequestTraceAttrs returns trace attributes for an HTTP request received by a -// server. -// -// The server must be the primary server name if it is known. For example this -// would be the ServerName directive -// (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache -// server, and the server_name directive -// (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an -// nginx server. More generically, the primary server name would be the host -// header value that matches the default virtual host of an HTTP server. It -// should include the host identifier and if a port is used to route to the -// server that port identifier should be included as an appropriate port -// suffix. -// -// If the primary server name is not known, server should be an empty string. -// The req Host will be used to determine the server instead. -func (o OldHTTPServer) RequestTraceAttrs(server string, req *http.Request, attrs []attribute.KeyValue) []attribute.KeyValue { - return semconvutil.HTTPServerRequest(server, req, semconvutil.HTTPServerRequestOptions{}, attrs) -} - -func (o OldHTTPServer) NetworkTransportAttr(network string) attribute.KeyValue { - return semconvutil.NetTransport(network) -} - -// ResponseTraceAttrs returns trace attributes for telemetry from an HTTP response. -// -// If any of the fields in the ResponseTelemetry are not set the attribute will be omitted. -func (o OldHTTPServer) ResponseTraceAttrs(resp ResponseTelemetry, attributes []attribute.KeyValue) []attribute.KeyValue { - if resp.ReadBytes > 0 { - attributes = append(attributes, semconv.HTTPRequestContentLength(int(resp.ReadBytes))) - } - if resp.ReadError != nil && !errors.Is(resp.ReadError, io.EOF) { - // This is not in the semantic conventions, but is historically provided - attributes = append(attributes, attribute.String("http.read_error", resp.ReadError.Error())) - } - if resp.WriteBytes > 0 { - attributes = append(attributes, semconv.HTTPResponseContentLength(int(resp.WriteBytes))) - } - if resp.StatusCode > 0 { - attributes = append(attributes, semconv.HTTPStatusCode(resp.StatusCode)) - } - if resp.WriteError != nil && !errors.Is(resp.WriteError, io.EOF) { - // This is not in the semantic conventions, but is historically provided - attributes = append(attributes, attribute.String("http.write_error", resp.WriteError.Error())) - } - - return attributes -} - -// Route returns the attribute for the route. -func (o OldHTTPServer) Route(route string) attribute.KeyValue { - return semconv.HTTPRoute(route) -} - -// HTTPStatusCode returns the attribute for the HTTP status code. -// This is a temporary function needed by metrics. This will be removed when MetricsRequest is added. -func HTTPStatusCode(status int) attribute.KeyValue { - return semconv.HTTPStatusCode(status) -} - -// Server HTTP metrics. -const ( - serverRequestSize = "http.server.request.size" // Incoming request bytes total - serverResponseSize = "http.server.response.size" // Incoming response bytes total - serverDuration = "http.server.duration" // Incoming end to end duration, milliseconds -) - -func (h OldHTTPServer) createMeasures(meter metric.Meter) (metric.Int64Counter, metric.Int64Counter, metric.Float64Histogram) { - if meter == nil { - return noop.Int64Counter{}, noop.Int64Counter{}, noop.Float64Histogram{} - } - var err error - requestBytesCounter, err := meter.Int64Counter( - serverRequestSize, - metric.WithUnit("By"), - metric.WithDescription("Measures the size of HTTP request messages."), - ) - handleErr(err) - - responseBytesCounter, err := meter.Int64Counter( - serverResponseSize, - metric.WithUnit("By"), - metric.WithDescription("Measures the size of HTTP response messages."), - ) - handleErr(err) - - serverLatencyMeasure, err := meter.Float64Histogram( - serverDuration, - metric.WithUnit("ms"), - metric.WithDescription("Measures the duration of inbound HTTP requests."), - ) - handleErr(err) - - return requestBytesCounter, responseBytesCounter, serverLatencyMeasure -} - -func (o OldHTTPServer) MetricAttributes(server string, req *http.Request, statusCode int, additionalAttributes []attribute.KeyValue) []attribute.KeyValue { - n := 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 { - n++ - } - protoName, protoVersion := netProtocol(req.Proto) - if protoName != "" { - n++ - } - if protoVersion != "" { - n++ - } - - if statusCode > 0 { - n++ - } - - attributes := slices.Grow(additionalAttributes, n) - attributes = append(attributes, - semconv.HTTPMethod(standardizeHTTPMethod(req.Method)), - o.scheme(req.TLS != nil), - semconv.NetHostName(host)) - - if hostPort > 0 { - attributes = append(attributes, semconv.NetHostPort(hostPort)) - } - if protoName != "" { - attributes = append(attributes, semconv.NetProtocolName(protoName)) - } - if protoVersion != "" { - attributes = append(attributes, semconv.NetProtocolVersion(protoVersion)) - } - - if statusCode > 0 { - attributes = append(attributes, semconv.HTTPStatusCode(statusCode)) - } - return attributes -} - -func (o OldHTTPServer) scheme(https bool) attribute.KeyValue { // nolint:revive - if https { - return semconv.HTTPSchemeHTTPS - } - return semconv.HTTPSchemeHTTP -} - -type OldHTTPClient struct{} - -func (o OldHTTPClient) RequestTraceAttrs(req *http.Request, attrs []attribute.KeyValue) []attribute.KeyValue { - return semconvutil.HTTPClientRequest(req, attrs) -} - -func (o OldHTTPClient) ResponseTraceAttrs(resp *http.Response, attrs []attribute.KeyValue) []attribute.KeyValue { - return semconvutil.HTTPClientResponse(resp, attrs) -} - -func (o OldHTTPClient) MetricAttributes(req *http.Request, statusCode int, additionalAttributes []attribute.KeyValue) []attribute.KeyValue { - /* The following semantic conventions are returned if present: - http.method string - http.status_code int - net.peer.name string - net.peer.port int - */ - - n := 2 // method, peer name. - var h string - if req.URL != nil { - h = req.URL.Host - } - var requestHost string - var requestPort int - for _, hostport := range []string{h, req.Header.Get("Host")} { - requestHost, requestPort = SplitHostPort(hostport) - if requestHost != "" || requestPort > 0 { - break - } - } - - port := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", requestPort) - if port > 0 { - n++ - } - - if statusCode > 0 { - n++ - } - - attributes := slices.Grow(additionalAttributes, n) - attributes = append(attributes, - semconv.HTTPMethod(standardizeHTTPMethod(req.Method)), - semconv.NetPeerName(requestHost), - ) - - if port > 0 { - attributes = append(attributes, semconv.NetPeerPort(port)) - } - - if statusCode > 0 { - attributes = append(attributes, semconv.HTTPStatusCode(statusCode)) - } - return attributes -} - -// Client HTTP metrics. -const ( - clientRequestSize = "http.client.request.size" // Incoming request bytes total - clientResponseSize = "http.client.response.size" // Incoming response bytes total - clientDuration = "http.client.duration" // Incoming end to end duration, milliseconds -) - -func (o OldHTTPClient) createMeasures(meter metric.Meter) (metric.Int64Counter, metric.Int64Counter, metric.Float64Histogram) { - if meter == nil { - return noop.Int64Counter{}, noop.Int64Counter{}, noop.Float64Histogram{} - } - requestBytesCounter, err := meter.Int64Counter( - clientRequestSize, - metric.WithUnit("By"), - metric.WithDescription("Measures the size of HTTP request messages."), - ) - handleErr(err) - - responseBytesCounter, err := meter.Int64Counter( - clientResponseSize, - metric.WithUnit("By"), - metric.WithDescription("Measures the size of HTTP response messages."), - ) - handleErr(err) - - latencyMeasure, err := meter.Float64Histogram( - clientDuration, - metric.WithUnit("ms"), - metric.WithDescription("Measures the duration of outbound HTTP requests."), - ) - handleErr(err) - - return requestBytesCounter, responseBytesCounter, latencyMeasure -} - -// TraceAttributes returns attributes for httptrace. -func (c OldHTTPClient) TraceAttributes(host string, attrs []attribute.KeyValue) []attribute.KeyValue { - return append(attrs, semconv.NetHostName(host)) -} diff --git a/instrumentation/github.com/gorilla/mux/otelmux/internal/semconvutil/gen.go b/instrumentation/github.com/gorilla/mux/otelmux/internal/semconvutil/gen.go deleted file mode 100644 index 53fa67d4524..00000000000 --- a/instrumentation/github.com/gorilla/mux/otelmux/internal/semconvutil/gen.go +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -package semconvutil // import "go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux/internal/semconvutil" - -// Generate semconvutil package: -//go:generate gotmpl --body=../../../../../../../internal/shared/semconvutil/httpconv_test.go.tmpl "--data={}" --out=httpconv_test.go -//go:generate gotmpl --body=../../../../../../../internal/shared/semconvutil/httpconv.go.tmpl "--data={}" --out=httpconv.go -//go:generate gotmpl --body=../../../../../../../internal/shared/semconvutil/netconv_test.go.tmpl "--data={}" --out=netconv_test.go -//go:generate gotmpl --body=../../../../../../../internal/shared/semconvutil/netconv.go.tmpl "--data={}" --out=netconv.go diff --git a/instrumentation/github.com/gorilla/mux/otelmux/internal/semconvutil/httpconv.go b/instrumentation/github.com/gorilla/mux/otelmux/internal/semconvutil/httpconv.go deleted file mode 100644 index 7316daf66c3..00000000000 --- a/instrumentation/github.com/gorilla/mux/otelmux/internal/semconvutil/httpconv.go +++ /dev/null @@ -1,594 +0,0 @@ -// Code generated by gotmpl. DO NOT MODIFY. -// source: internal/shared/semconvutil/httpconv.go.tmpl - -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -// Package semconvutil provides OpenTelemetry semantic convention utilities. -package semconvutil // import "go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux/internal/semconvutil" - -import ( - "fmt" - "net/http" - "slices" - "strings" - - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/codes" - semconv "go.opentelemetry.io/otel/semconv/v1.20.0" -) - -type HTTPServerRequestOptions struct { - // If set, this is used as value for the "http.client_ip" attribute. - HTTPClientIP string -} - -// HTTPClientResponse returns trace attributes for an HTTP response received by a -// client from a server. It will return the following attributes if the related -// values are defined in resp: "http.status.code", -// "http.response_content_length". -// -// This does not add all OpenTelemetry required attributes for an HTTP event, -// it assumes ClientRequest was used to create the span with a complete set of -// attributes. If a complete set of attributes can be generated using the -// request contained in resp. For example: -// -// HTTPClientResponse(resp, ClientRequest(resp.Request))) -func HTTPClientResponse(resp *http.Response, attrs []attribute.KeyValue) []attribute.KeyValue { - return hc.ClientResponse(resp, attrs) -} - -// HTTPClientRequest returns trace attributes for an HTTP request made by a client. -// The following attributes are always returned: "http.url", "http.method", -// "net.peer.name". The following attributes are returned if the related values -// are defined in req: "net.peer.port", "user_agent.original", -// "http.request_content_length". -func HTTPClientRequest(req *http.Request, attrs []attribute.KeyValue) []attribute.KeyValue { - return hc.ClientRequest(req, attrs) -} - -// HTTPClientRequestMetrics returns metric attributes for an HTTP request made by a client. -// The following attributes are always returned: "http.method", "net.peer.name". -// The following attributes are returned if the -// related values are defined in req: "net.peer.port". -func HTTPClientRequestMetrics(req *http.Request) []attribute.KeyValue { - return hc.ClientRequestMetrics(req) -} - -// HTTPClientStatus returns a span status code and message for an HTTP status code -// value received by a client. -func HTTPClientStatus(code int) (codes.Code, string) { - return hc.ClientStatus(code) -} - -// HTTPServerRequest returns trace attributes for an HTTP request received by a -// server. -// -// The server must be the primary server name if it is known. For example this -// would be the ServerName directive -// (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache -// server, and the server_name directive -// (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an -// nginx server. More generically, the primary server name would be the host -// header value that matches the default virtual host of an HTTP server. It -// should include the host identifier and if a port is used to route to the -// server that port identifier should be included as an appropriate port -// suffix. -// -// If the primary server name is not known, server should be an empty string. -// The req Host will be used to determine the server instead. -// -// The following attributes are always returned: "http.method", "http.scheme", -// "http.target", "net.host.name". The following attributes are returned if -// they related values are defined in req: "net.host.port", "net.sock.peer.addr", -// "net.sock.peer.port", "user_agent.original", "http.client_ip". -func HTTPServerRequest(server string, req *http.Request, opts HTTPServerRequestOptions, attrs []attribute.KeyValue) []attribute.KeyValue { - return hc.ServerRequest(server, req, opts, attrs) -} - -// HTTPServerRequestMetrics returns metric attributes for an HTTP request received by a -// server. -// -// The server must be the primary server name if it is known. For example this -// would be the ServerName directive -// (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache -// server, and the server_name directive -// (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an -// nginx server. More generically, the primary server name would be the host -// header value that matches the default virtual host of an HTTP server. It -// should include the host identifier and if a port is used to route to the -// server that port identifier should be included as an appropriate port -// suffix. -// -// If the primary server name is not known, server should be an empty string. -// The req Host will be used to determine the server instead. -// -// The following attributes are always returned: "http.method", "http.scheme", -// "net.host.name". The following attributes are returned if they related -// values are defined in req: "net.host.port". -func HTTPServerRequestMetrics(server string, req *http.Request) []attribute.KeyValue { - return hc.ServerRequestMetrics(server, req) -} - -// HTTPServerStatus returns a span status code and message for an HTTP status code -// value returned by a server. Status codes in the 400-499 range are not -// returned as errors. -func HTTPServerStatus(code int) (codes.Code, string) { - return hc.ServerStatus(code) -} - -// httpConv are the HTTP semantic convention attributes defined for a version -// of the OpenTelemetry specification. -type httpConv struct { - NetConv *netConv - - HTTPClientIPKey attribute.Key - HTTPMethodKey attribute.Key - HTTPRequestContentLengthKey attribute.Key - HTTPResponseContentLengthKey attribute.Key - HTTPRouteKey attribute.Key - HTTPSchemeHTTP attribute.KeyValue - HTTPSchemeHTTPS attribute.KeyValue - HTTPStatusCodeKey attribute.Key - HTTPTargetKey attribute.Key - HTTPURLKey attribute.Key - UserAgentOriginalKey attribute.Key -} - -var hc = &httpConv{ - NetConv: nc, - - HTTPClientIPKey: semconv.HTTPClientIPKey, - HTTPMethodKey: semconv.HTTPMethodKey, - HTTPRequestContentLengthKey: semconv.HTTPRequestContentLengthKey, - HTTPResponseContentLengthKey: semconv.HTTPResponseContentLengthKey, - HTTPRouteKey: semconv.HTTPRouteKey, - HTTPSchemeHTTP: semconv.HTTPSchemeHTTP, - HTTPSchemeHTTPS: semconv.HTTPSchemeHTTPS, - HTTPStatusCodeKey: semconv.HTTPStatusCodeKey, - HTTPTargetKey: semconv.HTTPTargetKey, - HTTPURLKey: semconv.HTTPURLKey, - UserAgentOriginalKey: semconv.UserAgentOriginalKey, -} - -// ClientResponse returns attributes for an HTTP response received by a client -// from a server. The following attributes are returned if the related values -// are defined in resp: "http.status.code", "http.response_content_length". -// -// This does not add all OpenTelemetry required attributes for an HTTP event, -// it assumes ClientRequest was used to create the span with a complete set of -// attributes. If a complete set of attributes can be generated using the -// request contained in resp. For example: -// -// ClientResponse(resp, ClientRequest(resp.Request)) -func (c *httpConv) ClientResponse(resp *http.Response, attrs []attribute.KeyValue) []attribute.KeyValue { - /* The following semantic conventions are returned if present: - http.status_code int - http.response_content_length int - */ - var n int - if resp.StatusCode > 0 { - n++ - } - if resp.ContentLength > 0 { - n++ - } - if n == 0 { - return attrs - } - - attrs = slices.Grow(attrs, n) - if resp.StatusCode > 0 { - attrs = append(attrs, c.HTTPStatusCodeKey.Int(resp.StatusCode)) - } - if resp.ContentLength > 0 { - attrs = append(attrs, c.HTTPResponseContentLengthKey.Int(int(resp.ContentLength))) - } - return attrs -} - -// ClientRequest returns attributes for an HTTP request made by a client. The -// following attributes are always returned: "http.url", "http.method", -// "net.peer.name". The following attributes are returned if the related values -// are defined in req: "net.peer.port", "user_agent.original", -// "http.request_content_length", "user_agent.original". -func (c *httpConv) ClientRequest(req *http.Request, attrs []attribute.KeyValue) []attribute.KeyValue { - /* The following semantic conventions are returned if present: - http.method string - user_agent.original string - http.url string - net.peer.name string - net.peer.port int - http.request_content_length int - */ - - /* The following semantic conventions are not returned: - http.status_code This requires the response. See ClientResponse. - http.response_content_length This requires the response. See ClientResponse. - net.sock.family This requires the socket used. - net.sock.peer.addr This requires the socket used. - net.sock.peer.name This requires the socket used. - net.sock.peer.port This requires the socket used. - http.resend_count This is something outside of a single request. - net.protocol.name The value is the Request is ignored, and the go client will always use "http". - net.protocol.version The value in the Request is ignored, and the go client will always use 1.1 or 2.0. - */ - n := 3 // URL, peer name, proto, and method. - var h string - if req.URL != nil { - h = req.URL.Host - } - peer, p := firstHostPort(h, req.Header.Get("Host")) - port := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", p) - if port > 0 { - n++ - } - useragent := req.UserAgent() - if useragent != "" { - n++ - } - if req.ContentLength > 0 { - n++ - } - - attrs = slices.Grow(attrs, n) - attrs = append(attrs, c.method(req.Method)) - - var u string - if req.URL != nil { - // Remove any username/password info that may be in the URL. - userinfo := req.URL.User - req.URL.User = nil - u = req.URL.String() - // Restore any username/password info that was removed. - req.URL.User = userinfo - } - attrs = append(attrs, c.HTTPURLKey.String(u)) - - attrs = append(attrs, c.NetConv.PeerName(peer)) - if port > 0 { - attrs = append(attrs, c.NetConv.PeerPort(port)) - } - - if useragent != "" { - attrs = append(attrs, c.UserAgentOriginalKey.String(useragent)) - } - - if l := req.ContentLength; l > 0 { - attrs = append(attrs, c.HTTPRequestContentLengthKey.Int64(l)) - } - - return attrs -} - -// ClientRequestMetrics returns metric attributes for an HTTP request made by a client. The -// following attributes are always returned: "http.method", "net.peer.name". -// The following attributes are returned if the related values -// are defined in req: "net.peer.port". -func (c *httpConv) ClientRequestMetrics(req *http.Request) []attribute.KeyValue { - /* The following semantic conventions are returned if present: - http.method string - net.peer.name string - net.peer.port int - */ - - n := 2 // method, peer name. - var h string - if req.URL != nil { - h = req.URL.Host - } - peer, p := firstHostPort(h, req.Header.Get("Host")) - port := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", p) - if port > 0 { - n++ - } - - attrs := make([]attribute.KeyValue, 0, n) - attrs = append(attrs, c.method(req.Method), c.NetConv.PeerName(peer)) - - if port > 0 { - attrs = append(attrs, c.NetConv.PeerPort(port)) - } - - return attrs -} - -// ServerRequest returns attributes for an HTTP request received by a server. -// -// The server must be the primary server name if it is known. For example this -// would be the ServerName directive -// (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache -// server, and the server_name directive -// (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an -// nginx server. More generically, the primary server name would be the host -// header value that matches the default virtual host of an HTTP server. It -// should include the host identifier and if a port is used to route to the -// server that port identifier should be included as an appropriate port -// suffix. -// -// If the primary server name is not known, server should be an empty string. -// The req Host will be used to determine the server instead. -// -// The following attributes are always returned: "http.method", "http.scheme", -// "http.target", "net.host.name". The following attributes are returned if they -// related values are defined in req: "net.host.port", "net.sock.peer.addr", -// "net.sock.peer.port", "user_agent.original", "http.client_ip", -// "net.protocol.name", "net.protocol.version". -func (c *httpConv) ServerRequest(server string, req *http.Request, opts HTTPServerRequestOptions, attrs []attribute.KeyValue) []attribute.KeyValue { - /* The following semantic conventions are returned if present: - http.method string - http.scheme string - net.host.name string - net.host.port int - net.sock.peer.addr string - net.sock.peer.port int - user_agent.original string - http.client_ip string - net.protocol.name string Note: not set if the value is "http". - net.protocol.version string - http.target string Note: doesn't include the query parameter. - */ - - /* The following semantic conventions are not returned: - http.status_code This requires the response. - http.request_content_length This requires the len() of body, which can mutate it. - http.response_content_length This requires the response. - http.route This is not available. - net.sock.peer.name This would require a DNS lookup. - net.sock.host.addr The request doesn't have access to the underlying socket. - net.sock.host.port The request doesn't have access to the underlying socket. - - */ - n := 4 // Method, scheme, proto, and host name. - 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 { - n++ - } - peer, peerPort := splitHostPort(req.RemoteAddr) - if peer != "" { - n++ - if peerPort > 0 { - n++ - } - } - useragent := req.UserAgent() - if useragent != "" { - n++ - } - - // For client IP, use, in order: - // 1. The value passed in the options - // 2. The value in the X-Forwarded-For header - // 3. The peer address - clientIP := opts.HTTPClientIP - if clientIP == "" { - clientIP = serverClientIP(req.Header.Get("X-Forwarded-For")) - if clientIP == "" { - clientIP = peer - } - } - if clientIP != "" { - n++ - } - - var target string - if req.URL != nil { - target = req.URL.Path - if target != "" { - n++ - } - } - protoName, protoVersion := netProtocol(req.Proto) - if protoName != "" && protoName != "http" { - n++ - } - if protoVersion != "" { - n++ - } - - attrs = slices.Grow(attrs, n) - - attrs = append(attrs, c.method(req.Method)) - attrs = append(attrs, c.scheme(req.TLS != nil)) - attrs = append(attrs, c.NetConv.HostName(host)) - - if hostPort > 0 { - attrs = append(attrs, c.NetConv.HostPort(hostPort)) - } - - if peer != "" { - // The Go HTTP server sets RemoteAddr to "IP:port", this will not be a - // file-path that would be interpreted with a sock family. - attrs = append(attrs, c.NetConv.SockPeerAddr(peer)) - if peerPort > 0 { - attrs = append(attrs, c.NetConv.SockPeerPort(peerPort)) - } - } - - if useragent != "" { - attrs = append(attrs, c.UserAgentOriginalKey.String(useragent)) - } - - if clientIP != "" { - attrs = append(attrs, c.HTTPClientIPKey.String(clientIP)) - } - - if target != "" { - attrs = append(attrs, c.HTTPTargetKey.String(target)) - } - - if protoName != "" && protoName != "http" { - attrs = append(attrs, c.NetConv.NetProtocolName.String(protoName)) - } - if protoVersion != "" { - attrs = append(attrs, c.NetConv.NetProtocolVersion.String(protoVersion)) - } - - return attrs -} - -// ServerRequestMetrics returns metric attributes for an HTTP request received -// by a server. -// -// The server must be the primary server name if it is known. For example this -// would be the ServerName directive -// (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache -// server, and the server_name directive -// (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an -// nginx server. More generically, the primary server name would be the host -// header value that matches the default virtual host of an HTTP server. It -// should include the host identifier and if a port is used to route to the -// server that port identifier should be included as an appropriate port -// suffix. -// -// If the primary server name is not known, server should be an empty string. -// The req Host will be used to determine the server instead. -// -// The following attributes are always returned: "http.method", "http.scheme", -// "net.host.name". The following attributes are returned if they related -// values are defined in req: "net.host.port". -func (c *httpConv) ServerRequestMetrics(server string, req *http.Request) []attribute.KeyValue { - /* The following semantic conventions are returned if present: - http.scheme string - http.route string - http.method string - http.status_code int - net.host.name string - net.host.port int - net.protocol.name string Note: not set if the value is "http". - net.protocol.version string - */ - - n := 3 // Method, scheme, and host name. - 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 { - n++ - } - protoName, protoVersion := netProtocol(req.Proto) - if protoName != "" { - n++ - } - if protoVersion != "" { - n++ - } - - attrs := make([]attribute.KeyValue, 0, n) - - attrs = append(attrs, c.methodMetric(req.Method)) - attrs = append(attrs, c.scheme(req.TLS != nil)) - attrs = append(attrs, c.NetConv.HostName(host)) - - if hostPort > 0 { - attrs = append(attrs, c.NetConv.HostPort(hostPort)) - } - if protoName != "" { - attrs = append(attrs, c.NetConv.NetProtocolName.String(protoName)) - } - if protoVersion != "" { - attrs = append(attrs, c.NetConv.NetProtocolVersion.String(protoVersion)) - } - - return attrs -} - -func (c *httpConv) method(method string) attribute.KeyValue { - if method == "" { - return c.HTTPMethodKey.String(http.MethodGet) - } - return c.HTTPMethodKey.String(method) -} - -func (c *httpConv) methodMetric(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 c.HTTPMethodKey.String(method) -} - -func (c *httpConv) scheme(https bool) attribute.KeyValue { // nolint:revive - if https { - return c.HTTPSchemeHTTPS - } - return c.HTTPSchemeHTTP -} - -func serverClientIP(xForwardedFor string) string { - if idx := strings.Index(xForwardedFor, ","); idx >= 0 { - xForwardedFor = xForwardedFor[:idx] - } - return xForwardedFor -} - -func requiredHTTPPort(https bool, port int) int { // nolint:revive - if https { - if port > 0 && port != 443 { - return port - } - } else { - if port > 0 && port != 80 { - return port - } - } - return -1 -} - -// Return the request host and port from the first non-empty source. -func firstHostPort(source ...string) (host string, port int) { - for _, hostport := range source { - host, port = splitHostPort(hostport) - if host != "" || port > 0 { - break - } - } - return -} - -// ClientStatus returns a span status code and message for an HTTP status code -// value received by a client. -func (c *httpConv) ClientStatus(code int) (codes.Code, string) { - if code < 100 || code >= 600 { - return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code) - } - if code >= 400 { - return codes.Error, "" - } - return codes.Unset, "" -} - -// ServerStatus returns a span status code and message for an HTTP status code -// value returned by a server. Status codes in the 400-499 range are not -// returned as errors. -func (c *httpConv) ServerStatus(code int) (codes.Code, string) { - if code < 100 || code >= 600 { - return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code) - } - if code >= 500 { - return codes.Error, "" - } - return codes.Unset, "" -} diff --git a/instrumentation/github.com/gorilla/mux/otelmux/internal/semconvutil/httpconv_test.go b/instrumentation/github.com/gorilla/mux/otelmux/internal/semconvutil/httpconv_test.go deleted file mode 100644 index ce3a4eb3319..00000000000 --- a/instrumentation/github.com/gorilla/mux/otelmux/internal/semconvutil/httpconv_test.go +++ /dev/null @@ -1,561 +0,0 @@ -// Code generated by gotmpl. DO NOT MODIFY. -// source: internal/shared/semconvutil/httpconv_test.go.tmpl - -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -package semconvutil - -import ( - "net/http" - "net/http/httptest" - "net/url" - "strconv" - "testing" - "time" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/codes" -) - -func TestHTTPClientResponse(t *testing.T) { - const stat, n = 201, 397 - resp := &http.Response{ - StatusCode: stat, - ContentLength: n, - } - got := HTTPClientResponse(resp, nil) - assert.Equal(t, 2, cap(got), "slice capacity") - assert.ElementsMatch(t, []attribute.KeyValue{ - attribute.Key("http.status_code").Int(stat), - attribute.Key("http.response_content_length").Int(n), - }, got) -} - -func TestHTTPSClientRequest(t *testing.T) { - req := &http.Request{ - Method: http.MethodGet, - URL: &url.URL{ - Scheme: "https", - Host: "127.0.0.1:443", - Path: "/resource", - }, - Proto: "HTTP/1.0", - ProtoMajor: 1, - ProtoMinor: 0, - } - - assert.ElementsMatch( - t, - []attribute.KeyValue{ - attribute.String("http.method", "GET"), - attribute.String("http.url", "https://127.0.0.1:443/resource"), - attribute.String("net.peer.name", "127.0.0.1"), - }, - HTTPClientRequest(req, nil), - ) -} - -func TestHTTPSClientRequestMetrics(t *testing.T) { - req := &http.Request{ - Method: http.MethodGet, - URL: &url.URL{ - Scheme: "https", - Host: "127.0.0.1:443", - Path: "/resource", - }, - Proto: "HTTP/1.0", - ProtoMajor: 1, - ProtoMinor: 0, - } - - assert.ElementsMatch( - t, - []attribute.KeyValue{ - attribute.String("http.method", "GET"), - attribute.String("net.peer.name", "127.0.0.1"), - }, - HTTPClientRequestMetrics(req), - ) -} - -func TestHTTPClientRequest(t *testing.T) { - const ( - user = "alice" - n = 128 - agent = "Go-http-client/1.1" - ) - req := &http.Request{ - Method: http.MethodGet, - URL: &url.URL{ - Scheme: "http", - Host: "127.0.0.1:8080", - Path: "/resource", - }, - Proto: "HTTP/1.0", - ProtoMajor: 1, - ProtoMinor: 0, - Header: http.Header{ - "User-Agent": []string{agent}, - }, - ContentLength: n, - } - req.SetBasicAuth(user, "pswrd") - - assert.ElementsMatch( - t, - []attribute.KeyValue{ - attribute.String("http.method", "GET"), - attribute.String("http.url", "http://127.0.0.1:8080/resource"), - attribute.String("net.peer.name", "127.0.0.1"), - attribute.Int("net.peer.port", 8080), - attribute.String("user_agent.original", agent), - attribute.Int("http.request_content_length", n), - }, - HTTPClientRequest(req, nil), - ) -} - -func TestHTTPClientRequestMetrics(t *testing.T) { - const ( - user = "alice" - n = 128 - agent = "Go-http-client/1.1" - ) - req := &http.Request{ - Method: http.MethodGet, - URL: &url.URL{ - Scheme: "http", - Host: "127.0.0.1:8080", - Path: "/resource", - }, - Proto: "HTTP/1.0", - ProtoMajor: 1, - ProtoMinor: 0, - Header: http.Header{ - "User-Agent": []string{agent}, - }, - ContentLength: n, - } - req.SetBasicAuth(user, "pswrd") - - assert.ElementsMatch( - t, - []attribute.KeyValue{ - attribute.String("http.method", "GET"), - attribute.String("net.peer.name", "127.0.0.1"), - attribute.Int("net.peer.port", 8080), - }, - HTTPClientRequestMetrics(req), - ) -} - -func TestHTTPClientRequestRequired(t *testing.T) { - req := new(http.Request) - var got []attribute.KeyValue - assert.NotPanics(t, func() { got = HTTPClientRequest(req, nil) }) - want := []attribute.KeyValue{ - attribute.String("http.method", "GET"), - attribute.String("http.url", ""), - attribute.String("net.peer.name", ""), - } - assert.Equal(t, want, got) -} - -func TestHTTPServerRequest(t *testing.T) { - for _, tt := range []struct { - name string - requestModifierFn func(r *http.Request) - httpServerRequestOpts HTTPServerRequestOptions - - wantClientIP string - }{ - { - name: "with a client IP from the network", - wantClientIP: "1.2.3.4", - }, - { - name: "with a client IP from x-forwarded-for header", - requestModifierFn: func(r *http.Request) { - r.Header.Add("X-Forwarded-For", "5.6.7.8") - }, - wantClientIP: "5.6.7.8", - }, - { - name: "with a client IP in options", - requestModifierFn: func(r *http.Request) { - r.Header.Add("X-Forwarded-For", "5.6.7.8") - }, - httpServerRequestOpts: HTTPServerRequestOptions{ - HTTPClientIP: "9.8.7.6", - }, - wantClientIP: "9.8.7.6", - }, - } { - t.Run(tt.name, func(t *testing.T) { - reqCh := make(chan *http.Request, 1) - handler := func(w http.ResponseWriter, r *http.Request) { - r.RemoteAddr = "1.2.3.4:5678" - reqCh <- r - w.WriteHeader(http.StatusOK) - } - - srv := httptest.NewServer(http.HandlerFunc(handler)) - defer srv.Close() - - srvURL, err := url.Parse(srv.URL) - require.NoError(t, err) - srvPort, err := strconv.ParseInt(srvURL.Port(), 10, 32) - require.NoError(t, err) - - req, err := http.NewRequest(http.MethodGet, srv.URL, nil) - require.NoError(t, err) - - if tt.requestModifierFn != nil { - tt.requestModifierFn(req) - } - - resp, err := srv.Client().Do(req) - require.NoError(t, err) - require.NoError(t, resp.Body.Close()) - - var got *http.Request - select { - case got = <-reqCh: - // All good - case <-time.After(5 * time.Second): - t.Fatal("Did not receive a signal in 5s") - } - - peer, peerPort := splitHostPort(got.RemoteAddr) - - const user = "alice" - got.SetBasicAuth(user, "pswrd") - - assert.ElementsMatch(t, - []attribute.KeyValue{ - attribute.String("http.method", "GET"), - attribute.String("http.scheme", "http"), - attribute.String("net.host.name", srvURL.Hostname()), - attribute.Int("net.host.port", int(srvPort)), - attribute.String("net.sock.peer.addr", peer), - attribute.Int("net.sock.peer.port", peerPort), - attribute.String("user_agent.original", "Go-http-client/1.1"), - attribute.String("http.client_ip", tt.wantClientIP), - attribute.String("net.protocol.version", "1.1"), - attribute.String("http.target", "/"), - }, - HTTPServerRequest("", got, tt.httpServerRequestOpts, nil)) - }) - } -} - -func TestHTTPServerRequestMetrics(t *testing.T) { - got := make(chan *http.Request, 1) - handler := func(w http.ResponseWriter, r *http.Request) { - got <- r - w.WriteHeader(http.StatusOK) - } - - srv := httptest.NewServer(http.HandlerFunc(handler)) - defer srv.Close() - - srvURL, err := url.Parse(srv.URL) - require.NoError(t, err) - srvPort, err := strconv.ParseInt(srvURL.Port(), 10, 32) - require.NoError(t, err) - - resp, err := srv.Client().Get(srv.URL) - require.NoError(t, err) - require.NoError(t, resp.Body.Close()) - - var req *http.Request - select { - case req = <-got: - // All good - case <-time.After(5 * time.Second): - t.Fatal("did not receive a signal in 5s") - } - - assert.ElementsMatch(t, - []attribute.KeyValue{ - attribute.String("http.method", "GET"), - attribute.String("http.scheme", "http"), - attribute.String("net.host.name", srvURL.Hostname()), - attribute.Int("net.host.port", int(srvPort)), - attribute.String("net.protocol.name", "http"), - attribute.String("net.protocol.version", "1.1"), - }, - HTTPServerRequestMetrics("", req)) -} - -func TestHTTPServerName(t *testing.T) { - req := new(http.Request) - var got []attribute.KeyValue - const ( - host = "test.semconv.server" - port = 8080 - ) - portStr := strconv.Itoa(port) - server := host + ":" + portStr - assert.NotPanics(t, func() { got = HTTPServerRequest(server, req, HTTPServerRequestOptions{}, nil) }) - assert.Contains(t, got, attribute.String("net.host.name", host)) - assert.Contains(t, got, attribute.Int("net.host.port", port)) - - req = &http.Request{Host: "alt.host.name:" + portStr} - // The server parameter does not include a port, ServerRequest should use - // the port in the request Host field. - assert.NotPanics(t, func() { got = HTTPServerRequest(host, req, HTTPServerRequestOptions{}, nil) }) - assert.Contains(t, got, attribute.String("net.host.name", host)) - assert.Contains(t, got, attribute.Int("net.host.port", port)) -} - -func TestHTTPServerRequestFailsGracefully(t *testing.T) { - req := new(http.Request) - var got []attribute.KeyValue - assert.NotPanics(t, func() { got = HTTPServerRequest("", req, HTTPServerRequestOptions{}, nil) }) - want := []attribute.KeyValue{ - attribute.String("http.method", "GET"), - attribute.String("http.scheme", "http"), - attribute.String("net.host.name", ""), - } - assert.ElementsMatch(t, want, got) -} - -func TestHTTPMethod(t *testing.T) { - assert.Equal(t, attribute.String("http.method", "POST"), hc.method("POST")) - assert.Equal(t, attribute.String("http.method", "GET"), hc.method("")) - assert.Equal(t, attribute.String("http.method", "garbage"), hc.method("garbage")) -} - -func TestHTTPScheme(t *testing.T) { - assert.Equal(t, attribute.String("http.scheme", "http"), hc.scheme(false)) - assert.Equal(t, attribute.String("http.scheme", "https"), hc.scheme(true)) -} - -func TestHTTPServerClientIP(t *testing.T) { - tests := []struct { - xForwardedFor string - want string - }{ - {"", ""}, - {"127.0.0.1", "127.0.0.1"}, - {"127.0.0.1,127.0.0.5", "127.0.0.1"}, - } - for _, test := range tests { - got := serverClientIP(test.xForwardedFor) - assert.Equal(t, test.want, got, test.xForwardedFor) - } -} - -func TestRequiredHTTPPort(t *testing.T) { - tests := []struct { - https bool - port int - want int - }{ - {true, 443, -1}, - {true, 80, 80}, - {true, 8081, 8081}, - {false, 443, 443}, - {false, 80, -1}, - {false, 8080, 8080}, - } - for _, test := range tests { - got := requiredHTTPPort(test.https, test.port) - assert.Equalf(t, test.want, got, "HTTPS: %t, Port: %d", test.https, test.port) - } -} - -func TestFirstHostPort(t *testing.T) { - host, port := "127.0.0.1", 8080 - hostport := "127.0.0.1:8080" - sources := [][]string{ - {hostport}, - {"", hostport}, - {"", "", hostport}, - {"", "", hostport, ""}, - {"", "", hostport, "127.0.0.3:80"}, - } - - for _, src := range sources { - h, p := firstHostPort(src...) - assert.Equal(t, host, h, "%+v", src) - assert.Equal(t, port, p, "%+v", src) - } -} - -func TestHTTPClientStatus(t *testing.T) { - tests := []struct { - code int - stat codes.Code - msg bool - }{ - {0, codes.Error, true}, - {http.StatusContinue, codes.Unset, false}, - {http.StatusSwitchingProtocols, codes.Unset, false}, - {http.StatusProcessing, codes.Unset, false}, - {http.StatusEarlyHints, codes.Unset, false}, - {http.StatusOK, codes.Unset, false}, - {http.StatusCreated, codes.Unset, false}, - {http.StatusAccepted, codes.Unset, false}, - {http.StatusNonAuthoritativeInfo, codes.Unset, false}, - {http.StatusNoContent, codes.Unset, false}, - {http.StatusResetContent, codes.Unset, false}, - {http.StatusPartialContent, codes.Unset, false}, - {http.StatusMultiStatus, codes.Unset, false}, - {http.StatusAlreadyReported, codes.Unset, false}, - {http.StatusIMUsed, codes.Unset, false}, - {http.StatusMultipleChoices, codes.Unset, false}, - {http.StatusMovedPermanently, codes.Unset, false}, - {http.StatusFound, codes.Unset, false}, - {http.StatusSeeOther, codes.Unset, false}, - {http.StatusNotModified, codes.Unset, false}, - {http.StatusUseProxy, codes.Unset, false}, - {306, codes.Unset, false}, - {http.StatusTemporaryRedirect, codes.Unset, false}, - {http.StatusPermanentRedirect, codes.Unset, false}, - {http.StatusBadRequest, codes.Error, false}, - {http.StatusUnauthorized, codes.Error, false}, - {http.StatusPaymentRequired, codes.Error, false}, - {http.StatusForbidden, codes.Error, false}, - {http.StatusNotFound, codes.Error, false}, - {http.StatusMethodNotAllowed, codes.Error, false}, - {http.StatusNotAcceptable, codes.Error, false}, - {http.StatusProxyAuthRequired, codes.Error, false}, - {http.StatusRequestTimeout, codes.Error, false}, - {http.StatusConflict, codes.Error, false}, - {http.StatusGone, codes.Error, false}, - {http.StatusLengthRequired, codes.Error, false}, - {http.StatusPreconditionFailed, codes.Error, false}, - {http.StatusRequestEntityTooLarge, codes.Error, false}, - {http.StatusRequestURITooLong, codes.Error, false}, - {http.StatusUnsupportedMediaType, codes.Error, false}, - {http.StatusRequestedRangeNotSatisfiable, codes.Error, false}, - {http.StatusExpectationFailed, codes.Error, false}, - {http.StatusTeapot, codes.Error, false}, - {http.StatusMisdirectedRequest, codes.Error, false}, - {http.StatusUnprocessableEntity, codes.Error, false}, - {http.StatusLocked, codes.Error, false}, - {http.StatusFailedDependency, codes.Error, false}, - {http.StatusTooEarly, codes.Error, false}, - {http.StatusUpgradeRequired, codes.Error, false}, - {http.StatusPreconditionRequired, codes.Error, false}, - {http.StatusTooManyRequests, codes.Error, false}, - {http.StatusRequestHeaderFieldsTooLarge, codes.Error, false}, - {http.StatusUnavailableForLegalReasons, codes.Error, false}, - {499, codes.Error, false}, - {http.StatusInternalServerError, codes.Error, false}, - {http.StatusNotImplemented, codes.Error, false}, - {http.StatusBadGateway, codes.Error, false}, - {http.StatusServiceUnavailable, codes.Error, false}, - {http.StatusGatewayTimeout, codes.Error, false}, - {http.StatusHTTPVersionNotSupported, codes.Error, false}, - {http.StatusVariantAlsoNegotiates, codes.Error, false}, - {http.StatusInsufficientStorage, codes.Error, false}, - {http.StatusLoopDetected, codes.Error, false}, - {http.StatusNotExtended, codes.Error, false}, - {http.StatusNetworkAuthenticationRequired, codes.Error, false}, - {600, codes.Error, true}, - } - - for _, test := range tests { - t.Run(strconv.Itoa(test.code), func(t *testing.T) { - c, msg := HTTPClientStatus(test.code) - assert.Equal(t, test.stat, c) - if test.msg && msg == "" { - t.Errorf("expected non-empty message for %d", test.code) - } else if !test.msg && msg != "" { - t.Errorf("expected empty message for %d, got: %s", test.code, msg) - } - }) - } -} - -func TestHTTPServerStatus(t *testing.T) { - tests := []struct { - code int - stat codes.Code - msg bool - }{ - {0, codes.Error, true}, - {http.StatusContinue, codes.Unset, false}, - {http.StatusSwitchingProtocols, codes.Unset, false}, - {http.StatusProcessing, codes.Unset, false}, - {http.StatusEarlyHints, codes.Unset, false}, - {http.StatusOK, codes.Unset, false}, - {http.StatusCreated, codes.Unset, false}, - {http.StatusAccepted, codes.Unset, false}, - {http.StatusNonAuthoritativeInfo, codes.Unset, false}, - {http.StatusNoContent, codes.Unset, false}, - {http.StatusResetContent, codes.Unset, false}, - {http.StatusPartialContent, codes.Unset, false}, - {http.StatusMultiStatus, codes.Unset, false}, - {http.StatusAlreadyReported, codes.Unset, false}, - {http.StatusIMUsed, codes.Unset, false}, - {http.StatusMultipleChoices, codes.Unset, false}, - {http.StatusMovedPermanently, codes.Unset, false}, - {http.StatusFound, codes.Unset, false}, - {http.StatusSeeOther, codes.Unset, false}, - {http.StatusNotModified, codes.Unset, false}, - {http.StatusUseProxy, codes.Unset, false}, - {306, codes.Unset, false}, - {http.StatusTemporaryRedirect, codes.Unset, false}, - {http.StatusPermanentRedirect, codes.Unset, false}, - {http.StatusBadRequest, codes.Unset, false}, - {http.StatusUnauthorized, codes.Unset, false}, - {http.StatusPaymentRequired, codes.Unset, false}, - {http.StatusForbidden, codes.Unset, false}, - {http.StatusNotFound, codes.Unset, false}, - {http.StatusMethodNotAllowed, codes.Unset, false}, - {http.StatusNotAcceptable, codes.Unset, false}, - {http.StatusProxyAuthRequired, codes.Unset, false}, - {http.StatusRequestTimeout, codes.Unset, false}, - {http.StatusConflict, codes.Unset, false}, - {http.StatusGone, codes.Unset, false}, - {http.StatusLengthRequired, codes.Unset, false}, - {http.StatusPreconditionFailed, codes.Unset, false}, - {http.StatusRequestEntityTooLarge, codes.Unset, false}, - {http.StatusRequestURITooLong, codes.Unset, false}, - {http.StatusUnsupportedMediaType, codes.Unset, false}, - {http.StatusRequestedRangeNotSatisfiable, codes.Unset, false}, - {http.StatusExpectationFailed, codes.Unset, false}, - {http.StatusTeapot, codes.Unset, false}, - {http.StatusMisdirectedRequest, codes.Unset, false}, - {http.StatusUnprocessableEntity, codes.Unset, false}, - {http.StatusLocked, codes.Unset, false}, - {http.StatusFailedDependency, codes.Unset, false}, - {http.StatusTooEarly, codes.Unset, false}, - {http.StatusUpgradeRequired, codes.Unset, false}, - {http.StatusPreconditionRequired, codes.Unset, false}, - {http.StatusTooManyRequests, codes.Unset, false}, - {http.StatusRequestHeaderFieldsTooLarge, codes.Unset, false}, - {http.StatusUnavailableForLegalReasons, codes.Unset, false}, - {499, codes.Unset, false}, - {http.StatusInternalServerError, codes.Error, false}, - {http.StatusNotImplemented, codes.Error, false}, - {http.StatusBadGateway, codes.Error, false}, - {http.StatusServiceUnavailable, codes.Error, false}, - {http.StatusGatewayTimeout, codes.Error, false}, - {http.StatusHTTPVersionNotSupported, codes.Error, false}, - {http.StatusVariantAlsoNegotiates, codes.Error, false}, - {http.StatusInsufficientStorage, codes.Error, false}, - {http.StatusLoopDetected, codes.Error, false}, - {http.StatusNotExtended, codes.Error, false}, - {http.StatusNetworkAuthenticationRequired, codes.Error, false}, - {600, codes.Error, true}, - } - - for _, test := range tests { - c, msg := HTTPServerStatus(test.code) - assert.Equal(t, test.stat, c) - if test.msg && msg == "" { - t.Errorf("expected non-empty message for %d", test.code) - } else if !test.msg && msg != "" { - t.Errorf("expected empty message for %d, got: %s", test.code, msg) - } - } -} diff --git a/instrumentation/github.com/gorilla/mux/otelmux/internal/semconvutil/netconv.go b/instrumentation/github.com/gorilla/mux/otelmux/internal/semconvutil/netconv.go deleted file mode 100644 index e7e8f47f7a6..00000000000 --- a/instrumentation/github.com/gorilla/mux/otelmux/internal/semconvutil/netconv.go +++ /dev/null @@ -1,214 +0,0 @@ -// Code generated by gotmpl. DO NOT MODIFY. -// source: internal/shared/semconvutil/netconv.go.tmpl - -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -package semconvutil // import "go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux/internal/semconvutil" - -import ( - "net" - "strconv" - "strings" - - "go.opentelemetry.io/otel/attribute" - semconv "go.opentelemetry.io/otel/semconv/v1.20.0" -) - -// NetTransport returns a trace attribute describing the transport protocol of the -// passed network. See the net.Dial for information about acceptable network -// values. -func NetTransport(network string) attribute.KeyValue { - return nc.Transport(network) -} - -// netConv are the network semantic convention attributes defined for a version -// of the OpenTelemetry specification. -type netConv struct { - NetHostNameKey attribute.Key - NetHostPortKey attribute.Key - NetPeerNameKey attribute.Key - NetPeerPortKey attribute.Key - NetProtocolName attribute.Key - NetProtocolVersion attribute.Key - NetSockFamilyKey attribute.Key - NetSockPeerAddrKey attribute.Key - NetSockPeerPortKey attribute.Key - NetSockHostAddrKey attribute.Key - NetSockHostPortKey attribute.Key - NetTransportOther attribute.KeyValue - NetTransportTCP attribute.KeyValue - NetTransportUDP attribute.KeyValue - NetTransportInProc attribute.KeyValue -} - -var nc = &netConv{ - NetHostNameKey: semconv.NetHostNameKey, - NetHostPortKey: semconv.NetHostPortKey, - NetPeerNameKey: semconv.NetPeerNameKey, - NetPeerPortKey: semconv.NetPeerPortKey, - NetProtocolName: semconv.NetProtocolNameKey, - NetProtocolVersion: semconv.NetProtocolVersionKey, - NetSockFamilyKey: semconv.NetSockFamilyKey, - NetSockPeerAddrKey: semconv.NetSockPeerAddrKey, - NetSockPeerPortKey: semconv.NetSockPeerPortKey, - NetSockHostAddrKey: semconv.NetSockHostAddrKey, - NetSockHostPortKey: semconv.NetSockHostPortKey, - NetTransportOther: semconv.NetTransportOther, - NetTransportTCP: semconv.NetTransportTCP, - NetTransportUDP: semconv.NetTransportUDP, - NetTransportInProc: semconv.NetTransportInProc, -} - -func (c *netConv) Transport(network string) attribute.KeyValue { - switch network { - case "tcp", "tcp4", "tcp6": - return c.NetTransportTCP - case "udp", "udp4", "udp6": - return c.NetTransportUDP - case "unix", "unixgram", "unixpacket": - return c.NetTransportInProc - default: - // "ip:*", "ip4:*", and "ip6:*" all are considered other. - return c.NetTransportOther - } -} - -// Host returns attributes for a network host address. -func (c *netConv) Host(address string) []attribute.KeyValue { - h, p := splitHostPort(address) - var n int - if h != "" { - n++ - if p > 0 { - n++ - } - } - - if n == 0 { - return nil - } - - attrs := make([]attribute.KeyValue, 0, n) - attrs = append(attrs, c.HostName(h)) - if p > 0 { - attrs = append(attrs, c.HostPort(p)) - } - return attrs -} - -func (c *netConv) HostName(name string) attribute.KeyValue { - return c.NetHostNameKey.String(name) -} - -func (c *netConv) HostPort(port int) attribute.KeyValue { - return c.NetHostPortKey.Int(port) -} - -func family(network, address string) string { - switch network { - case "unix", "unixgram", "unixpacket": - return "unix" - default: - if ip := net.ParseIP(address); ip != nil { - if ip.To4() == nil { - return "inet6" - } - return "inet" - } - } - return "" -} - -// Peer returns attributes for a network peer address. -func (c *netConv) Peer(address string) []attribute.KeyValue { - h, p := splitHostPort(address) - var n int - if h != "" { - n++ - if p > 0 { - n++ - } - } - - if n == 0 { - return nil - } - - attrs := make([]attribute.KeyValue, 0, n) - attrs = append(attrs, c.PeerName(h)) - if p > 0 { - attrs = append(attrs, c.PeerPort(p)) - } - return attrs -} - -func (c *netConv) PeerName(name string) attribute.KeyValue { - return c.NetPeerNameKey.String(name) -} - -func (c *netConv) PeerPort(port int) attribute.KeyValue { - return c.NetPeerPortKey.Int(port) -} - -func (c *netConv) SockPeerAddr(addr string) attribute.KeyValue { - return c.NetSockPeerAddrKey.String(addr) -} - -func (c *netConv) SockPeerPort(port int) attribute.KeyValue { - return c.NetSockPeerPortKey.Int(port) -} - -// splitHostPort splits a network address hostport of the form "host", -// "host%zone", "[host]", "[host%zone], "host:port", "host%zone:port", -// "[host]:port", "[host%zone]:port", or ":port" into host or host%zone and -// port. -// -// An empty host is returned if it is not provided or unparsable. A negative -// port is returned if it is not provided or unparsable. -func splitHostPort(hostport string) (host string, port int) { - port = -1 - - if strings.HasPrefix(hostport, "[") { - addrEnd := strings.LastIndex(hostport, "]") - if addrEnd < 0 { - // Invalid hostport. - return - } - if i := strings.LastIndex(hostport[addrEnd:], ":"); i < 0 { - host = hostport[1:addrEnd] - return - } - } else { - if i := strings.LastIndex(hostport, ":"); i < 0 { - host = hostport - return - } - } - - host, pStr, err := net.SplitHostPort(hostport) - if err != nil { - return - } - - p, err := strconv.ParseUint(pStr, 10, 16) - if err != nil { - return - } - return host, int(p) // nolint: gosec // Bitsize checked to be 16 above. -} - -func netProtocol(proto string) (name string, version string) { - name, version, _ = strings.Cut(proto, "/") - switch name { - case "HTTP": - name = "http" - case "QUIC": - name = "quic" - case "SPDY": - name = "spdy" - default: - name = strings.ToLower(name) - } - return name, version -} diff --git a/instrumentation/github.com/gorilla/mux/otelmux/internal/semconvutil/netconv_test.go b/instrumentation/github.com/gorilla/mux/otelmux/internal/semconvutil/netconv_test.go deleted file mode 100644 index 4f3c1540c13..00000000000 --- a/instrumentation/github.com/gorilla/mux/otelmux/internal/semconvutil/netconv_test.go +++ /dev/null @@ -1,200 +0,0 @@ -// Code generated by gotmpl. DO NOT MODIFY. -// source: internal/shared/semconvutil/netconv_test.go.tmpl - -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -package semconvutil - -import ( - "testing" - - "github.com/stretchr/testify/assert" - - "go.opentelemetry.io/otel/attribute" -) - -const ( - addr = "127.0.0.1" - port = 1834 -) - -func TestNetTransport(t *testing.T) { - transports := map[string]attribute.KeyValue{ - "tcp": attribute.String("net.transport", "ip_tcp"), - "tcp4": attribute.String("net.transport", "ip_tcp"), - "tcp6": attribute.String("net.transport", "ip_tcp"), - "udp": attribute.String("net.transport", "ip_udp"), - "udp4": attribute.String("net.transport", "ip_udp"), - "udp6": attribute.String("net.transport", "ip_udp"), - "unix": attribute.String("net.transport", "inproc"), - "unixgram": attribute.String("net.transport", "inproc"), - "unixpacket": attribute.String("net.transport", "inproc"), - "ip:1": attribute.String("net.transport", "other"), - "ip:icmp": attribute.String("net.transport", "other"), - "ip4:proto": attribute.String("net.transport", "other"), - "ip6:proto": attribute.String("net.transport", "other"), - } - - for network, want := range transports { - assert.Equal(t, want, NetTransport(network)) - } -} - -func TestNetHost(t *testing.T) { - testAddrs(t, []addrTest{ - {address: "", expected: nil}, - {address: "192.0.0.1", expected: []attribute.KeyValue{ - nc.HostName("192.0.0.1"), - }}, - {address: "192.0.0.1:9090", expected: []attribute.KeyValue{ - nc.HostName("192.0.0.1"), - nc.HostPort(9090), - }}, - }, nc.Host) -} - -func TestNetHostName(t *testing.T) { - expected := attribute.Key("net.host.name").String(addr) - assert.Equal(t, expected, nc.HostName(addr)) -} - -func TestNetHostPort(t *testing.T) { - expected := attribute.Key("net.host.port").Int(port) - assert.Equal(t, expected, nc.HostPort(port)) -} - -func TestNetPeer(t *testing.T) { - testAddrs(t, []addrTest{ - {address: "", expected: nil}, - {address: "example.com", expected: []attribute.KeyValue{ - nc.PeerName("example.com"), - }}, - {address: "/tmp/file", expected: []attribute.KeyValue{ - nc.PeerName("/tmp/file"), - }}, - {address: "192.0.0.1", expected: []attribute.KeyValue{ - nc.PeerName("192.0.0.1"), - }}, - {address: ":9090", expected: nil}, - {address: "192.0.0.1:9090", expected: []attribute.KeyValue{ - nc.PeerName("192.0.0.1"), - nc.PeerPort(9090), - }}, - }, nc.Peer) -} - -func TestNetPeerName(t *testing.T) { - expected := attribute.Key("net.peer.name").String(addr) - assert.Equal(t, expected, nc.PeerName(addr)) -} - -func TestNetPeerPort(t *testing.T) { - expected := attribute.Key("net.peer.port").Int(port) - assert.Equal(t, expected, nc.PeerPort(port)) -} - -func TestNetSockPeerName(t *testing.T) { - expected := attribute.Key("net.sock.peer.addr").String(addr) - assert.Equal(t, expected, nc.SockPeerAddr(addr)) -} - -func TestNetSockPeerPort(t *testing.T) { - expected := attribute.Key("net.sock.peer.port").Int(port) - assert.Equal(t, expected, nc.SockPeerPort(port)) -} - -func TestNetFamily(t *testing.T) { - tests := []struct { - network string - address string - expect string - }{ - {"", "", ""}, - {"unix", "", "unix"}, - {"unix", "gibberish", "unix"}, - {"unixgram", "", "unix"}, - {"unixgram", "gibberish", "unix"}, - {"unixpacket", "gibberish", "unix"}, - {"tcp", "123.0.2.8", "inet"}, - {"tcp", "gibberish", ""}, - {"", "123.0.2.8", "inet"}, - {"", "gibberish", ""}, - {"tcp", "fe80::1", "inet6"}, - {"", "fe80::1", "inet6"}, - } - - for _, test := range tests { - got := family(test.network, test.address) - assert.Equal(t, test.expect, got, test.network+"/"+test.address) - } -} - -func TestSplitHostPort(t *testing.T) { - tests := []struct { - hostport string - host string - port int - }{ - {"", "", -1}, - {":8080", "", 8080}, - {"127.0.0.1", "127.0.0.1", -1}, - {"www.example.com", "www.example.com", -1}, - {"127.0.0.1%25en0", "127.0.0.1%25en0", -1}, - {"[]", "", -1}, // Ensure this doesn't panic. - {"[fe80::1", "", -1}, - {"[fe80::1]", "fe80::1", -1}, - {"[fe80::1%25en0]", "fe80::1%25en0", -1}, - {"[fe80::1]:8080", "fe80::1", 8080}, - {"[fe80::1]::", "", -1}, // Too many colons. - {"127.0.0.1:", "127.0.0.1", -1}, - {"127.0.0.1:port", "127.0.0.1", -1}, - {"127.0.0.1:8080", "127.0.0.1", 8080}, - {"www.example.com:8080", "www.example.com", 8080}, - {"127.0.0.1%25en0:8080", "127.0.0.1%25en0", 8080}, - } - - for _, test := range tests { - h, p := splitHostPort(test.hostport) - assert.Equal(t, test.host, h, test.hostport) - assert.Equal(t, test.port, p, test.hostport) - } -} - -type addrTest struct { - address string - expected []attribute.KeyValue -} - -func testAddrs(t *testing.T, tests []addrTest, f func(string) []attribute.KeyValue) { - t.Helper() - - for _, test := range tests { - got := f(test.address) - assert.Equal(t, cap(test.expected), cap(got), "slice capacity") - assert.ElementsMatch(t, test.expected, got, test.address) - } -} - -func TestNetProtocol(t *testing.T) { - type testCase struct { - name, version string - } - tests := map[string]testCase{ - "HTTP/1.0": {name: "http", version: "1.0"}, - "HTTP/1.1": {name: "http", version: "1.1"}, - "HTTP/2": {name: "http", version: "2"}, - "HTTP/3": {name: "http", version: "3"}, - "SPDY": {name: "spdy"}, - "SPDY/2": {name: "spdy", version: "2"}, - "QUIC": {name: "quic"}, - "unknown/proto/2": {name: "unknown", version: "proto/2"}, - "other": {name: "other"}, - } - - for proto, want := range tests { - name, version := netProtocol(proto) - assert.Equal(t, want.name, name) - assert.Equal(t, want.version, version) - } -} diff --git a/instrumentation/github.com/labstack/echo/otelecho/internal/semconvutil/gen.go b/instrumentation/github.com/labstack/echo/otelecho/internal/semconvutil/gen.go deleted file mode 100644 index d76f317a6f4..00000000000 --- a/instrumentation/github.com/labstack/echo/otelecho/internal/semconvutil/gen.go +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -package semconvutil // import "go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho/internal/semconvutil" - -// Generate semconvutil package: -//go:generate gotmpl --body=../../../../../../../internal/shared/semconvutil/httpconv_test.go.tmpl "--data={}" --out=httpconv_test.go -//go:generate gotmpl --body=../../../../../../../internal/shared/semconvutil/httpconv.go.tmpl "--data={}" --out=httpconv.go -//go:generate gotmpl --body=../../../../../../../internal/shared/semconvutil/netconv_test.go.tmpl "--data={}" --out=netconv_test.go -//go:generate gotmpl --body=../../../../../../../internal/shared/semconvutil/netconv.go.tmpl "--data={}" --out=netconv.go diff --git a/instrumentation/github.com/labstack/echo/otelecho/internal/semconvutil/httpconv.go b/instrumentation/github.com/labstack/echo/otelecho/internal/semconvutil/httpconv.go deleted file mode 100644 index edfe1459d9d..00000000000 --- a/instrumentation/github.com/labstack/echo/otelecho/internal/semconvutil/httpconv.go +++ /dev/null @@ -1,594 +0,0 @@ -// Code generated by gotmpl. DO NOT MODIFY. -// source: internal/shared/semconvutil/httpconv.go.tmpl - -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -// Package semconvutil provides OpenTelemetry semantic convention utilities. -package semconvutil // import "go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho/internal/semconvutil" - -import ( - "fmt" - "net/http" - "slices" - "strings" - - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/codes" - semconv "go.opentelemetry.io/otel/semconv/v1.20.0" -) - -type HTTPServerRequestOptions struct { - // If set, this is used as value for the "http.client_ip" attribute. - HTTPClientIP string -} - -// HTTPClientResponse returns trace attributes for an HTTP response received by a -// client from a server. It will return the following attributes if the related -// values are defined in resp: "http.status.code", -// "http.response_content_length". -// -// This does not add all OpenTelemetry required attributes for an HTTP event, -// it assumes ClientRequest was used to create the span with a complete set of -// attributes. If a complete set of attributes can be generated using the -// request contained in resp. For example: -// -// HTTPClientResponse(resp, ClientRequest(resp.Request))) -func HTTPClientResponse(resp *http.Response, attrs []attribute.KeyValue) []attribute.KeyValue { - return hc.ClientResponse(resp, attrs) -} - -// HTTPClientRequest returns trace attributes for an HTTP request made by a client. -// The following attributes are always returned: "http.url", "http.method", -// "net.peer.name". The following attributes are returned if the related values -// are defined in req: "net.peer.port", "user_agent.original", -// "http.request_content_length". -func HTTPClientRequest(req *http.Request, attrs []attribute.KeyValue) []attribute.KeyValue { - return hc.ClientRequest(req, attrs) -} - -// HTTPClientRequestMetrics returns metric attributes for an HTTP request made by a client. -// The following attributes are always returned: "http.method", "net.peer.name". -// The following attributes are returned if the -// related values are defined in req: "net.peer.port". -func HTTPClientRequestMetrics(req *http.Request) []attribute.KeyValue { - return hc.ClientRequestMetrics(req) -} - -// HTTPClientStatus returns a span status code and message for an HTTP status code -// value received by a client. -func HTTPClientStatus(code int) (codes.Code, string) { - return hc.ClientStatus(code) -} - -// HTTPServerRequest returns trace attributes for an HTTP request received by a -// server. -// -// The server must be the primary server name if it is known. For example this -// would be the ServerName directive -// (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache -// server, and the server_name directive -// (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an -// nginx server. More generically, the primary server name would be the host -// header value that matches the default virtual host of an HTTP server. It -// should include the host identifier and if a port is used to route to the -// server that port identifier should be included as an appropriate port -// suffix. -// -// If the primary server name is not known, server should be an empty string. -// The req Host will be used to determine the server instead. -// -// The following attributes are always returned: "http.method", "http.scheme", -// "http.target", "net.host.name". The following attributes are returned if -// they related values are defined in req: "net.host.port", "net.sock.peer.addr", -// "net.sock.peer.port", "user_agent.original", "http.client_ip". -func HTTPServerRequest(server string, req *http.Request, opts HTTPServerRequestOptions, attrs []attribute.KeyValue) []attribute.KeyValue { - return hc.ServerRequest(server, req, opts, attrs) -} - -// HTTPServerRequestMetrics returns metric attributes for an HTTP request received by a -// server. -// -// The server must be the primary server name if it is known. For example this -// would be the ServerName directive -// (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache -// server, and the server_name directive -// (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an -// nginx server. More generically, the primary server name would be the host -// header value that matches the default virtual host of an HTTP server. It -// should include the host identifier and if a port is used to route to the -// server that port identifier should be included as an appropriate port -// suffix. -// -// If the primary server name is not known, server should be an empty string. -// The req Host will be used to determine the server instead. -// -// The following attributes are always returned: "http.method", "http.scheme", -// "net.host.name". The following attributes are returned if they related -// values are defined in req: "net.host.port". -func HTTPServerRequestMetrics(server string, req *http.Request) []attribute.KeyValue { - return hc.ServerRequestMetrics(server, req) -} - -// HTTPServerStatus returns a span status code and message for an HTTP status code -// value returned by a server. Status codes in the 400-499 range are not -// returned as errors. -func HTTPServerStatus(code int) (codes.Code, string) { - return hc.ServerStatus(code) -} - -// httpConv are the HTTP semantic convention attributes defined for a version -// of the OpenTelemetry specification. -type httpConv struct { - NetConv *netConv - - HTTPClientIPKey attribute.Key - HTTPMethodKey attribute.Key - HTTPRequestContentLengthKey attribute.Key - HTTPResponseContentLengthKey attribute.Key - HTTPRouteKey attribute.Key - HTTPSchemeHTTP attribute.KeyValue - HTTPSchemeHTTPS attribute.KeyValue - HTTPStatusCodeKey attribute.Key - HTTPTargetKey attribute.Key - HTTPURLKey attribute.Key - UserAgentOriginalKey attribute.Key -} - -var hc = &httpConv{ - NetConv: nc, - - HTTPClientIPKey: semconv.HTTPClientIPKey, - HTTPMethodKey: semconv.HTTPMethodKey, - HTTPRequestContentLengthKey: semconv.HTTPRequestContentLengthKey, - HTTPResponseContentLengthKey: semconv.HTTPResponseContentLengthKey, - HTTPRouteKey: semconv.HTTPRouteKey, - HTTPSchemeHTTP: semconv.HTTPSchemeHTTP, - HTTPSchemeHTTPS: semconv.HTTPSchemeHTTPS, - HTTPStatusCodeKey: semconv.HTTPStatusCodeKey, - HTTPTargetKey: semconv.HTTPTargetKey, - HTTPURLKey: semconv.HTTPURLKey, - UserAgentOriginalKey: semconv.UserAgentOriginalKey, -} - -// ClientResponse returns attributes for an HTTP response received by a client -// from a server. The following attributes are returned if the related values -// are defined in resp: "http.status.code", "http.response_content_length". -// -// This does not add all OpenTelemetry required attributes for an HTTP event, -// it assumes ClientRequest was used to create the span with a complete set of -// attributes. If a complete set of attributes can be generated using the -// request contained in resp. For example: -// -// ClientResponse(resp, ClientRequest(resp.Request)) -func (c *httpConv) ClientResponse(resp *http.Response, attrs []attribute.KeyValue) []attribute.KeyValue { - /* The following semantic conventions are returned if present: - http.status_code int - http.response_content_length int - */ - var n int - if resp.StatusCode > 0 { - n++ - } - if resp.ContentLength > 0 { - n++ - } - if n == 0 { - return attrs - } - - attrs = slices.Grow(attrs, n) - if resp.StatusCode > 0 { - attrs = append(attrs, c.HTTPStatusCodeKey.Int(resp.StatusCode)) - } - if resp.ContentLength > 0 { - attrs = append(attrs, c.HTTPResponseContentLengthKey.Int(int(resp.ContentLength))) - } - return attrs -} - -// ClientRequest returns attributes for an HTTP request made by a client. The -// following attributes are always returned: "http.url", "http.method", -// "net.peer.name". The following attributes are returned if the related values -// are defined in req: "net.peer.port", "user_agent.original", -// "http.request_content_length", "user_agent.original". -func (c *httpConv) ClientRequest(req *http.Request, attrs []attribute.KeyValue) []attribute.KeyValue { - /* The following semantic conventions are returned if present: - http.method string - user_agent.original string - http.url string - net.peer.name string - net.peer.port int - http.request_content_length int - */ - - /* The following semantic conventions are not returned: - http.status_code This requires the response. See ClientResponse. - http.response_content_length This requires the response. See ClientResponse. - net.sock.family This requires the socket used. - net.sock.peer.addr This requires the socket used. - net.sock.peer.name This requires the socket used. - net.sock.peer.port This requires the socket used. - http.resend_count This is something outside of a single request. - net.protocol.name The value is the Request is ignored, and the go client will always use "http". - net.protocol.version The value in the Request is ignored, and the go client will always use 1.1 or 2.0. - */ - n := 3 // URL, peer name, proto, and method. - var h string - if req.URL != nil { - h = req.URL.Host - } - peer, p := firstHostPort(h, req.Header.Get("Host")) - port := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", p) - if port > 0 { - n++ - } - useragent := req.UserAgent() - if useragent != "" { - n++ - } - if req.ContentLength > 0 { - n++ - } - - attrs = slices.Grow(attrs, n) - attrs = append(attrs, c.method(req.Method)) - - var u string - if req.URL != nil { - // Remove any username/password info that may be in the URL. - userinfo := req.URL.User - req.URL.User = nil - u = req.URL.String() - // Restore any username/password info that was removed. - req.URL.User = userinfo - } - attrs = append(attrs, c.HTTPURLKey.String(u)) - - attrs = append(attrs, c.NetConv.PeerName(peer)) - if port > 0 { - attrs = append(attrs, c.NetConv.PeerPort(port)) - } - - if useragent != "" { - attrs = append(attrs, c.UserAgentOriginalKey.String(useragent)) - } - - if l := req.ContentLength; l > 0 { - attrs = append(attrs, c.HTTPRequestContentLengthKey.Int64(l)) - } - - return attrs -} - -// ClientRequestMetrics returns metric attributes for an HTTP request made by a client. The -// following attributes are always returned: "http.method", "net.peer.name". -// The following attributes are returned if the related values -// are defined in req: "net.peer.port". -func (c *httpConv) ClientRequestMetrics(req *http.Request) []attribute.KeyValue { - /* The following semantic conventions are returned if present: - http.method string - net.peer.name string - net.peer.port int - */ - - n := 2 // method, peer name. - var h string - if req.URL != nil { - h = req.URL.Host - } - peer, p := firstHostPort(h, req.Header.Get("Host")) - port := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", p) - if port > 0 { - n++ - } - - attrs := make([]attribute.KeyValue, 0, n) - attrs = append(attrs, c.method(req.Method), c.NetConv.PeerName(peer)) - - if port > 0 { - attrs = append(attrs, c.NetConv.PeerPort(port)) - } - - return attrs -} - -// ServerRequest returns attributes for an HTTP request received by a server. -// -// The server must be the primary server name if it is known. For example this -// would be the ServerName directive -// (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache -// server, and the server_name directive -// (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an -// nginx server. More generically, the primary server name would be the host -// header value that matches the default virtual host of an HTTP server. It -// should include the host identifier and if a port is used to route to the -// server that port identifier should be included as an appropriate port -// suffix. -// -// If the primary server name is not known, server should be an empty string. -// The req Host will be used to determine the server instead. -// -// The following attributes are always returned: "http.method", "http.scheme", -// "http.target", "net.host.name". The following attributes are returned if they -// related values are defined in req: "net.host.port", "net.sock.peer.addr", -// "net.sock.peer.port", "user_agent.original", "http.client_ip", -// "net.protocol.name", "net.protocol.version". -func (c *httpConv) ServerRequest(server string, req *http.Request, opts HTTPServerRequestOptions, attrs []attribute.KeyValue) []attribute.KeyValue { - /* The following semantic conventions are returned if present: - http.method string - http.scheme string - net.host.name string - net.host.port int - net.sock.peer.addr string - net.sock.peer.port int - user_agent.original string - http.client_ip string - net.protocol.name string Note: not set if the value is "http". - net.protocol.version string - http.target string Note: doesn't include the query parameter. - */ - - /* The following semantic conventions are not returned: - http.status_code This requires the response. - http.request_content_length This requires the len() of body, which can mutate it. - http.response_content_length This requires the response. - http.route This is not available. - net.sock.peer.name This would require a DNS lookup. - net.sock.host.addr The request doesn't have access to the underlying socket. - net.sock.host.port The request doesn't have access to the underlying socket. - - */ - n := 4 // Method, scheme, proto, and host name. - 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 { - n++ - } - peer, peerPort := splitHostPort(req.RemoteAddr) - if peer != "" { - n++ - if peerPort > 0 { - n++ - } - } - useragent := req.UserAgent() - if useragent != "" { - n++ - } - - // For client IP, use, in order: - // 1. The value passed in the options - // 2. The value in the X-Forwarded-For header - // 3. The peer address - clientIP := opts.HTTPClientIP - if clientIP == "" { - clientIP = serverClientIP(req.Header.Get("X-Forwarded-For")) - if clientIP == "" { - clientIP = peer - } - } - if clientIP != "" { - n++ - } - - var target string - if req.URL != nil { - target = req.URL.Path - if target != "" { - n++ - } - } - protoName, protoVersion := netProtocol(req.Proto) - if protoName != "" && protoName != "http" { - n++ - } - if protoVersion != "" { - n++ - } - - attrs = slices.Grow(attrs, n) - - attrs = append(attrs, c.method(req.Method)) - attrs = append(attrs, c.scheme(req.TLS != nil)) - attrs = append(attrs, c.NetConv.HostName(host)) - - if hostPort > 0 { - attrs = append(attrs, c.NetConv.HostPort(hostPort)) - } - - if peer != "" { - // The Go HTTP server sets RemoteAddr to "IP:port", this will not be a - // file-path that would be interpreted with a sock family. - attrs = append(attrs, c.NetConv.SockPeerAddr(peer)) - if peerPort > 0 { - attrs = append(attrs, c.NetConv.SockPeerPort(peerPort)) - } - } - - if useragent != "" { - attrs = append(attrs, c.UserAgentOriginalKey.String(useragent)) - } - - if clientIP != "" { - attrs = append(attrs, c.HTTPClientIPKey.String(clientIP)) - } - - if target != "" { - attrs = append(attrs, c.HTTPTargetKey.String(target)) - } - - if protoName != "" && protoName != "http" { - attrs = append(attrs, c.NetConv.NetProtocolName.String(protoName)) - } - if protoVersion != "" { - attrs = append(attrs, c.NetConv.NetProtocolVersion.String(protoVersion)) - } - - return attrs -} - -// ServerRequestMetrics returns metric attributes for an HTTP request received -// by a server. -// -// The server must be the primary server name if it is known. For example this -// would be the ServerName directive -// (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache -// server, and the server_name directive -// (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an -// nginx server. More generically, the primary server name would be the host -// header value that matches the default virtual host of an HTTP server. It -// should include the host identifier and if a port is used to route to the -// server that port identifier should be included as an appropriate port -// suffix. -// -// If the primary server name is not known, server should be an empty string. -// The req Host will be used to determine the server instead. -// -// The following attributes are always returned: "http.method", "http.scheme", -// "net.host.name". The following attributes are returned if they related -// values are defined in req: "net.host.port". -func (c *httpConv) ServerRequestMetrics(server string, req *http.Request) []attribute.KeyValue { - /* The following semantic conventions are returned if present: - http.scheme string - http.route string - http.method string - http.status_code int - net.host.name string - net.host.port int - net.protocol.name string Note: not set if the value is "http". - net.protocol.version string - */ - - n := 3 // Method, scheme, and host name. - 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 { - n++ - } - protoName, protoVersion := netProtocol(req.Proto) - if protoName != "" { - n++ - } - if protoVersion != "" { - n++ - } - - attrs := make([]attribute.KeyValue, 0, n) - - attrs = append(attrs, c.methodMetric(req.Method)) - attrs = append(attrs, c.scheme(req.TLS != nil)) - attrs = append(attrs, c.NetConv.HostName(host)) - - if hostPort > 0 { - attrs = append(attrs, c.NetConv.HostPort(hostPort)) - } - if protoName != "" { - attrs = append(attrs, c.NetConv.NetProtocolName.String(protoName)) - } - if protoVersion != "" { - attrs = append(attrs, c.NetConv.NetProtocolVersion.String(protoVersion)) - } - - return attrs -} - -func (c *httpConv) method(method string) attribute.KeyValue { - if method == "" { - return c.HTTPMethodKey.String(http.MethodGet) - } - return c.HTTPMethodKey.String(method) -} - -func (c *httpConv) methodMetric(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 c.HTTPMethodKey.String(method) -} - -func (c *httpConv) scheme(https bool) attribute.KeyValue { // nolint:revive - if https { - return c.HTTPSchemeHTTPS - } - return c.HTTPSchemeHTTP -} - -func serverClientIP(xForwardedFor string) string { - if idx := strings.Index(xForwardedFor, ","); idx >= 0 { - xForwardedFor = xForwardedFor[:idx] - } - return xForwardedFor -} - -func requiredHTTPPort(https bool, port int) int { // nolint:revive - if https { - if port > 0 && port != 443 { - return port - } - } else { - if port > 0 && port != 80 { - return port - } - } - return -1 -} - -// Return the request host and port from the first non-empty source. -func firstHostPort(source ...string) (host string, port int) { - for _, hostport := range source { - host, port = splitHostPort(hostport) - if host != "" || port > 0 { - break - } - } - return -} - -// ClientStatus returns a span status code and message for an HTTP status code -// value received by a client. -func (c *httpConv) ClientStatus(code int) (codes.Code, string) { - if code < 100 || code >= 600 { - return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code) - } - if code >= 400 { - return codes.Error, "" - } - return codes.Unset, "" -} - -// ServerStatus returns a span status code and message for an HTTP status code -// value returned by a server. Status codes in the 400-499 range are not -// returned as errors. -func (c *httpConv) ServerStatus(code int) (codes.Code, string) { - if code < 100 || code >= 600 { - return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code) - } - if code >= 500 { - return codes.Error, "" - } - return codes.Unset, "" -} diff --git a/instrumentation/github.com/labstack/echo/otelecho/internal/semconvutil/httpconv_test.go b/instrumentation/github.com/labstack/echo/otelecho/internal/semconvutil/httpconv_test.go deleted file mode 100644 index ce3a4eb3319..00000000000 --- a/instrumentation/github.com/labstack/echo/otelecho/internal/semconvutil/httpconv_test.go +++ /dev/null @@ -1,561 +0,0 @@ -// Code generated by gotmpl. DO NOT MODIFY. -// source: internal/shared/semconvutil/httpconv_test.go.tmpl - -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -package semconvutil - -import ( - "net/http" - "net/http/httptest" - "net/url" - "strconv" - "testing" - "time" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/codes" -) - -func TestHTTPClientResponse(t *testing.T) { - const stat, n = 201, 397 - resp := &http.Response{ - StatusCode: stat, - ContentLength: n, - } - got := HTTPClientResponse(resp, nil) - assert.Equal(t, 2, cap(got), "slice capacity") - assert.ElementsMatch(t, []attribute.KeyValue{ - attribute.Key("http.status_code").Int(stat), - attribute.Key("http.response_content_length").Int(n), - }, got) -} - -func TestHTTPSClientRequest(t *testing.T) { - req := &http.Request{ - Method: http.MethodGet, - URL: &url.URL{ - Scheme: "https", - Host: "127.0.0.1:443", - Path: "/resource", - }, - Proto: "HTTP/1.0", - ProtoMajor: 1, - ProtoMinor: 0, - } - - assert.ElementsMatch( - t, - []attribute.KeyValue{ - attribute.String("http.method", "GET"), - attribute.String("http.url", "https://127.0.0.1:443/resource"), - attribute.String("net.peer.name", "127.0.0.1"), - }, - HTTPClientRequest(req, nil), - ) -} - -func TestHTTPSClientRequestMetrics(t *testing.T) { - req := &http.Request{ - Method: http.MethodGet, - URL: &url.URL{ - Scheme: "https", - Host: "127.0.0.1:443", - Path: "/resource", - }, - Proto: "HTTP/1.0", - ProtoMajor: 1, - ProtoMinor: 0, - } - - assert.ElementsMatch( - t, - []attribute.KeyValue{ - attribute.String("http.method", "GET"), - attribute.String("net.peer.name", "127.0.0.1"), - }, - HTTPClientRequestMetrics(req), - ) -} - -func TestHTTPClientRequest(t *testing.T) { - const ( - user = "alice" - n = 128 - agent = "Go-http-client/1.1" - ) - req := &http.Request{ - Method: http.MethodGet, - URL: &url.URL{ - Scheme: "http", - Host: "127.0.0.1:8080", - Path: "/resource", - }, - Proto: "HTTP/1.0", - ProtoMajor: 1, - ProtoMinor: 0, - Header: http.Header{ - "User-Agent": []string{agent}, - }, - ContentLength: n, - } - req.SetBasicAuth(user, "pswrd") - - assert.ElementsMatch( - t, - []attribute.KeyValue{ - attribute.String("http.method", "GET"), - attribute.String("http.url", "http://127.0.0.1:8080/resource"), - attribute.String("net.peer.name", "127.0.0.1"), - attribute.Int("net.peer.port", 8080), - attribute.String("user_agent.original", agent), - attribute.Int("http.request_content_length", n), - }, - HTTPClientRequest(req, nil), - ) -} - -func TestHTTPClientRequestMetrics(t *testing.T) { - const ( - user = "alice" - n = 128 - agent = "Go-http-client/1.1" - ) - req := &http.Request{ - Method: http.MethodGet, - URL: &url.URL{ - Scheme: "http", - Host: "127.0.0.1:8080", - Path: "/resource", - }, - Proto: "HTTP/1.0", - ProtoMajor: 1, - ProtoMinor: 0, - Header: http.Header{ - "User-Agent": []string{agent}, - }, - ContentLength: n, - } - req.SetBasicAuth(user, "pswrd") - - assert.ElementsMatch( - t, - []attribute.KeyValue{ - attribute.String("http.method", "GET"), - attribute.String("net.peer.name", "127.0.0.1"), - attribute.Int("net.peer.port", 8080), - }, - HTTPClientRequestMetrics(req), - ) -} - -func TestHTTPClientRequestRequired(t *testing.T) { - req := new(http.Request) - var got []attribute.KeyValue - assert.NotPanics(t, func() { got = HTTPClientRequest(req, nil) }) - want := []attribute.KeyValue{ - attribute.String("http.method", "GET"), - attribute.String("http.url", ""), - attribute.String("net.peer.name", ""), - } - assert.Equal(t, want, got) -} - -func TestHTTPServerRequest(t *testing.T) { - for _, tt := range []struct { - name string - requestModifierFn func(r *http.Request) - httpServerRequestOpts HTTPServerRequestOptions - - wantClientIP string - }{ - { - name: "with a client IP from the network", - wantClientIP: "1.2.3.4", - }, - { - name: "with a client IP from x-forwarded-for header", - requestModifierFn: func(r *http.Request) { - r.Header.Add("X-Forwarded-For", "5.6.7.8") - }, - wantClientIP: "5.6.7.8", - }, - { - name: "with a client IP in options", - requestModifierFn: func(r *http.Request) { - r.Header.Add("X-Forwarded-For", "5.6.7.8") - }, - httpServerRequestOpts: HTTPServerRequestOptions{ - HTTPClientIP: "9.8.7.6", - }, - wantClientIP: "9.8.7.6", - }, - } { - t.Run(tt.name, func(t *testing.T) { - reqCh := make(chan *http.Request, 1) - handler := func(w http.ResponseWriter, r *http.Request) { - r.RemoteAddr = "1.2.3.4:5678" - reqCh <- r - w.WriteHeader(http.StatusOK) - } - - srv := httptest.NewServer(http.HandlerFunc(handler)) - defer srv.Close() - - srvURL, err := url.Parse(srv.URL) - require.NoError(t, err) - srvPort, err := strconv.ParseInt(srvURL.Port(), 10, 32) - require.NoError(t, err) - - req, err := http.NewRequest(http.MethodGet, srv.URL, nil) - require.NoError(t, err) - - if tt.requestModifierFn != nil { - tt.requestModifierFn(req) - } - - resp, err := srv.Client().Do(req) - require.NoError(t, err) - require.NoError(t, resp.Body.Close()) - - var got *http.Request - select { - case got = <-reqCh: - // All good - case <-time.After(5 * time.Second): - t.Fatal("Did not receive a signal in 5s") - } - - peer, peerPort := splitHostPort(got.RemoteAddr) - - const user = "alice" - got.SetBasicAuth(user, "pswrd") - - assert.ElementsMatch(t, - []attribute.KeyValue{ - attribute.String("http.method", "GET"), - attribute.String("http.scheme", "http"), - attribute.String("net.host.name", srvURL.Hostname()), - attribute.Int("net.host.port", int(srvPort)), - attribute.String("net.sock.peer.addr", peer), - attribute.Int("net.sock.peer.port", peerPort), - attribute.String("user_agent.original", "Go-http-client/1.1"), - attribute.String("http.client_ip", tt.wantClientIP), - attribute.String("net.protocol.version", "1.1"), - attribute.String("http.target", "/"), - }, - HTTPServerRequest("", got, tt.httpServerRequestOpts, nil)) - }) - } -} - -func TestHTTPServerRequestMetrics(t *testing.T) { - got := make(chan *http.Request, 1) - handler := func(w http.ResponseWriter, r *http.Request) { - got <- r - w.WriteHeader(http.StatusOK) - } - - srv := httptest.NewServer(http.HandlerFunc(handler)) - defer srv.Close() - - srvURL, err := url.Parse(srv.URL) - require.NoError(t, err) - srvPort, err := strconv.ParseInt(srvURL.Port(), 10, 32) - require.NoError(t, err) - - resp, err := srv.Client().Get(srv.URL) - require.NoError(t, err) - require.NoError(t, resp.Body.Close()) - - var req *http.Request - select { - case req = <-got: - // All good - case <-time.After(5 * time.Second): - t.Fatal("did not receive a signal in 5s") - } - - assert.ElementsMatch(t, - []attribute.KeyValue{ - attribute.String("http.method", "GET"), - attribute.String("http.scheme", "http"), - attribute.String("net.host.name", srvURL.Hostname()), - attribute.Int("net.host.port", int(srvPort)), - attribute.String("net.protocol.name", "http"), - attribute.String("net.protocol.version", "1.1"), - }, - HTTPServerRequestMetrics("", req)) -} - -func TestHTTPServerName(t *testing.T) { - req := new(http.Request) - var got []attribute.KeyValue - const ( - host = "test.semconv.server" - port = 8080 - ) - portStr := strconv.Itoa(port) - server := host + ":" + portStr - assert.NotPanics(t, func() { got = HTTPServerRequest(server, req, HTTPServerRequestOptions{}, nil) }) - assert.Contains(t, got, attribute.String("net.host.name", host)) - assert.Contains(t, got, attribute.Int("net.host.port", port)) - - req = &http.Request{Host: "alt.host.name:" + portStr} - // The server parameter does not include a port, ServerRequest should use - // the port in the request Host field. - assert.NotPanics(t, func() { got = HTTPServerRequest(host, req, HTTPServerRequestOptions{}, nil) }) - assert.Contains(t, got, attribute.String("net.host.name", host)) - assert.Contains(t, got, attribute.Int("net.host.port", port)) -} - -func TestHTTPServerRequestFailsGracefully(t *testing.T) { - req := new(http.Request) - var got []attribute.KeyValue - assert.NotPanics(t, func() { got = HTTPServerRequest("", req, HTTPServerRequestOptions{}, nil) }) - want := []attribute.KeyValue{ - attribute.String("http.method", "GET"), - attribute.String("http.scheme", "http"), - attribute.String("net.host.name", ""), - } - assert.ElementsMatch(t, want, got) -} - -func TestHTTPMethod(t *testing.T) { - assert.Equal(t, attribute.String("http.method", "POST"), hc.method("POST")) - assert.Equal(t, attribute.String("http.method", "GET"), hc.method("")) - assert.Equal(t, attribute.String("http.method", "garbage"), hc.method("garbage")) -} - -func TestHTTPScheme(t *testing.T) { - assert.Equal(t, attribute.String("http.scheme", "http"), hc.scheme(false)) - assert.Equal(t, attribute.String("http.scheme", "https"), hc.scheme(true)) -} - -func TestHTTPServerClientIP(t *testing.T) { - tests := []struct { - xForwardedFor string - want string - }{ - {"", ""}, - {"127.0.0.1", "127.0.0.1"}, - {"127.0.0.1,127.0.0.5", "127.0.0.1"}, - } - for _, test := range tests { - got := serverClientIP(test.xForwardedFor) - assert.Equal(t, test.want, got, test.xForwardedFor) - } -} - -func TestRequiredHTTPPort(t *testing.T) { - tests := []struct { - https bool - port int - want int - }{ - {true, 443, -1}, - {true, 80, 80}, - {true, 8081, 8081}, - {false, 443, 443}, - {false, 80, -1}, - {false, 8080, 8080}, - } - for _, test := range tests { - got := requiredHTTPPort(test.https, test.port) - assert.Equalf(t, test.want, got, "HTTPS: %t, Port: %d", test.https, test.port) - } -} - -func TestFirstHostPort(t *testing.T) { - host, port := "127.0.0.1", 8080 - hostport := "127.0.0.1:8080" - sources := [][]string{ - {hostport}, - {"", hostport}, - {"", "", hostport}, - {"", "", hostport, ""}, - {"", "", hostport, "127.0.0.3:80"}, - } - - for _, src := range sources { - h, p := firstHostPort(src...) - assert.Equal(t, host, h, "%+v", src) - assert.Equal(t, port, p, "%+v", src) - } -} - -func TestHTTPClientStatus(t *testing.T) { - tests := []struct { - code int - stat codes.Code - msg bool - }{ - {0, codes.Error, true}, - {http.StatusContinue, codes.Unset, false}, - {http.StatusSwitchingProtocols, codes.Unset, false}, - {http.StatusProcessing, codes.Unset, false}, - {http.StatusEarlyHints, codes.Unset, false}, - {http.StatusOK, codes.Unset, false}, - {http.StatusCreated, codes.Unset, false}, - {http.StatusAccepted, codes.Unset, false}, - {http.StatusNonAuthoritativeInfo, codes.Unset, false}, - {http.StatusNoContent, codes.Unset, false}, - {http.StatusResetContent, codes.Unset, false}, - {http.StatusPartialContent, codes.Unset, false}, - {http.StatusMultiStatus, codes.Unset, false}, - {http.StatusAlreadyReported, codes.Unset, false}, - {http.StatusIMUsed, codes.Unset, false}, - {http.StatusMultipleChoices, codes.Unset, false}, - {http.StatusMovedPermanently, codes.Unset, false}, - {http.StatusFound, codes.Unset, false}, - {http.StatusSeeOther, codes.Unset, false}, - {http.StatusNotModified, codes.Unset, false}, - {http.StatusUseProxy, codes.Unset, false}, - {306, codes.Unset, false}, - {http.StatusTemporaryRedirect, codes.Unset, false}, - {http.StatusPermanentRedirect, codes.Unset, false}, - {http.StatusBadRequest, codes.Error, false}, - {http.StatusUnauthorized, codes.Error, false}, - {http.StatusPaymentRequired, codes.Error, false}, - {http.StatusForbidden, codes.Error, false}, - {http.StatusNotFound, codes.Error, false}, - {http.StatusMethodNotAllowed, codes.Error, false}, - {http.StatusNotAcceptable, codes.Error, false}, - {http.StatusProxyAuthRequired, codes.Error, false}, - {http.StatusRequestTimeout, codes.Error, false}, - {http.StatusConflict, codes.Error, false}, - {http.StatusGone, codes.Error, false}, - {http.StatusLengthRequired, codes.Error, false}, - {http.StatusPreconditionFailed, codes.Error, false}, - {http.StatusRequestEntityTooLarge, codes.Error, false}, - {http.StatusRequestURITooLong, codes.Error, false}, - {http.StatusUnsupportedMediaType, codes.Error, false}, - {http.StatusRequestedRangeNotSatisfiable, codes.Error, false}, - {http.StatusExpectationFailed, codes.Error, false}, - {http.StatusTeapot, codes.Error, false}, - {http.StatusMisdirectedRequest, codes.Error, false}, - {http.StatusUnprocessableEntity, codes.Error, false}, - {http.StatusLocked, codes.Error, false}, - {http.StatusFailedDependency, codes.Error, false}, - {http.StatusTooEarly, codes.Error, false}, - {http.StatusUpgradeRequired, codes.Error, false}, - {http.StatusPreconditionRequired, codes.Error, false}, - {http.StatusTooManyRequests, codes.Error, false}, - {http.StatusRequestHeaderFieldsTooLarge, codes.Error, false}, - {http.StatusUnavailableForLegalReasons, codes.Error, false}, - {499, codes.Error, false}, - {http.StatusInternalServerError, codes.Error, false}, - {http.StatusNotImplemented, codes.Error, false}, - {http.StatusBadGateway, codes.Error, false}, - {http.StatusServiceUnavailable, codes.Error, false}, - {http.StatusGatewayTimeout, codes.Error, false}, - {http.StatusHTTPVersionNotSupported, codes.Error, false}, - {http.StatusVariantAlsoNegotiates, codes.Error, false}, - {http.StatusInsufficientStorage, codes.Error, false}, - {http.StatusLoopDetected, codes.Error, false}, - {http.StatusNotExtended, codes.Error, false}, - {http.StatusNetworkAuthenticationRequired, codes.Error, false}, - {600, codes.Error, true}, - } - - for _, test := range tests { - t.Run(strconv.Itoa(test.code), func(t *testing.T) { - c, msg := HTTPClientStatus(test.code) - assert.Equal(t, test.stat, c) - if test.msg && msg == "" { - t.Errorf("expected non-empty message for %d", test.code) - } else if !test.msg && msg != "" { - t.Errorf("expected empty message for %d, got: %s", test.code, msg) - } - }) - } -} - -func TestHTTPServerStatus(t *testing.T) { - tests := []struct { - code int - stat codes.Code - msg bool - }{ - {0, codes.Error, true}, - {http.StatusContinue, codes.Unset, false}, - {http.StatusSwitchingProtocols, codes.Unset, false}, - {http.StatusProcessing, codes.Unset, false}, - {http.StatusEarlyHints, codes.Unset, false}, - {http.StatusOK, codes.Unset, false}, - {http.StatusCreated, codes.Unset, false}, - {http.StatusAccepted, codes.Unset, false}, - {http.StatusNonAuthoritativeInfo, codes.Unset, false}, - {http.StatusNoContent, codes.Unset, false}, - {http.StatusResetContent, codes.Unset, false}, - {http.StatusPartialContent, codes.Unset, false}, - {http.StatusMultiStatus, codes.Unset, false}, - {http.StatusAlreadyReported, codes.Unset, false}, - {http.StatusIMUsed, codes.Unset, false}, - {http.StatusMultipleChoices, codes.Unset, false}, - {http.StatusMovedPermanently, codes.Unset, false}, - {http.StatusFound, codes.Unset, false}, - {http.StatusSeeOther, codes.Unset, false}, - {http.StatusNotModified, codes.Unset, false}, - {http.StatusUseProxy, codes.Unset, false}, - {306, codes.Unset, false}, - {http.StatusTemporaryRedirect, codes.Unset, false}, - {http.StatusPermanentRedirect, codes.Unset, false}, - {http.StatusBadRequest, codes.Unset, false}, - {http.StatusUnauthorized, codes.Unset, false}, - {http.StatusPaymentRequired, codes.Unset, false}, - {http.StatusForbidden, codes.Unset, false}, - {http.StatusNotFound, codes.Unset, false}, - {http.StatusMethodNotAllowed, codes.Unset, false}, - {http.StatusNotAcceptable, codes.Unset, false}, - {http.StatusProxyAuthRequired, codes.Unset, false}, - {http.StatusRequestTimeout, codes.Unset, false}, - {http.StatusConflict, codes.Unset, false}, - {http.StatusGone, codes.Unset, false}, - {http.StatusLengthRequired, codes.Unset, false}, - {http.StatusPreconditionFailed, codes.Unset, false}, - {http.StatusRequestEntityTooLarge, codes.Unset, false}, - {http.StatusRequestURITooLong, codes.Unset, false}, - {http.StatusUnsupportedMediaType, codes.Unset, false}, - {http.StatusRequestedRangeNotSatisfiable, codes.Unset, false}, - {http.StatusExpectationFailed, codes.Unset, false}, - {http.StatusTeapot, codes.Unset, false}, - {http.StatusMisdirectedRequest, codes.Unset, false}, - {http.StatusUnprocessableEntity, codes.Unset, false}, - {http.StatusLocked, codes.Unset, false}, - {http.StatusFailedDependency, codes.Unset, false}, - {http.StatusTooEarly, codes.Unset, false}, - {http.StatusUpgradeRequired, codes.Unset, false}, - {http.StatusPreconditionRequired, codes.Unset, false}, - {http.StatusTooManyRequests, codes.Unset, false}, - {http.StatusRequestHeaderFieldsTooLarge, codes.Unset, false}, - {http.StatusUnavailableForLegalReasons, codes.Unset, false}, - {499, codes.Unset, false}, - {http.StatusInternalServerError, codes.Error, false}, - {http.StatusNotImplemented, codes.Error, false}, - {http.StatusBadGateway, codes.Error, false}, - {http.StatusServiceUnavailable, codes.Error, false}, - {http.StatusGatewayTimeout, codes.Error, false}, - {http.StatusHTTPVersionNotSupported, codes.Error, false}, - {http.StatusVariantAlsoNegotiates, codes.Error, false}, - {http.StatusInsufficientStorage, codes.Error, false}, - {http.StatusLoopDetected, codes.Error, false}, - {http.StatusNotExtended, codes.Error, false}, - {http.StatusNetworkAuthenticationRequired, codes.Error, false}, - {600, codes.Error, true}, - } - - for _, test := range tests { - c, msg := HTTPServerStatus(test.code) - assert.Equal(t, test.stat, c) - if test.msg && msg == "" { - t.Errorf("expected non-empty message for %d", test.code) - } else if !test.msg && msg != "" { - t.Errorf("expected empty message for %d, got: %s", test.code, msg) - } - } -} diff --git a/instrumentation/github.com/labstack/echo/otelecho/internal/semconvutil/netconv.go b/instrumentation/github.com/labstack/echo/otelecho/internal/semconvutil/netconv.go deleted file mode 100644 index 22d4636ce7e..00000000000 --- a/instrumentation/github.com/labstack/echo/otelecho/internal/semconvutil/netconv.go +++ /dev/null @@ -1,214 +0,0 @@ -// Code generated by gotmpl. DO NOT MODIFY. -// source: internal/shared/semconvutil/netconv.go.tmpl - -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -package semconvutil // import "go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho/internal/semconvutil" - -import ( - "net" - "strconv" - "strings" - - "go.opentelemetry.io/otel/attribute" - semconv "go.opentelemetry.io/otel/semconv/v1.20.0" -) - -// NetTransport returns a trace attribute describing the transport protocol of the -// passed network. See the net.Dial for information about acceptable network -// values. -func NetTransport(network string) attribute.KeyValue { - return nc.Transport(network) -} - -// netConv are the network semantic convention attributes defined for a version -// of the OpenTelemetry specification. -type netConv struct { - NetHostNameKey attribute.Key - NetHostPortKey attribute.Key - NetPeerNameKey attribute.Key - NetPeerPortKey attribute.Key - NetProtocolName attribute.Key - NetProtocolVersion attribute.Key - NetSockFamilyKey attribute.Key - NetSockPeerAddrKey attribute.Key - NetSockPeerPortKey attribute.Key - NetSockHostAddrKey attribute.Key - NetSockHostPortKey attribute.Key - NetTransportOther attribute.KeyValue - NetTransportTCP attribute.KeyValue - NetTransportUDP attribute.KeyValue - NetTransportInProc attribute.KeyValue -} - -var nc = &netConv{ - NetHostNameKey: semconv.NetHostNameKey, - NetHostPortKey: semconv.NetHostPortKey, - NetPeerNameKey: semconv.NetPeerNameKey, - NetPeerPortKey: semconv.NetPeerPortKey, - NetProtocolName: semconv.NetProtocolNameKey, - NetProtocolVersion: semconv.NetProtocolVersionKey, - NetSockFamilyKey: semconv.NetSockFamilyKey, - NetSockPeerAddrKey: semconv.NetSockPeerAddrKey, - NetSockPeerPortKey: semconv.NetSockPeerPortKey, - NetSockHostAddrKey: semconv.NetSockHostAddrKey, - NetSockHostPortKey: semconv.NetSockHostPortKey, - NetTransportOther: semconv.NetTransportOther, - NetTransportTCP: semconv.NetTransportTCP, - NetTransportUDP: semconv.NetTransportUDP, - NetTransportInProc: semconv.NetTransportInProc, -} - -func (c *netConv) Transport(network string) attribute.KeyValue { - switch network { - case "tcp", "tcp4", "tcp6": - return c.NetTransportTCP - case "udp", "udp4", "udp6": - return c.NetTransportUDP - case "unix", "unixgram", "unixpacket": - return c.NetTransportInProc - default: - // "ip:*", "ip4:*", and "ip6:*" all are considered other. - return c.NetTransportOther - } -} - -// Host returns attributes for a network host address. -func (c *netConv) Host(address string) []attribute.KeyValue { - h, p := splitHostPort(address) - var n int - if h != "" { - n++ - if p > 0 { - n++ - } - } - - if n == 0 { - return nil - } - - attrs := make([]attribute.KeyValue, 0, n) - attrs = append(attrs, c.HostName(h)) - if p > 0 { - attrs = append(attrs, c.HostPort(p)) - } - return attrs -} - -func (c *netConv) HostName(name string) attribute.KeyValue { - return c.NetHostNameKey.String(name) -} - -func (c *netConv) HostPort(port int) attribute.KeyValue { - return c.NetHostPortKey.Int(port) -} - -func family(network, address string) string { - switch network { - case "unix", "unixgram", "unixpacket": - return "unix" - default: - if ip := net.ParseIP(address); ip != nil { - if ip.To4() == nil { - return "inet6" - } - return "inet" - } - } - return "" -} - -// Peer returns attributes for a network peer address. -func (c *netConv) Peer(address string) []attribute.KeyValue { - h, p := splitHostPort(address) - var n int - if h != "" { - n++ - if p > 0 { - n++ - } - } - - if n == 0 { - return nil - } - - attrs := make([]attribute.KeyValue, 0, n) - attrs = append(attrs, c.PeerName(h)) - if p > 0 { - attrs = append(attrs, c.PeerPort(p)) - } - return attrs -} - -func (c *netConv) PeerName(name string) attribute.KeyValue { - return c.NetPeerNameKey.String(name) -} - -func (c *netConv) PeerPort(port int) attribute.KeyValue { - return c.NetPeerPortKey.Int(port) -} - -func (c *netConv) SockPeerAddr(addr string) attribute.KeyValue { - return c.NetSockPeerAddrKey.String(addr) -} - -func (c *netConv) SockPeerPort(port int) attribute.KeyValue { - return c.NetSockPeerPortKey.Int(port) -} - -// splitHostPort splits a network address hostport of the form "host", -// "host%zone", "[host]", "[host%zone], "host:port", "host%zone:port", -// "[host]:port", "[host%zone]:port", or ":port" into host or host%zone and -// port. -// -// An empty host is returned if it is not provided or unparsable. A negative -// port is returned if it is not provided or unparsable. -func splitHostPort(hostport string) (host string, port int) { - port = -1 - - if strings.HasPrefix(hostport, "[") { - addrEnd := strings.LastIndex(hostport, "]") - if addrEnd < 0 { - // Invalid hostport. - return - } - if i := strings.LastIndex(hostport[addrEnd:], ":"); i < 0 { - host = hostport[1:addrEnd] - return - } - } else { - if i := strings.LastIndex(hostport, ":"); i < 0 { - host = hostport - return - } - } - - host, pStr, err := net.SplitHostPort(hostport) - if err != nil { - return - } - - p, err := strconv.ParseUint(pStr, 10, 16) - if err != nil { - return - } - return host, int(p) // nolint: gosec // Bitsize checked to be 16 above. -} - -func netProtocol(proto string) (name string, version string) { - name, version, _ = strings.Cut(proto, "/") - switch name { - case "HTTP": - name = "http" - case "QUIC": - name = "quic" - case "SPDY": - name = "spdy" - default: - name = strings.ToLower(name) - } - return name, version -} diff --git a/instrumentation/github.com/labstack/echo/otelecho/internal/semconvutil/netconv_test.go b/instrumentation/github.com/labstack/echo/otelecho/internal/semconvutil/netconv_test.go deleted file mode 100644 index 4f3c1540c13..00000000000 --- a/instrumentation/github.com/labstack/echo/otelecho/internal/semconvutil/netconv_test.go +++ /dev/null @@ -1,200 +0,0 @@ -// Code generated by gotmpl. DO NOT MODIFY. -// source: internal/shared/semconvutil/netconv_test.go.tmpl - -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -package semconvutil - -import ( - "testing" - - "github.com/stretchr/testify/assert" - - "go.opentelemetry.io/otel/attribute" -) - -const ( - addr = "127.0.0.1" - port = 1834 -) - -func TestNetTransport(t *testing.T) { - transports := map[string]attribute.KeyValue{ - "tcp": attribute.String("net.transport", "ip_tcp"), - "tcp4": attribute.String("net.transport", "ip_tcp"), - "tcp6": attribute.String("net.transport", "ip_tcp"), - "udp": attribute.String("net.transport", "ip_udp"), - "udp4": attribute.String("net.transport", "ip_udp"), - "udp6": attribute.String("net.transport", "ip_udp"), - "unix": attribute.String("net.transport", "inproc"), - "unixgram": attribute.String("net.transport", "inproc"), - "unixpacket": attribute.String("net.transport", "inproc"), - "ip:1": attribute.String("net.transport", "other"), - "ip:icmp": attribute.String("net.transport", "other"), - "ip4:proto": attribute.String("net.transport", "other"), - "ip6:proto": attribute.String("net.transport", "other"), - } - - for network, want := range transports { - assert.Equal(t, want, NetTransport(network)) - } -} - -func TestNetHost(t *testing.T) { - testAddrs(t, []addrTest{ - {address: "", expected: nil}, - {address: "192.0.0.1", expected: []attribute.KeyValue{ - nc.HostName("192.0.0.1"), - }}, - {address: "192.0.0.1:9090", expected: []attribute.KeyValue{ - nc.HostName("192.0.0.1"), - nc.HostPort(9090), - }}, - }, nc.Host) -} - -func TestNetHostName(t *testing.T) { - expected := attribute.Key("net.host.name").String(addr) - assert.Equal(t, expected, nc.HostName(addr)) -} - -func TestNetHostPort(t *testing.T) { - expected := attribute.Key("net.host.port").Int(port) - assert.Equal(t, expected, nc.HostPort(port)) -} - -func TestNetPeer(t *testing.T) { - testAddrs(t, []addrTest{ - {address: "", expected: nil}, - {address: "example.com", expected: []attribute.KeyValue{ - nc.PeerName("example.com"), - }}, - {address: "/tmp/file", expected: []attribute.KeyValue{ - nc.PeerName("/tmp/file"), - }}, - {address: "192.0.0.1", expected: []attribute.KeyValue{ - nc.PeerName("192.0.0.1"), - }}, - {address: ":9090", expected: nil}, - {address: "192.0.0.1:9090", expected: []attribute.KeyValue{ - nc.PeerName("192.0.0.1"), - nc.PeerPort(9090), - }}, - }, nc.Peer) -} - -func TestNetPeerName(t *testing.T) { - expected := attribute.Key("net.peer.name").String(addr) - assert.Equal(t, expected, nc.PeerName(addr)) -} - -func TestNetPeerPort(t *testing.T) { - expected := attribute.Key("net.peer.port").Int(port) - assert.Equal(t, expected, nc.PeerPort(port)) -} - -func TestNetSockPeerName(t *testing.T) { - expected := attribute.Key("net.sock.peer.addr").String(addr) - assert.Equal(t, expected, nc.SockPeerAddr(addr)) -} - -func TestNetSockPeerPort(t *testing.T) { - expected := attribute.Key("net.sock.peer.port").Int(port) - assert.Equal(t, expected, nc.SockPeerPort(port)) -} - -func TestNetFamily(t *testing.T) { - tests := []struct { - network string - address string - expect string - }{ - {"", "", ""}, - {"unix", "", "unix"}, - {"unix", "gibberish", "unix"}, - {"unixgram", "", "unix"}, - {"unixgram", "gibberish", "unix"}, - {"unixpacket", "gibberish", "unix"}, - {"tcp", "123.0.2.8", "inet"}, - {"tcp", "gibberish", ""}, - {"", "123.0.2.8", "inet"}, - {"", "gibberish", ""}, - {"tcp", "fe80::1", "inet6"}, - {"", "fe80::1", "inet6"}, - } - - for _, test := range tests { - got := family(test.network, test.address) - assert.Equal(t, test.expect, got, test.network+"/"+test.address) - } -} - -func TestSplitHostPort(t *testing.T) { - tests := []struct { - hostport string - host string - port int - }{ - {"", "", -1}, - {":8080", "", 8080}, - {"127.0.0.1", "127.0.0.1", -1}, - {"www.example.com", "www.example.com", -1}, - {"127.0.0.1%25en0", "127.0.0.1%25en0", -1}, - {"[]", "", -1}, // Ensure this doesn't panic. - {"[fe80::1", "", -1}, - {"[fe80::1]", "fe80::1", -1}, - {"[fe80::1%25en0]", "fe80::1%25en0", -1}, - {"[fe80::1]:8080", "fe80::1", 8080}, - {"[fe80::1]::", "", -1}, // Too many colons. - {"127.0.0.1:", "127.0.0.1", -1}, - {"127.0.0.1:port", "127.0.0.1", -1}, - {"127.0.0.1:8080", "127.0.0.1", 8080}, - {"www.example.com:8080", "www.example.com", 8080}, - {"127.0.0.1%25en0:8080", "127.0.0.1%25en0", 8080}, - } - - for _, test := range tests { - h, p := splitHostPort(test.hostport) - assert.Equal(t, test.host, h, test.hostport) - assert.Equal(t, test.port, p, test.hostport) - } -} - -type addrTest struct { - address string - expected []attribute.KeyValue -} - -func testAddrs(t *testing.T, tests []addrTest, f func(string) []attribute.KeyValue) { - t.Helper() - - for _, test := range tests { - got := f(test.address) - assert.Equal(t, cap(test.expected), cap(got), "slice capacity") - assert.ElementsMatch(t, test.expected, got, test.address) - } -} - -func TestNetProtocol(t *testing.T) { - type testCase struct { - name, version string - } - tests := map[string]testCase{ - "HTTP/1.0": {name: "http", version: "1.0"}, - "HTTP/1.1": {name: "http", version: "1.1"}, - "HTTP/2": {name: "http", version: "2"}, - "HTTP/3": {name: "http", version: "3"}, - "SPDY": {name: "spdy"}, - "SPDY/2": {name: "spdy", version: "2"}, - "QUIC": {name: "quic"}, - "unknown/proto/2": {name: "unknown", version: "proto/2"}, - "other": {name: "other"}, - } - - for proto, want := range tests { - name, version := netProtocol(proto) - assert.Equal(t, want.name, name) - assert.Equal(t, want.version, version) - } -} diff --git a/instrumentation/net/http/httptrace/otelhttptrace/internal/semconv/env.go b/instrumentation/net/http/httptrace/otelhttptrace/internal/semconv/env.go index 08a54c036cb..e1a4343ae47 100644 --- a/instrumentation/net/http/httptrace/otelhttptrace/internal/semconv/env.go +++ b/instrumentation/net/http/httptrace/otelhttptrace/internal/semconv/env.go @@ -10,7 +10,6 @@ import ( "context" "fmt" "net/http" - "os" "strings" "sync" @@ -33,14 +32,6 @@ type ResponseTelemetry struct { } type HTTPServer struct { - duplicate bool - - // Old metrics - requestBytesCounter metric.Int64Counter - responseBytesCounter metric.Int64Counter - serverLatencyMeasure metric.Float64Histogram - - // New metrics requestBodySizeHistogram httpconv.ServerRequestBodySize responseBodySizeHistogram httpconv.ServerResponseBodySize requestDurationHistogram httpconv.ServerRequestDuration @@ -63,20 +54,10 @@ type HTTPServer struct { // If the primary server name is not known, server should be an empty string. // The req Host will be used to determine the server instead. func (s HTTPServer) RequestTraceAttrs(server string, req *http.Request, opts RequestTraceAttrsOpts) []attribute.KeyValue { - attrs := CurrentHTTPServer{}.RequestTraceAttrs(server, req, opts) - if s.duplicate { - return OldHTTPServer{}.RequestTraceAttrs(server, req, attrs) - } - return attrs + return CurrentHTTPServer{}.RequestTraceAttrs(server, req, opts) } func (s HTTPServer) NetworkTransportAttr(network string) []attribute.KeyValue { - if s.duplicate { - return []attribute.KeyValue{ - OldHTTPServer{}.NetworkTransportAttr(network), - CurrentHTTPServer{}.NetworkTransportAttr(network), - } - } return []attribute.KeyValue{ CurrentHTTPServer{}.NetworkTransportAttr(network), } @@ -86,11 +67,7 @@ func (s HTTPServer) NetworkTransportAttr(network string) []attribute.KeyValue { // // If any of the fields in the ResponseTelemetry are not set the attribute will be omitted. func (s HTTPServer) ResponseTraceAttrs(resp ResponseTelemetry) []attribute.KeyValue { - attrs := CurrentHTTPServer{}.ResponseTraceAttrs(resp) - if s.duplicate { - return OldHTTPServer{}.ResponseTraceAttrs(resp, attrs) - } - return attrs + return CurrentHTTPServer{}.ResponseTraceAttrs(resp) } // Route returns the attribute for the route. @@ -156,18 +133,6 @@ func (s HTTPServer) RecordMetrics(ctx context.Context, md ServerMetricData) { s.requestDurationHistogram.Inst().Record(ctx, md.ElapsedTime/1000.0, o) *recordOpts = (*recordOpts)[:0] metricRecordOptionPool.Put(recordOpts) - - if s.duplicate && 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) - } } // hasOptIn returns true if the comma-separated version string contains the @@ -182,11 +147,7 @@ func hasOptIn(version, optIn string) bool { } func NewHTTPServer(meter metric.Meter) HTTPServer { - env := strings.ToLower(os.Getenv(OTelSemConvStabilityOptIn)) - duplicate := hasOptIn(env, "http/dup") - server := HTTPServer{ - duplicate: duplicate, - } + server := HTTPServer{} var err error server.requestBodySizeHistogram, err = httpconv.NewServerRequestBodySize(meter) @@ -203,32 +164,16 @@ func NewHTTPServer(meter metric.Meter) HTTPServer { ), ) handleErr(err) - - if duplicate { - server.requestBytesCounter, server.responseBytesCounter, server.serverLatencyMeasure = OldHTTPServer{}.createMeasures(meter) - } return server } type HTTPClient struct { - duplicate bool - - // old metrics - requestBytesCounter metric.Int64Counter - responseBytesCounter metric.Int64Counter - latencyMeasure metric.Float64Histogram - - // new metrics requestBodySize httpconv.ClientRequestBodySize requestDuration httpconv.ClientRequestDuration } func NewHTTPClient(meter metric.Meter) HTTPClient { - env := strings.ToLower(os.Getenv(OTelSemConvStabilityOptIn)) - duplicate := hasOptIn(env, "http/dup") - client := HTTPClient{ - duplicate: duplicate, - } + client := HTTPClient{} var err error client.requestBodySize, err = httpconv.NewClientRequestBodySize(meter) @@ -240,29 +185,17 @@ func NewHTTPClient(meter metric.Meter) HTTPClient { ) handleErr(err) - if duplicate { - client.requestBytesCounter, client.responseBytesCounter, client.latencyMeasure = OldHTTPClient{}.createMeasures(meter) - } - return client } // RequestTraceAttrs returns attributes for an HTTP request made by a client. func (c HTTPClient) RequestTraceAttrs(req *http.Request) []attribute.KeyValue { - attrs := CurrentHTTPClient{}.RequestTraceAttrs(req) - if c.duplicate { - return OldHTTPClient{}.RequestTraceAttrs(req, attrs) - } - return attrs + return CurrentHTTPClient{}.RequestTraceAttrs(req) } // ResponseTraceAttrs returns metric attributes for an HTTP request made by a client. func (c HTTPClient) ResponseTraceAttrs(resp *http.Response) []attribute.KeyValue { - attrs := CurrentHTTPClient{}.ResponseTraceAttrs(resp) - if c.duplicate { - return OldHTTPClient{}.ResponseTraceAttrs(resp, attrs) - } - return attrs + return CurrentHTTPClient{}.ResponseTraceAttrs(resp) } func (c HTTPClient) Status(code int) (codes.Code, string) { @@ -302,42 +235,14 @@ func (c HTTPClient) MetricOptions(ma MetricAttributes) map[string]MetricOpts { addOptions: set, } - if c.duplicate { - attributes := OldHTTPClient{}.MetricAttributes(ma.Req, ma.StatusCode, ma.AdditionalAttributes) - set := metric.WithAttributeSet(attribute.NewSet(attributes...)) - opts["old"] = MetricOpts{ - measurement: set, - addOptions: set, - } - } - return opts } func (s HTTPClient) RecordMetrics(ctx context.Context, md MetricData, opts map[string]MetricOpts) { s.requestBodySize.Inst().Record(ctx, md.RequestSize, opts["new"].MeasurementOption()) s.requestDuration.Inst().Record(ctx, md.ElapsedTime/1000, opts["new"].MeasurementOption()) - - if s.duplicate { - s.requestBytesCounter.Add(ctx, md.RequestSize, opts["old"].AddOptions()) - s.latencyMeasure.Record(ctx, md.ElapsedTime, opts["old"].MeasurementOption()) - } -} - -func (s HTTPClient) RecordResponseSize(ctx context.Context, responseData int64, opts map[string]MetricOpts) { - if s.responseBytesCounter == nil { - // This will happen if an HTTPClient{} is used instead of NewHTTPClient(). - return - } - - s.responseBytesCounter.Add(ctx, responseData, opts["old"].AddOptions()) } func (s HTTPClient) TraceAttributes(host string) []attribute.KeyValue { - attrs := CurrentHTTPClient{}.TraceAttributes(host) - if s.duplicate { - return OldHTTPClient{}.TraceAttributes(host, attrs) - } - - return attrs + return CurrentHTTPClient{}.TraceAttributes(host) } diff --git a/instrumentation/net/http/httptrace/otelhttptrace/internal/semconv/env_test.go b/instrumentation/net/http/httptrace/otelhttptrace/internal/semconv/env_test.go index 45b520854dd..11388bb8324 100644 --- a/instrumentation/net/http/httptrace/otelhttptrace/internal/semconv/env_test.go +++ b/instrumentation/net/http/httptrace/otelhttptrace/internal/semconv/env_test.go @@ -54,7 +54,6 @@ func TestHTTPServerDoesNotPanic(t *testing.T) { func TestServerNetworkTransportAttr(t *testing.T) { for _, tt := range []struct { name string - optinVal string network string wantAttributes []attribute.KeyValue @@ -67,29 +66,8 @@ func TestServerNetworkTransportAttr(t *testing.T) { attribute.String("network.transport", "tcp"), }, }, - { - name: "without a dup optin", - optinVal: "http/dup", - network: "tcp", - - wantAttributes: []attribute.KeyValue{ - attribute.String("net.transport", "ip_tcp"), - attribute.String("network.transport", "tcp"), - }, - }, - { - name: "with mixed categories", - optinVal: "http/dup,database", - network: "tcp", - - wantAttributes: []attribute.KeyValue{ - attribute.String("net.transport", "ip_tcp"), - attribute.String("network.transport", "tcp"), - }, - }, } { t.Run(tt.name, func(t *testing.T) { - t.Setenv(OTelSemConvStabilityOptIn, tt.optinVal) s := NewHTTPServer(nil) assert.Equal(t, tt.wantAttributes, s.NetworkTransportAttr(tt.network)) @@ -124,7 +102,6 @@ func TestHTTPClientDoesNotPanic(t *testing.T) { Req: req, StatusCode: 200, }) - tt.client.RecordResponseSize(context.Background(), 40, opts) tt.client.RecordMetrics(context.Background(), MetricData{ RequestSize: 20, ElapsedTime: 1, @@ -137,7 +114,6 @@ func TestHTTPClientDoesNotPanic(t *testing.T) { func TestHTTPClientTraceAttributes(t *testing.T) { for _, tt := range []struct { name string - optinVal string wantAttributes []attribute.KeyValue }{ @@ -148,28 +124,8 @@ func TestHTTPClientTraceAttributes(t *testing.T) { attribute.String("server.address", "example.com"), }, }, - { - name: "with optin set to duplicate", - optinVal: "http/dup", - - wantAttributes: []attribute.KeyValue{ - attribute.String("server.address", "example.com"), - attribute.String("net.host.name", "example.com"), - }, - }, - { - name: "with mixed categories", - optinVal: "http/dup,database", - - wantAttributes: []attribute.KeyValue{ - attribute.String("server.address", "example.com"), - attribute.String("net.host.name", "example.com"), - }, - }, } { t.Run(tt.name, func(t *testing.T) { - t.Setenv(OTelSemConvStabilityOptIn, tt.optinVal) - c := NewHTTPClient(nil) a := c.TraceAttributes("example.com") assert.Equal(t, tt.wantAttributes, a) @@ -180,7 +136,6 @@ func TestHTTPClientTraceAttributes(t *testing.T) { func TestClientTraceAttributes(t *testing.T) { for _, tt := range []struct { name string - optinVal string host string wantAttributes []attribute.KeyValue @@ -193,29 +148,8 @@ func TestClientTraceAttributes(t *testing.T) { attribute.String("server.address", "example.com"), }, }, - { - name: "without a dup optin", - optinVal: "http/dup", - host: "example.com", - - wantAttributes: []attribute.KeyValue{ - attribute.String("server.address", "example.com"), - attribute.String("net.host.name", "example.com"), - }, - }, - { - name: "with mixed categories", - optinVal: "http/dup", - host: "example.com", - - wantAttributes: []attribute.KeyValue{ - attribute.String("server.address", "example.com"), - attribute.String("net.host.name", "example.com"), - }, - }, } { t.Run(tt.name, func(t *testing.T) { - t.Setenv(OTelSemConvStabilityOptIn, tt.optinVal) s := NewHTTPClient(nil) assert.Equal(t, tt.wantAttributes, s.TraceAttributes(tt.host)) diff --git a/instrumentation/net/http/httptrace/otelhttptrace/internal/semconv/gen.go b/instrumentation/net/http/httptrace/otelhttptrace/internal/semconv/gen.go index 8ff7ce44347..c1c580a830f 100644 --- a/instrumentation/net/http/httptrace/otelhttptrace/internal/semconv/gen.go +++ b/instrumentation/net/http/httptrace/otelhttptrace/internal/semconv/gen.go @@ -13,4 +13,3 @@ package semconv // import "go.opentelemetry.io/contrib/instrumentation/net/http/ //go:generate gotmpl --body=../../../../../../../internal/shared/semconv/httpconvtest_test.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace\" }" --out=httpconvtest_test.go //go:generate gotmpl --body=../../../../../../../internal/shared/semconv/util.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace\" }" --out=util.go //go:generate gotmpl --body=../../../../../../../internal/shared/semconv/util_test.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace\" }" --out=util_test.go -//go:generate gotmpl --body=../../../../../../../internal/shared/semconv/v1.20.0.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace\" }" --out=v1.20.0.go diff --git a/instrumentation/net/http/httptrace/otelhttptrace/internal/semconv/httpconvtest_test.go b/instrumentation/net/http/httptrace/otelhttptrace/internal/semconv/httpconvtest_test.go index f5134e35afa..a4036e1f591 100644 --- a/instrumentation/net/http/httptrace/otelhttptrace/internal/semconv/httpconvtest_test.go +++ b/instrumentation/net/http/httptrace/otelhttptrace/internal/semconv/httpconvtest_test.go @@ -28,7 +28,6 @@ import ( ) func TestNewTraceRequest(t *testing.T) { - t.Setenv("OTEL_SEMCONV_STABILITY_OPT_IN", "http/dup") serv := semconv.NewHTTPServer(nil) want := func(req testServerReq) []attribute.KeyValue { return []attribute.KeyValue{ @@ -42,16 +41,6 @@ func TestNewTraceRequest(t *testing.T) { attribute.String("client.address", req.clientIP), attribute.String("network.protocol.version", "1.1"), attribute.String("url.path", "/"), - attribute.String("http.method", "GET"), - attribute.String("http.scheme", "http"), - attribute.String("net.host.name", req.hostname), - attribute.Int("net.host.port", req.serverPort), - attribute.String("net.sock.peer.addr", req.peerAddr), - attribute.Int("net.sock.peer.port", req.peerPort), - attribute.String("user_agent.original", "Go-http-client/1.1"), - attribute.String("http.client_ip", req.clientIP), - attribute.String("net.protocol.version", "1.1"), - attribute.String("http.target", "/"), } } testTraceRequest(t, serv, want) @@ -174,13 +163,11 @@ func TestNewServerRecordMetrics(t *testing.T) { 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, + name: "No Meter", serverFunc: func(metric.MeterProvider) semconv.HTTPServer { return semconv.NewHTTPServer(nil) }, @@ -189,8 +176,7 @@ func TestNewServerRecordMetrics(t *testing.T) { }, }, { - name: "No environment variable set, but with Meter", - setEnv: false, + name: "With Meter", serverFunc: func(mp metric.MeterProvider) semconv.HTTPServer { return semconv.NewHTTPServer(mp.Meter("test")) }, @@ -202,36 +188,10 @@ func TestNewServerRecordMetrics(t *testing.T) { metricdatatest.AssertEqual(t, expectedCurrentScopeMetric, 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, expectedOldScopeMetric, 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)) @@ -312,7 +272,6 @@ func TestNewTraceResponse(t *testing.T) { } func TestNewTraceRequest_Client(t *testing.T) { - t.Setenv("OTEL_SEMCONV_STABILITY_OPT_IN", "http/dup") body := strings.NewReader("Hello, world!") url := "https://example.com:8888/foo/bar?stuff=morestuff" req := httptest.NewRequest("pOST", url, body) @@ -321,29 +280,22 @@ func TestNewTraceRequest_Client(t *testing.T) { want := []attribute.KeyValue{ attribute.String("http.request.method", "POST"), attribute.String("http.request.method_original", "pOST"), - attribute.String("http.method", "pOST"), attribute.String("url.full", url), - attribute.String("http.url", url), attribute.String("server.address", "example.com"), attribute.Int("server.port", 8888), attribute.String("network.protocol.version", "1.1"), - attribute.String("net.peer.name", "example.com"), - attribute.Int("net.peer.port", 8888), - attribute.String("user_agent.original", "go-test-agent"), - attribute.Int("http.request_content_length", 13), } client := semconv.NewHTTPClient(nil) assert.ElementsMatch(t, want, client.RequestTraceAttrs(req)) } func TestNewTraceResponse_Client(t *testing.T) { - t.Setenv("OTEL_SEMCONV_STABILITY_OPT_IN", "http/dup") testcases := []struct { resp http.Response want []attribute.KeyValue }{ - {resp: http.Response{StatusCode: 200, ContentLength: 123}, want: []attribute.KeyValue{attribute.Int("http.response.status_code", 200), attribute.Int("http.status_code", 200), attribute.Int("http.response_content_length", 123)}}, - {resp: http.Response{StatusCode: 404, ContentLength: 0}, want: []attribute.KeyValue{attribute.Int("http.response.status_code", 404), attribute.Int("http.status_code", 404), attribute.String("error.type", "404")}}, + {resp: http.Response{StatusCode: 200, ContentLength: 123}, want: []attribute.KeyValue{attribute.Int("http.response.status_code", 200)}}, + {resp: http.Response{StatusCode: 404, ContentLength: 0}, want: []attribute.KeyValue{attribute.Int("http.response.status_code", 404), attribute.String("error.type", "404")}}, } for _, tt := range testcases { @@ -401,12 +353,6 @@ func TestRequestErrorType(t *testing.T) { } func TestNewClientRecordMetrics(t *testing.T) { - oldAttrs := attribute.NewSet( - attribute.String("http.method", "POST"), - attribute.Int64("http.status_code", 301), - attribute.String("net.peer.name", "example.com"), - ) - currAttrs := attribute.NewSet( attribute.String("http.request.method", "POST"), attribute.Int64("http.response.status_code", 301), @@ -451,47 +397,13 @@ func TestNewClientRecordMetrics(t *testing.T) { }, } - // The OldHTTPClient version - expectedOldScopeMetric := expectedCurrentScopeMetric - expectedOldScopeMetric.Metrics = append(expectedOldScopeMetric.Metrics, []metricdata.Metrics{ - { - Name: "http.client.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.client.duration", - Description: "Measures the duration of outbound HTTP requests.", - Unit: "ms", - Data: metricdata.Histogram[float64]{ - Temporality: metricdata.CumulativeTemporality, - DataPoints: []metricdata.HistogramDataPoint[float64]{ - { - Attributes: oldAttrs, - }, - }, - }, - }, - }...) - tests := []struct { name string - setEnv bool clientFunc func(metric.MeterProvider) semconv.HTTPClient wantFunc func(t *testing.T, rm metricdata.ResourceMetrics) }{ { name: "No environment variable set, and no Meter", - setEnv: false, clientFunc: func(metric.MeterProvider) semconv.HTTPClient { return semconv.NewHTTPClient(nil) }, @@ -500,8 +412,7 @@ func TestNewClientRecordMetrics(t *testing.T) { }, }, { - name: "No environment variable set, but with Meter", - setEnv: false, + name: "With Meter", clientFunc: func(mp metric.MeterProvider) semconv.HTTPClient { return semconv.NewHTTPClient(mp.Meter("test")) }, @@ -512,36 +423,10 @@ func TestNewClientRecordMetrics(t *testing.T) { metricdatatest.AssertEqual(t, expectedCurrentScopeMetric, rm.ScopeMetrics[0], metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreValue()) }, }, - { - name: "Set environment variable, but no Meter", - setEnv: true, - clientFunc: func(metric.MeterProvider) semconv.HTTPClient { - return semconv.NewHTTPClient(nil) - }, - wantFunc: func(t *testing.T, rm metricdata.ResourceMetrics) { - assert.Empty(t, rm.ScopeMetrics) - }, - }, - { - name: "Set environment variable and Meter", - setEnv: true, - clientFunc: func(mp metric.MeterProvider) semconv.HTTPClient { - return semconv.NewHTTPClient(mp.Meter("test")) - }, - wantFunc: func(t *testing.T, rm metricdata.ResourceMetrics) { - require.Len(t, rm.ScopeMetrics, 1) - require.Len(t, rm.ScopeMetrics[0].Metrics, 4) - metricdatatest.AssertEqual(t, expectedOldScopeMetric, 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)) @@ -564,111 +449,6 @@ func TestNewClientRecordMetrics(t *testing.T) { } } -func TestClientRecordResponseSize(t *testing.T) { - oldAttrs := attribute.NewSet( - attribute.String("http.method", "POST"), - attribute.Int64("http.status_code", 301), - attribute.String("net.peer.name", "example.com"), - ) - - // The OldHTTPClient version - expectedOldScopeMetric := metricdata.ScopeMetrics{ - Scope: instrumentation.Scope{ - Name: "test", - }, - Metrics: []metricdata.Metrics{ - { - Name: "http.client.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, - }, - }, - }, - }, - }, - } - - tests := []struct { - name string - setEnv bool - clientFunc func(metric.MeterProvider) semconv.HTTPClient - wantFunc func(t *testing.T, rm metricdata.ResourceMetrics) - }{ - { - name: "No environment variable set, and no Meter", - setEnv: false, - clientFunc: func(metric.MeterProvider) semconv.HTTPClient { - return semconv.NewHTTPClient(nil) - }, - wantFunc: func(t *testing.T, rm metricdata.ResourceMetrics) { - assert.Empty(t, rm.ScopeMetrics) - }, - }, - { - name: "No environment variable set, but with Meter", - setEnv: false, - clientFunc: func(mp metric.MeterProvider) semconv.HTTPClient { - return semconv.NewHTTPClient(mp.Meter("test")) - }, - wantFunc: func(t *testing.T, rm metricdata.ResourceMetrics) { - require.Empty(t, rm.ScopeMetrics) - }, - }, - { - name: "Set environment variable, but no Meter", - setEnv: true, - clientFunc: func(metric.MeterProvider) semconv.HTTPClient { - return semconv.NewHTTPClient(nil) - }, - wantFunc: func(t *testing.T, rm metricdata.ResourceMetrics) { - assert.Empty(t, rm.ScopeMetrics) - }, - }, - { - name: "Set environment variable and Meter", - setEnv: true, - clientFunc: func(mp metric.MeterProvider) semconv.HTTPClient { - return semconv.NewHTTPClient(mp.Meter("test")) - }, - wantFunc: func(t *testing.T, rm metricdata.ResourceMetrics) { - require.Len(t, rm.ScopeMetrics, 1) - require.Len(t, rm.ScopeMetrics[0].Metrics, 1) - metricdatatest.AssertEqual(t, expectedOldScopeMetric, 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)) - - client := tt.clientFunc(mp) - req, err := http.NewRequest("POST", "http://example.com", nil) - assert.NoError(t, err) - - client.RecordResponseSize(context.Background(), 100, client.MetricOptions(semconv.MetricAttributes{ - Req: req, - StatusCode: 301, - })) - - rm := metricdata.ResourceMetrics{} - require.NoError(t, reader.Collect(context.Background(), &rm)) - tt.wantFunc(t, rm) - }) - } -} - type customError struct{} func (customError) Error() string { diff --git a/instrumentation/net/http/httptrace/otelhttptrace/internal/semconv/v1.20.0.go b/instrumentation/net/http/httptrace/otelhttptrace/internal/semconv/v1.20.0.go deleted file mode 100644 index d5fc58708a2..00000000000 --- a/instrumentation/net/http/httptrace/otelhttptrace/internal/semconv/v1.20.0.go +++ /dev/null @@ -1,273 +0,0 @@ -// Code generated by gotmpl. DO NOT MODIFY. -// source: internal/shared/semconv/v120.0.go.tmpl - -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -package semconv // import "go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace/internal/semconv" - -import ( - "errors" - "io" - "net/http" - "slices" - - "go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace/internal/semconvutil" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/metric" - "go.opentelemetry.io/otel/metric/noop" - semconv "go.opentelemetry.io/otel/semconv/v1.20.0" -) - -type OldHTTPServer struct{} - -// RequestTraceAttrs returns trace attributes for an HTTP request received by a -// server. -// -// The server must be the primary server name if it is known. For example this -// would be the ServerName directive -// (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache -// server, and the server_name directive -// (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an -// nginx server. More generically, the primary server name would be the host -// header value that matches the default virtual host of an HTTP server. It -// should include the host identifier and if a port is used to route to the -// server that port identifier should be included as an appropriate port -// suffix. -// -// If the primary server name is not known, server should be an empty string. -// The req Host will be used to determine the server instead. -func (o OldHTTPServer) RequestTraceAttrs(server string, req *http.Request, attrs []attribute.KeyValue) []attribute.KeyValue { - return semconvutil.HTTPServerRequest(server, req, semconvutil.HTTPServerRequestOptions{}, attrs) -} - -func (o OldHTTPServer) NetworkTransportAttr(network string) attribute.KeyValue { - return semconvutil.NetTransport(network) -} - -// ResponseTraceAttrs returns trace attributes for telemetry from an HTTP response. -// -// If any of the fields in the ResponseTelemetry are not set the attribute will be omitted. -func (o OldHTTPServer) ResponseTraceAttrs(resp ResponseTelemetry, attributes []attribute.KeyValue) []attribute.KeyValue { - if resp.ReadBytes > 0 { - attributes = append(attributes, semconv.HTTPRequestContentLength(int(resp.ReadBytes))) - } - if resp.ReadError != nil && !errors.Is(resp.ReadError, io.EOF) { - // This is not in the semantic conventions, but is historically provided - attributes = append(attributes, attribute.String("http.read_error", resp.ReadError.Error())) - } - if resp.WriteBytes > 0 { - attributes = append(attributes, semconv.HTTPResponseContentLength(int(resp.WriteBytes))) - } - if resp.StatusCode > 0 { - attributes = append(attributes, semconv.HTTPStatusCode(resp.StatusCode)) - } - if resp.WriteError != nil && !errors.Is(resp.WriteError, io.EOF) { - // This is not in the semantic conventions, but is historically provided - attributes = append(attributes, attribute.String("http.write_error", resp.WriteError.Error())) - } - - return attributes -} - -// Route returns the attribute for the route. -func (o OldHTTPServer) Route(route string) attribute.KeyValue { - return semconv.HTTPRoute(route) -} - -// HTTPStatusCode returns the attribute for the HTTP status code. -// This is a temporary function needed by metrics. This will be removed when MetricsRequest is added. -func HTTPStatusCode(status int) attribute.KeyValue { - return semconv.HTTPStatusCode(status) -} - -// Server HTTP metrics. -const ( - serverRequestSize = "http.server.request.size" // Incoming request bytes total - serverResponseSize = "http.server.response.size" // Incoming response bytes total - serverDuration = "http.server.duration" // Incoming end to end duration, milliseconds -) - -func (h OldHTTPServer) createMeasures(meter metric.Meter) (metric.Int64Counter, metric.Int64Counter, metric.Float64Histogram) { - if meter == nil { - return noop.Int64Counter{}, noop.Int64Counter{}, noop.Float64Histogram{} - } - var err error - requestBytesCounter, err := meter.Int64Counter( - serverRequestSize, - metric.WithUnit("By"), - metric.WithDescription("Measures the size of HTTP request messages."), - ) - handleErr(err) - - responseBytesCounter, err := meter.Int64Counter( - serverResponseSize, - metric.WithUnit("By"), - metric.WithDescription("Measures the size of HTTP response messages."), - ) - handleErr(err) - - serverLatencyMeasure, err := meter.Float64Histogram( - serverDuration, - metric.WithUnit("ms"), - metric.WithDescription("Measures the duration of inbound HTTP requests."), - ) - handleErr(err) - - return requestBytesCounter, responseBytesCounter, serverLatencyMeasure -} - -func (o OldHTTPServer) MetricAttributes(server string, req *http.Request, statusCode int, additionalAttributes []attribute.KeyValue) []attribute.KeyValue { - n := 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 { - n++ - } - protoName, protoVersion := netProtocol(req.Proto) - if protoName != "" { - n++ - } - if protoVersion != "" { - n++ - } - - if statusCode > 0 { - n++ - } - - attributes := slices.Grow(additionalAttributes, n) - attributes = append(attributes, - semconv.HTTPMethod(standardizeHTTPMethod(req.Method)), - o.scheme(req.TLS != nil), - semconv.NetHostName(host)) - - if hostPort > 0 { - attributes = append(attributes, semconv.NetHostPort(hostPort)) - } - if protoName != "" { - attributes = append(attributes, semconv.NetProtocolName(protoName)) - } - if protoVersion != "" { - attributes = append(attributes, semconv.NetProtocolVersion(protoVersion)) - } - - if statusCode > 0 { - attributes = append(attributes, semconv.HTTPStatusCode(statusCode)) - } - return attributes -} - -func (o OldHTTPServer) scheme(https bool) attribute.KeyValue { // nolint:revive - if https { - return semconv.HTTPSchemeHTTPS - } - return semconv.HTTPSchemeHTTP -} - -type OldHTTPClient struct{} - -func (o OldHTTPClient) RequestTraceAttrs(req *http.Request, attrs []attribute.KeyValue) []attribute.KeyValue { - return semconvutil.HTTPClientRequest(req, attrs) -} - -func (o OldHTTPClient) ResponseTraceAttrs(resp *http.Response, attrs []attribute.KeyValue) []attribute.KeyValue { - return semconvutil.HTTPClientResponse(resp, attrs) -} - -func (o OldHTTPClient) MetricAttributes(req *http.Request, statusCode int, additionalAttributes []attribute.KeyValue) []attribute.KeyValue { - /* The following semantic conventions are returned if present: - http.method string - http.status_code int - net.peer.name string - net.peer.port int - */ - - n := 2 // method, peer name. - var h string - if req.URL != nil { - h = req.URL.Host - } - var requestHost string - var requestPort int - for _, hostport := range []string{h, req.Header.Get("Host")} { - requestHost, requestPort = SplitHostPort(hostport) - if requestHost != "" || requestPort > 0 { - break - } - } - - port := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", requestPort) - if port > 0 { - n++ - } - - if statusCode > 0 { - n++ - } - - attributes := slices.Grow(additionalAttributes, n) - attributes = append(attributes, - semconv.HTTPMethod(standardizeHTTPMethod(req.Method)), - semconv.NetPeerName(requestHost), - ) - - if port > 0 { - attributes = append(attributes, semconv.NetPeerPort(port)) - } - - if statusCode > 0 { - attributes = append(attributes, semconv.HTTPStatusCode(statusCode)) - } - return attributes -} - -// Client HTTP metrics. -const ( - clientRequestSize = "http.client.request.size" // Incoming request bytes total - clientResponseSize = "http.client.response.size" // Incoming response bytes total - clientDuration = "http.client.duration" // Incoming end to end duration, milliseconds -) - -func (o OldHTTPClient) createMeasures(meter metric.Meter) (metric.Int64Counter, metric.Int64Counter, metric.Float64Histogram) { - if meter == nil { - return noop.Int64Counter{}, noop.Int64Counter{}, noop.Float64Histogram{} - } - requestBytesCounter, err := meter.Int64Counter( - clientRequestSize, - metric.WithUnit("By"), - metric.WithDescription("Measures the size of HTTP request messages."), - ) - handleErr(err) - - responseBytesCounter, err := meter.Int64Counter( - clientResponseSize, - metric.WithUnit("By"), - metric.WithDescription("Measures the size of HTTP response messages."), - ) - handleErr(err) - - latencyMeasure, err := meter.Float64Histogram( - clientDuration, - metric.WithUnit("ms"), - metric.WithDescription("Measures the duration of outbound HTTP requests."), - ) - handleErr(err) - - return requestBytesCounter, responseBytesCounter, latencyMeasure -} - -// TraceAttributes returns attributes for httptrace. -func (c OldHTTPClient) TraceAttributes(host string, attrs []attribute.KeyValue) []attribute.KeyValue { - return append(attrs, semconv.NetHostName(host)) -} diff --git a/instrumentation/net/http/httptrace/otelhttptrace/internal/semconvutil/gen.go b/instrumentation/net/http/httptrace/otelhttptrace/internal/semconvutil/gen.go deleted file mode 100644 index 4e9c361102e..00000000000 --- a/instrumentation/net/http/httptrace/otelhttptrace/internal/semconvutil/gen.go +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -package semconvutil // import "go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace/internal/semconvutil" - -// Generate semconvutil package: -//go:generate gotmpl --body=../../../../../../../internal/shared/semconvutil/httpconv_test.go.tmpl "--data={}" --out=httpconv_test.go -//go:generate gotmpl --body=../../../../../../../internal/shared/semconvutil/httpconv.go.tmpl "--data={}" --out=httpconv.go -//go:generate gotmpl --body=../../../../../../../internal/shared/semconvutil/netconv_test.go.tmpl "--data={}" --out=netconv_test.go -//go:generate gotmpl --body=../../../../../../../internal/shared/semconvutil/netconv.go.tmpl "--data={}" --out=netconv.go diff --git a/instrumentation/net/http/httptrace/otelhttptrace/internal/semconvutil/httpconv.go b/instrumentation/net/http/httptrace/otelhttptrace/internal/semconvutil/httpconv.go deleted file mode 100644 index 76004b734bf..00000000000 --- a/instrumentation/net/http/httptrace/otelhttptrace/internal/semconvutil/httpconv.go +++ /dev/null @@ -1,594 +0,0 @@ -// Code generated by gotmpl. DO NOT MODIFY. -// source: internal/shared/semconvutil/httpconv.go.tmpl - -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -// Package semconvutil provides OpenTelemetry semantic convention utilities. -package semconvutil // import "go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace/internal/semconvutil" - -import ( - "fmt" - "net/http" - "slices" - "strings" - - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/codes" - semconv "go.opentelemetry.io/otel/semconv/v1.20.0" -) - -type HTTPServerRequestOptions struct { - // If set, this is used as value for the "http.client_ip" attribute. - HTTPClientIP string -} - -// HTTPClientResponse returns trace attributes for an HTTP response received by a -// client from a server. It will return the following attributes if the related -// values are defined in resp: "http.status.code", -// "http.response_content_length". -// -// This does not add all OpenTelemetry required attributes for an HTTP event, -// it assumes ClientRequest was used to create the span with a complete set of -// attributes. If a complete set of attributes can be generated using the -// request contained in resp. For example: -// -// HTTPClientResponse(resp, ClientRequest(resp.Request))) -func HTTPClientResponse(resp *http.Response, attrs []attribute.KeyValue) []attribute.KeyValue { - return hc.ClientResponse(resp, attrs) -} - -// HTTPClientRequest returns trace attributes for an HTTP request made by a client. -// The following attributes are always returned: "http.url", "http.method", -// "net.peer.name". The following attributes are returned if the related values -// are defined in req: "net.peer.port", "user_agent.original", -// "http.request_content_length". -func HTTPClientRequest(req *http.Request, attrs []attribute.KeyValue) []attribute.KeyValue { - return hc.ClientRequest(req, attrs) -} - -// HTTPClientRequestMetrics returns metric attributes for an HTTP request made by a client. -// The following attributes are always returned: "http.method", "net.peer.name". -// The following attributes are returned if the -// related values are defined in req: "net.peer.port". -func HTTPClientRequestMetrics(req *http.Request) []attribute.KeyValue { - return hc.ClientRequestMetrics(req) -} - -// HTTPClientStatus returns a span status code and message for an HTTP status code -// value received by a client. -func HTTPClientStatus(code int) (codes.Code, string) { - return hc.ClientStatus(code) -} - -// HTTPServerRequest returns trace attributes for an HTTP request received by a -// server. -// -// The server must be the primary server name if it is known. For example this -// would be the ServerName directive -// (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache -// server, and the server_name directive -// (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an -// nginx server. More generically, the primary server name would be the host -// header value that matches the default virtual host of an HTTP server. It -// should include the host identifier and if a port is used to route to the -// server that port identifier should be included as an appropriate port -// suffix. -// -// If the primary server name is not known, server should be an empty string. -// The req Host will be used to determine the server instead. -// -// The following attributes are always returned: "http.method", "http.scheme", -// "http.target", "net.host.name". The following attributes are returned if -// they related values are defined in req: "net.host.port", "net.sock.peer.addr", -// "net.sock.peer.port", "user_agent.original", "http.client_ip". -func HTTPServerRequest(server string, req *http.Request, opts HTTPServerRequestOptions, attrs []attribute.KeyValue) []attribute.KeyValue { - return hc.ServerRequest(server, req, opts, attrs) -} - -// HTTPServerRequestMetrics returns metric attributes for an HTTP request received by a -// server. -// -// The server must be the primary server name if it is known. For example this -// would be the ServerName directive -// (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache -// server, and the server_name directive -// (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an -// nginx server. More generically, the primary server name would be the host -// header value that matches the default virtual host of an HTTP server. It -// should include the host identifier and if a port is used to route to the -// server that port identifier should be included as an appropriate port -// suffix. -// -// If the primary server name is not known, server should be an empty string. -// The req Host will be used to determine the server instead. -// -// The following attributes are always returned: "http.method", "http.scheme", -// "net.host.name". The following attributes are returned if they related -// values are defined in req: "net.host.port". -func HTTPServerRequestMetrics(server string, req *http.Request) []attribute.KeyValue { - return hc.ServerRequestMetrics(server, req) -} - -// HTTPServerStatus returns a span status code and message for an HTTP status code -// value returned by a server. Status codes in the 400-499 range are not -// returned as errors. -func HTTPServerStatus(code int) (codes.Code, string) { - return hc.ServerStatus(code) -} - -// httpConv are the HTTP semantic convention attributes defined for a version -// of the OpenTelemetry specification. -type httpConv struct { - NetConv *netConv - - HTTPClientIPKey attribute.Key - HTTPMethodKey attribute.Key - HTTPRequestContentLengthKey attribute.Key - HTTPResponseContentLengthKey attribute.Key - HTTPRouteKey attribute.Key - HTTPSchemeHTTP attribute.KeyValue - HTTPSchemeHTTPS attribute.KeyValue - HTTPStatusCodeKey attribute.Key - HTTPTargetKey attribute.Key - HTTPURLKey attribute.Key - UserAgentOriginalKey attribute.Key -} - -var hc = &httpConv{ - NetConv: nc, - - HTTPClientIPKey: semconv.HTTPClientIPKey, - HTTPMethodKey: semconv.HTTPMethodKey, - HTTPRequestContentLengthKey: semconv.HTTPRequestContentLengthKey, - HTTPResponseContentLengthKey: semconv.HTTPResponseContentLengthKey, - HTTPRouteKey: semconv.HTTPRouteKey, - HTTPSchemeHTTP: semconv.HTTPSchemeHTTP, - HTTPSchemeHTTPS: semconv.HTTPSchemeHTTPS, - HTTPStatusCodeKey: semconv.HTTPStatusCodeKey, - HTTPTargetKey: semconv.HTTPTargetKey, - HTTPURLKey: semconv.HTTPURLKey, - UserAgentOriginalKey: semconv.UserAgentOriginalKey, -} - -// ClientResponse returns attributes for an HTTP response received by a client -// from a server. The following attributes are returned if the related values -// are defined in resp: "http.status.code", "http.response_content_length". -// -// This does not add all OpenTelemetry required attributes for an HTTP event, -// it assumes ClientRequest was used to create the span with a complete set of -// attributes. If a complete set of attributes can be generated using the -// request contained in resp. For example: -// -// ClientResponse(resp, ClientRequest(resp.Request)) -func (c *httpConv) ClientResponse(resp *http.Response, attrs []attribute.KeyValue) []attribute.KeyValue { - /* The following semantic conventions are returned if present: - http.status_code int - http.response_content_length int - */ - var n int - if resp.StatusCode > 0 { - n++ - } - if resp.ContentLength > 0 { - n++ - } - if n == 0 { - return attrs - } - - attrs = slices.Grow(attrs, n) - if resp.StatusCode > 0 { - attrs = append(attrs, c.HTTPStatusCodeKey.Int(resp.StatusCode)) - } - if resp.ContentLength > 0 { - attrs = append(attrs, c.HTTPResponseContentLengthKey.Int(int(resp.ContentLength))) - } - return attrs -} - -// ClientRequest returns attributes for an HTTP request made by a client. The -// following attributes are always returned: "http.url", "http.method", -// "net.peer.name". The following attributes are returned if the related values -// are defined in req: "net.peer.port", "user_agent.original", -// "http.request_content_length", "user_agent.original". -func (c *httpConv) ClientRequest(req *http.Request, attrs []attribute.KeyValue) []attribute.KeyValue { - /* The following semantic conventions are returned if present: - http.method string - user_agent.original string - http.url string - net.peer.name string - net.peer.port int - http.request_content_length int - */ - - /* The following semantic conventions are not returned: - http.status_code This requires the response. See ClientResponse. - http.response_content_length This requires the response. See ClientResponse. - net.sock.family This requires the socket used. - net.sock.peer.addr This requires the socket used. - net.sock.peer.name This requires the socket used. - net.sock.peer.port This requires the socket used. - http.resend_count This is something outside of a single request. - net.protocol.name The value is the Request is ignored, and the go client will always use "http". - net.protocol.version The value in the Request is ignored, and the go client will always use 1.1 or 2.0. - */ - n := 3 // URL, peer name, proto, and method. - var h string - if req.URL != nil { - h = req.URL.Host - } - peer, p := firstHostPort(h, req.Header.Get("Host")) - port := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", p) - if port > 0 { - n++ - } - useragent := req.UserAgent() - if useragent != "" { - n++ - } - if req.ContentLength > 0 { - n++ - } - - attrs = slices.Grow(attrs, n) - attrs = append(attrs, c.method(req.Method)) - - var u string - if req.URL != nil { - // Remove any username/password info that may be in the URL. - userinfo := req.URL.User - req.URL.User = nil - u = req.URL.String() - // Restore any username/password info that was removed. - req.URL.User = userinfo - } - attrs = append(attrs, c.HTTPURLKey.String(u)) - - attrs = append(attrs, c.NetConv.PeerName(peer)) - if port > 0 { - attrs = append(attrs, c.NetConv.PeerPort(port)) - } - - if useragent != "" { - attrs = append(attrs, c.UserAgentOriginalKey.String(useragent)) - } - - if l := req.ContentLength; l > 0 { - attrs = append(attrs, c.HTTPRequestContentLengthKey.Int64(l)) - } - - return attrs -} - -// ClientRequestMetrics returns metric attributes for an HTTP request made by a client. The -// following attributes are always returned: "http.method", "net.peer.name". -// The following attributes are returned if the related values -// are defined in req: "net.peer.port". -func (c *httpConv) ClientRequestMetrics(req *http.Request) []attribute.KeyValue { - /* The following semantic conventions are returned if present: - http.method string - net.peer.name string - net.peer.port int - */ - - n := 2 // method, peer name. - var h string - if req.URL != nil { - h = req.URL.Host - } - peer, p := firstHostPort(h, req.Header.Get("Host")) - port := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", p) - if port > 0 { - n++ - } - - attrs := make([]attribute.KeyValue, 0, n) - attrs = append(attrs, c.method(req.Method), c.NetConv.PeerName(peer)) - - if port > 0 { - attrs = append(attrs, c.NetConv.PeerPort(port)) - } - - return attrs -} - -// ServerRequest returns attributes for an HTTP request received by a server. -// -// The server must be the primary server name if it is known. For example this -// would be the ServerName directive -// (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache -// server, and the server_name directive -// (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an -// nginx server. More generically, the primary server name would be the host -// header value that matches the default virtual host of an HTTP server. It -// should include the host identifier and if a port is used to route to the -// server that port identifier should be included as an appropriate port -// suffix. -// -// If the primary server name is not known, server should be an empty string. -// The req Host will be used to determine the server instead. -// -// The following attributes are always returned: "http.method", "http.scheme", -// "http.target", "net.host.name". The following attributes are returned if they -// related values are defined in req: "net.host.port", "net.sock.peer.addr", -// "net.sock.peer.port", "user_agent.original", "http.client_ip", -// "net.protocol.name", "net.protocol.version". -func (c *httpConv) ServerRequest(server string, req *http.Request, opts HTTPServerRequestOptions, attrs []attribute.KeyValue) []attribute.KeyValue { - /* The following semantic conventions are returned if present: - http.method string - http.scheme string - net.host.name string - net.host.port int - net.sock.peer.addr string - net.sock.peer.port int - user_agent.original string - http.client_ip string - net.protocol.name string Note: not set if the value is "http". - net.protocol.version string - http.target string Note: doesn't include the query parameter. - */ - - /* The following semantic conventions are not returned: - http.status_code This requires the response. - http.request_content_length This requires the len() of body, which can mutate it. - http.response_content_length This requires the response. - http.route This is not available. - net.sock.peer.name This would require a DNS lookup. - net.sock.host.addr The request doesn't have access to the underlying socket. - net.sock.host.port The request doesn't have access to the underlying socket. - - */ - n := 4 // Method, scheme, proto, and host name. - 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 { - n++ - } - peer, peerPort := splitHostPort(req.RemoteAddr) - if peer != "" { - n++ - if peerPort > 0 { - n++ - } - } - useragent := req.UserAgent() - if useragent != "" { - n++ - } - - // For client IP, use, in order: - // 1. The value passed in the options - // 2. The value in the X-Forwarded-For header - // 3. The peer address - clientIP := opts.HTTPClientIP - if clientIP == "" { - clientIP = serverClientIP(req.Header.Get("X-Forwarded-For")) - if clientIP == "" { - clientIP = peer - } - } - if clientIP != "" { - n++ - } - - var target string - if req.URL != nil { - target = req.URL.Path - if target != "" { - n++ - } - } - protoName, protoVersion := netProtocol(req.Proto) - if protoName != "" && protoName != "http" { - n++ - } - if protoVersion != "" { - n++ - } - - attrs = slices.Grow(attrs, n) - - attrs = append(attrs, c.method(req.Method)) - attrs = append(attrs, c.scheme(req.TLS != nil)) - attrs = append(attrs, c.NetConv.HostName(host)) - - if hostPort > 0 { - attrs = append(attrs, c.NetConv.HostPort(hostPort)) - } - - if peer != "" { - // The Go HTTP server sets RemoteAddr to "IP:port", this will not be a - // file-path that would be interpreted with a sock family. - attrs = append(attrs, c.NetConv.SockPeerAddr(peer)) - if peerPort > 0 { - attrs = append(attrs, c.NetConv.SockPeerPort(peerPort)) - } - } - - if useragent != "" { - attrs = append(attrs, c.UserAgentOriginalKey.String(useragent)) - } - - if clientIP != "" { - attrs = append(attrs, c.HTTPClientIPKey.String(clientIP)) - } - - if target != "" { - attrs = append(attrs, c.HTTPTargetKey.String(target)) - } - - if protoName != "" && protoName != "http" { - attrs = append(attrs, c.NetConv.NetProtocolName.String(protoName)) - } - if protoVersion != "" { - attrs = append(attrs, c.NetConv.NetProtocolVersion.String(protoVersion)) - } - - return attrs -} - -// ServerRequestMetrics returns metric attributes for an HTTP request received -// by a server. -// -// The server must be the primary server name if it is known. For example this -// would be the ServerName directive -// (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache -// server, and the server_name directive -// (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an -// nginx server. More generically, the primary server name would be the host -// header value that matches the default virtual host of an HTTP server. It -// should include the host identifier and if a port is used to route to the -// server that port identifier should be included as an appropriate port -// suffix. -// -// If the primary server name is not known, server should be an empty string. -// The req Host will be used to determine the server instead. -// -// The following attributes are always returned: "http.method", "http.scheme", -// "net.host.name". The following attributes are returned if they related -// values are defined in req: "net.host.port". -func (c *httpConv) ServerRequestMetrics(server string, req *http.Request) []attribute.KeyValue { - /* The following semantic conventions are returned if present: - http.scheme string - http.route string - http.method string - http.status_code int - net.host.name string - net.host.port int - net.protocol.name string Note: not set if the value is "http". - net.protocol.version string - */ - - n := 3 // Method, scheme, and host name. - 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 { - n++ - } - protoName, protoVersion := netProtocol(req.Proto) - if protoName != "" { - n++ - } - if protoVersion != "" { - n++ - } - - attrs := make([]attribute.KeyValue, 0, n) - - attrs = append(attrs, c.methodMetric(req.Method)) - attrs = append(attrs, c.scheme(req.TLS != nil)) - attrs = append(attrs, c.NetConv.HostName(host)) - - if hostPort > 0 { - attrs = append(attrs, c.NetConv.HostPort(hostPort)) - } - if protoName != "" { - attrs = append(attrs, c.NetConv.NetProtocolName.String(protoName)) - } - if protoVersion != "" { - attrs = append(attrs, c.NetConv.NetProtocolVersion.String(protoVersion)) - } - - return attrs -} - -func (c *httpConv) method(method string) attribute.KeyValue { - if method == "" { - return c.HTTPMethodKey.String(http.MethodGet) - } - return c.HTTPMethodKey.String(method) -} - -func (c *httpConv) methodMetric(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 c.HTTPMethodKey.String(method) -} - -func (c *httpConv) scheme(https bool) attribute.KeyValue { // nolint:revive - if https { - return c.HTTPSchemeHTTPS - } - return c.HTTPSchemeHTTP -} - -func serverClientIP(xForwardedFor string) string { - if idx := strings.Index(xForwardedFor, ","); idx >= 0 { - xForwardedFor = xForwardedFor[:idx] - } - return xForwardedFor -} - -func requiredHTTPPort(https bool, port int) int { // nolint:revive - if https { - if port > 0 && port != 443 { - return port - } - } else { - if port > 0 && port != 80 { - return port - } - } - return -1 -} - -// Return the request host and port from the first non-empty source. -func firstHostPort(source ...string) (host string, port int) { - for _, hostport := range source { - host, port = splitHostPort(hostport) - if host != "" || port > 0 { - break - } - } - return -} - -// ClientStatus returns a span status code and message for an HTTP status code -// value received by a client. -func (c *httpConv) ClientStatus(code int) (codes.Code, string) { - if code < 100 || code >= 600 { - return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code) - } - if code >= 400 { - return codes.Error, "" - } - return codes.Unset, "" -} - -// ServerStatus returns a span status code and message for an HTTP status code -// value returned by a server. Status codes in the 400-499 range are not -// returned as errors. -func (c *httpConv) ServerStatus(code int) (codes.Code, string) { - if code < 100 || code >= 600 { - return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code) - } - if code >= 500 { - return codes.Error, "" - } - return codes.Unset, "" -} diff --git a/instrumentation/net/http/httptrace/otelhttptrace/internal/semconvutil/httpconv_test.go b/instrumentation/net/http/httptrace/otelhttptrace/internal/semconvutil/httpconv_test.go deleted file mode 100644 index ce3a4eb3319..00000000000 --- a/instrumentation/net/http/httptrace/otelhttptrace/internal/semconvutil/httpconv_test.go +++ /dev/null @@ -1,561 +0,0 @@ -// Code generated by gotmpl. DO NOT MODIFY. -// source: internal/shared/semconvutil/httpconv_test.go.tmpl - -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -package semconvutil - -import ( - "net/http" - "net/http/httptest" - "net/url" - "strconv" - "testing" - "time" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/codes" -) - -func TestHTTPClientResponse(t *testing.T) { - const stat, n = 201, 397 - resp := &http.Response{ - StatusCode: stat, - ContentLength: n, - } - got := HTTPClientResponse(resp, nil) - assert.Equal(t, 2, cap(got), "slice capacity") - assert.ElementsMatch(t, []attribute.KeyValue{ - attribute.Key("http.status_code").Int(stat), - attribute.Key("http.response_content_length").Int(n), - }, got) -} - -func TestHTTPSClientRequest(t *testing.T) { - req := &http.Request{ - Method: http.MethodGet, - URL: &url.URL{ - Scheme: "https", - Host: "127.0.0.1:443", - Path: "/resource", - }, - Proto: "HTTP/1.0", - ProtoMajor: 1, - ProtoMinor: 0, - } - - assert.ElementsMatch( - t, - []attribute.KeyValue{ - attribute.String("http.method", "GET"), - attribute.String("http.url", "https://127.0.0.1:443/resource"), - attribute.String("net.peer.name", "127.0.0.1"), - }, - HTTPClientRequest(req, nil), - ) -} - -func TestHTTPSClientRequestMetrics(t *testing.T) { - req := &http.Request{ - Method: http.MethodGet, - URL: &url.URL{ - Scheme: "https", - Host: "127.0.0.1:443", - Path: "/resource", - }, - Proto: "HTTP/1.0", - ProtoMajor: 1, - ProtoMinor: 0, - } - - assert.ElementsMatch( - t, - []attribute.KeyValue{ - attribute.String("http.method", "GET"), - attribute.String("net.peer.name", "127.0.0.1"), - }, - HTTPClientRequestMetrics(req), - ) -} - -func TestHTTPClientRequest(t *testing.T) { - const ( - user = "alice" - n = 128 - agent = "Go-http-client/1.1" - ) - req := &http.Request{ - Method: http.MethodGet, - URL: &url.URL{ - Scheme: "http", - Host: "127.0.0.1:8080", - Path: "/resource", - }, - Proto: "HTTP/1.0", - ProtoMajor: 1, - ProtoMinor: 0, - Header: http.Header{ - "User-Agent": []string{agent}, - }, - ContentLength: n, - } - req.SetBasicAuth(user, "pswrd") - - assert.ElementsMatch( - t, - []attribute.KeyValue{ - attribute.String("http.method", "GET"), - attribute.String("http.url", "http://127.0.0.1:8080/resource"), - attribute.String("net.peer.name", "127.0.0.1"), - attribute.Int("net.peer.port", 8080), - attribute.String("user_agent.original", agent), - attribute.Int("http.request_content_length", n), - }, - HTTPClientRequest(req, nil), - ) -} - -func TestHTTPClientRequestMetrics(t *testing.T) { - const ( - user = "alice" - n = 128 - agent = "Go-http-client/1.1" - ) - req := &http.Request{ - Method: http.MethodGet, - URL: &url.URL{ - Scheme: "http", - Host: "127.0.0.1:8080", - Path: "/resource", - }, - Proto: "HTTP/1.0", - ProtoMajor: 1, - ProtoMinor: 0, - Header: http.Header{ - "User-Agent": []string{agent}, - }, - ContentLength: n, - } - req.SetBasicAuth(user, "pswrd") - - assert.ElementsMatch( - t, - []attribute.KeyValue{ - attribute.String("http.method", "GET"), - attribute.String("net.peer.name", "127.0.0.1"), - attribute.Int("net.peer.port", 8080), - }, - HTTPClientRequestMetrics(req), - ) -} - -func TestHTTPClientRequestRequired(t *testing.T) { - req := new(http.Request) - var got []attribute.KeyValue - assert.NotPanics(t, func() { got = HTTPClientRequest(req, nil) }) - want := []attribute.KeyValue{ - attribute.String("http.method", "GET"), - attribute.String("http.url", ""), - attribute.String("net.peer.name", ""), - } - assert.Equal(t, want, got) -} - -func TestHTTPServerRequest(t *testing.T) { - for _, tt := range []struct { - name string - requestModifierFn func(r *http.Request) - httpServerRequestOpts HTTPServerRequestOptions - - wantClientIP string - }{ - { - name: "with a client IP from the network", - wantClientIP: "1.2.3.4", - }, - { - name: "with a client IP from x-forwarded-for header", - requestModifierFn: func(r *http.Request) { - r.Header.Add("X-Forwarded-For", "5.6.7.8") - }, - wantClientIP: "5.6.7.8", - }, - { - name: "with a client IP in options", - requestModifierFn: func(r *http.Request) { - r.Header.Add("X-Forwarded-For", "5.6.7.8") - }, - httpServerRequestOpts: HTTPServerRequestOptions{ - HTTPClientIP: "9.8.7.6", - }, - wantClientIP: "9.8.7.6", - }, - } { - t.Run(tt.name, func(t *testing.T) { - reqCh := make(chan *http.Request, 1) - handler := func(w http.ResponseWriter, r *http.Request) { - r.RemoteAddr = "1.2.3.4:5678" - reqCh <- r - w.WriteHeader(http.StatusOK) - } - - srv := httptest.NewServer(http.HandlerFunc(handler)) - defer srv.Close() - - srvURL, err := url.Parse(srv.URL) - require.NoError(t, err) - srvPort, err := strconv.ParseInt(srvURL.Port(), 10, 32) - require.NoError(t, err) - - req, err := http.NewRequest(http.MethodGet, srv.URL, nil) - require.NoError(t, err) - - if tt.requestModifierFn != nil { - tt.requestModifierFn(req) - } - - resp, err := srv.Client().Do(req) - require.NoError(t, err) - require.NoError(t, resp.Body.Close()) - - var got *http.Request - select { - case got = <-reqCh: - // All good - case <-time.After(5 * time.Second): - t.Fatal("Did not receive a signal in 5s") - } - - peer, peerPort := splitHostPort(got.RemoteAddr) - - const user = "alice" - got.SetBasicAuth(user, "pswrd") - - assert.ElementsMatch(t, - []attribute.KeyValue{ - attribute.String("http.method", "GET"), - attribute.String("http.scheme", "http"), - attribute.String("net.host.name", srvURL.Hostname()), - attribute.Int("net.host.port", int(srvPort)), - attribute.String("net.sock.peer.addr", peer), - attribute.Int("net.sock.peer.port", peerPort), - attribute.String("user_agent.original", "Go-http-client/1.1"), - attribute.String("http.client_ip", tt.wantClientIP), - attribute.String("net.protocol.version", "1.1"), - attribute.String("http.target", "/"), - }, - HTTPServerRequest("", got, tt.httpServerRequestOpts, nil)) - }) - } -} - -func TestHTTPServerRequestMetrics(t *testing.T) { - got := make(chan *http.Request, 1) - handler := func(w http.ResponseWriter, r *http.Request) { - got <- r - w.WriteHeader(http.StatusOK) - } - - srv := httptest.NewServer(http.HandlerFunc(handler)) - defer srv.Close() - - srvURL, err := url.Parse(srv.URL) - require.NoError(t, err) - srvPort, err := strconv.ParseInt(srvURL.Port(), 10, 32) - require.NoError(t, err) - - resp, err := srv.Client().Get(srv.URL) - require.NoError(t, err) - require.NoError(t, resp.Body.Close()) - - var req *http.Request - select { - case req = <-got: - // All good - case <-time.After(5 * time.Second): - t.Fatal("did not receive a signal in 5s") - } - - assert.ElementsMatch(t, - []attribute.KeyValue{ - attribute.String("http.method", "GET"), - attribute.String("http.scheme", "http"), - attribute.String("net.host.name", srvURL.Hostname()), - attribute.Int("net.host.port", int(srvPort)), - attribute.String("net.protocol.name", "http"), - attribute.String("net.protocol.version", "1.1"), - }, - HTTPServerRequestMetrics("", req)) -} - -func TestHTTPServerName(t *testing.T) { - req := new(http.Request) - var got []attribute.KeyValue - const ( - host = "test.semconv.server" - port = 8080 - ) - portStr := strconv.Itoa(port) - server := host + ":" + portStr - assert.NotPanics(t, func() { got = HTTPServerRequest(server, req, HTTPServerRequestOptions{}, nil) }) - assert.Contains(t, got, attribute.String("net.host.name", host)) - assert.Contains(t, got, attribute.Int("net.host.port", port)) - - req = &http.Request{Host: "alt.host.name:" + portStr} - // The server parameter does not include a port, ServerRequest should use - // the port in the request Host field. - assert.NotPanics(t, func() { got = HTTPServerRequest(host, req, HTTPServerRequestOptions{}, nil) }) - assert.Contains(t, got, attribute.String("net.host.name", host)) - assert.Contains(t, got, attribute.Int("net.host.port", port)) -} - -func TestHTTPServerRequestFailsGracefully(t *testing.T) { - req := new(http.Request) - var got []attribute.KeyValue - assert.NotPanics(t, func() { got = HTTPServerRequest("", req, HTTPServerRequestOptions{}, nil) }) - want := []attribute.KeyValue{ - attribute.String("http.method", "GET"), - attribute.String("http.scheme", "http"), - attribute.String("net.host.name", ""), - } - assert.ElementsMatch(t, want, got) -} - -func TestHTTPMethod(t *testing.T) { - assert.Equal(t, attribute.String("http.method", "POST"), hc.method("POST")) - assert.Equal(t, attribute.String("http.method", "GET"), hc.method("")) - assert.Equal(t, attribute.String("http.method", "garbage"), hc.method("garbage")) -} - -func TestHTTPScheme(t *testing.T) { - assert.Equal(t, attribute.String("http.scheme", "http"), hc.scheme(false)) - assert.Equal(t, attribute.String("http.scheme", "https"), hc.scheme(true)) -} - -func TestHTTPServerClientIP(t *testing.T) { - tests := []struct { - xForwardedFor string - want string - }{ - {"", ""}, - {"127.0.0.1", "127.0.0.1"}, - {"127.0.0.1,127.0.0.5", "127.0.0.1"}, - } - for _, test := range tests { - got := serverClientIP(test.xForwardedFor) - assert.Equal(t, test.want, got, test.xForwardedFor) - } -} - -func TestRequiredHTTPPort(t *testing.T) { - tests := []struct { - https bool - port int - want int - }{ - {true, 443, -1}, - {true, 80, 80}, - {true, 8081, 8081}, - {false, 443, 443}, - {false, 80, -1}, - {false, 8080, 8080}, - } - for _, test := range tests { - got := requiredHTTPPort(test.https, test.port) - assert.Equalf(t, test.want, got, "HTTPS: %t, Port: %d", test.https, test.port) - } -} - -func TestFirstHostPort(t *testing.T) { - host, port := "127.0.0.1", 8080 - hostport := "127.0.0.1:8080" - sources := [][]string{ - {hostport}, - {"", hostport}, - {"", "", hostport}, - {"", "", hostport, ""}, - {"", "", hostport, "127.0.0.3:80"}, - } - - for _, src := range sources { - h, p := firstHostPort(src...) - assert.Equal(t, host, h, "%+v", src) - assert.Equal(t, port, p, "%+v", src) - } -} - -func TestHTTPClientStatus(t *testing.T) { - tests := []struct { - code int - stat codes.Code - msg bool - }{ - {0, codes.Error, true}, - {http.StatusContinue, codes.Unset, false}, - {http.StatusSwitchingProtocols, codes.Unset, false}, - {http.StatusProcessing, codes.Unset, false}, - {http.StatusEarlyHints, codes.Unset, false}, - {http.StatusOK, codes.Unset, false}, - {http.StatusCreated, codes.Unset, false}, - {http.StatusAccepted, codes.Unset, false}, - {http.StatusNonAuthoritativeInfo, codes.Unset, false}, - {http.StatusNoContent, codes.Unset, false}, - {http.StatusResetContent, codes.Unset, false}, - {http.StatusPartialContent, codes.Unset, false}, - {http.StatusMultiStatus, codes.Unset, false}, - {http.StatusAlreadyReported, codes.Unset, false}, - {http.StatusIMUsed, codes.Unset, false}, - {http.StatusMultipleChoices, codes.Unset, false}, - {http.StatusMovedPermanently, codes.Unset, false}, - {http.StatusFound, codes.Unset, false}, - {http.StatusSeeOther, codes.Unset, false}, - {http.StatusNotModified, codes.Unset, false}, - {http.StatusUseProxy, codes.Unset, false}, - {306, codes.Unset, false}, - {http.StatusTemporaryRedirect, codes.Unset, false}, - {http.StatusPermanentRedirect, codes.Unset, false}, - {http.StatusBadRequest, codes.Error, false}, - {http.StatusUnauthorized, codes.Error, false}, - {http.StatusPaymentRequired, codes.Error, false}, - {http.StatusForbidden, codes.Error, false}, - {http.StatusNotFound, codes.Error, false}, - {http.StatusMethodNotAllowed, codes.Error, false}, - {http.StatusNotAcceptable, codes.Error, false}, - {http.StatusProxyAuthRequired, codes.Error, false}, - {http.StatusRequestTimeout, codes.Error, false}, - {http.StatusConflict, codes.Error, false}, - {http.StatusGone, codes.Error, false}, - {http.StatusLengthRequired, codes.Error, false}, - {http.StatusPreconditionFailed, codes.Error, false}, - {http.StatusRequestEntityTooLarge, codes.Error, false}, - {http.StatusRequestURITooLong, codes.Error, false}, - {http.StatusUnsupportedMediaType, codes.Error, false}, - {http.StatusRequestedRangeNotSatisfiable, codes.Error, false}, - {http.StatusExpectationFailed, codes.Error, false}, - {http.StatusTeapot, codes.Error, false}, - {http.StatusMisdirectedRequest, codes.Error, false}, - {http.StatusUnprocessableEntity, codes.Error, false}, - {http.StatusLocked, codes.Error, false}, - {http.StatusFailedDependency, codes.Error, false}, - {http.StatusTooEarly, codes.Error, false}, - {http.StatusUpgradeRequired, codes.Error, false}, - {http.StatusPreconditionRequired, codes.Error, false}, - {http.StatusTooManyRequests, codes.Error, false}, - {http.StatusRequestHeaderFieldsTooLarge, codes.Error, false}, - {http.StatusUnavailableForLegalReasons, codes.Error, false}, - {499, codes.Error, false}, - {http.StatusInternalServerError, codes.Error, false}, - {http.StatusNotImplemented, codes.Error, false}, - {http.StatusBadGateway, codes.Error, false}, - {http.StatusServiceUnavailable, codes.Error, false}, - {http.StatusGatewayTimeout, codes.Error, false}, - {http.StatusHTTPVersionNotSupported, codes.Error, false}, - {http.StatusVariantAlsoNegotiates, codes.Error, false}, - {http.StatusInsufficientStorage, codes.Error, false}, - {http.StatusLoopDetected, codes.Error, false}, - {http.StatusNotExtended, codes.Error, false}, - {http.StatusNetworkAuthenticationRequired, codes.Error, false}, - {600, codes.Error, true}, - } - - for _, test := range tests { - t.Run(strconv.Itoa(test.code), func(t *testing.T) { - c, msg := HTTPClientStatus(test.code) - assert.Equal(t, test.stat, c) - if test.msg && msg == "" { - t.Errorf("expected non-empty message for %d", test.code) - } else if !test.msg && msg != "" { - t.Errorf("expected empty message for %d, got: %s", test.code, msg) - } - }) - } -} - -func TestHTTPServerStatus(t *testing.T) { - tests := []struct { - code int - stat codes.Code - msg bool - }{ - {0, codes.Error, true}, - {http.StatusContinue, codes.Unset, false}, - {http.StatusSwitchingProtocols, codes.Unset, false}, - {http.StatusProcessing, codes.Unset, false}, - {http.StatusEarlyHints, codes.Unset, false}, - {http.StatusOK, codes.Unset, false}, - {http.StatusCreated, codes.Unset, false}, - {http.StatusAccepted, codes.Unset, false}, - {http.StatusNonAuthoritativeInfo, codes.Unset, false}, - {http.StatusNoContent, codes.Unset, false}, - {http.StatusResetContent, codes.Unset, false}, - {http.StatusPartialContent, codes.Unset, false}, - {http.StatusMultiStatus, codes.Unset, false}, - {http.StatusAlreadyReported, codes.Unset, false}, - {http.StatusIMUsed, codes.Unset, false}, - {http.StatusMultipleChoices, codes.Unset, false}, - {http.StatusMovedPermanently, codes.Unset, false}, - {http.StatusFound, codes.Unset, false}, - {http.StatusSeeOther, codes.Unset, false}, - {http.StatusNotModified, codes.Unset, false}, - {http.StatusUseProxy, codes.Unset, false}, - {306, codes.Unset, false}, - {http.StatusTemporaryRedirect, codes.Unset, false}, - {http.StatusPermanentRedirect, codes.Unset, false}, - {http.StatusBadRequest, codes.Unset, false}, - {http.StatusUnauthorized, codes.Unset, false}, - {http.StatusPaymentRequired, codes.Unset, false}, - {http.StatusForbidden, codes.Unset, false}, - {http.StatusNotFound, codes.Unset, false}, - {http.StatusMethodNotAllowed, codes.Unset, false}, - {http.StatusNotAcceptable, codes.Unset, false}, - {http.StatusProxyAuthRequired, codes.Unset, false}, - {http.StatusRequestTimeout, codes.Unset, false}, - {http.StatusConflict, codes.Unset, false}, - {http.StatusGone, codes.Unset, false}, - {http.StatusLengthRequired, codes.Unset, false}, - {http.StatusPreconditionFailed, codes.Unset, false}, - {http.StatusRequestEntityTooLarge, codes.Unset, false}, - {http.StatusRequestURITooLong, codes.Unset, false}, - {http.StatusUnsupportedMediaType, codes.Unset, false}, - {http.StatusRequestedRangeNotSatisfiable, codes.Unset, false}, - {http.StatusExpectationFailed, codes.Unset, false}, - {http.StatusTeapot, codes.Unset, false}, - {http.StatusMisdirectedRequest, codes.Unset, false}, - {http.StatusUnprocessableEntity, codes.Unset, false}, - {http.StatusLocked, codes.Unset, false}, - {http.StatusFailedDependency, codes.Unset, false}, - {http.StatusTooEarly, codes.Unset, false}, - {http.StatusUpgradeRequired, codes.Unset, false}, - {http.StatusPreconditionRequired, codes.Unset, false}, - {http.StatusTooManyRequests, codes.Unset, false}, - {http.StatusRequestHeaderFieldsTooLarge, codes.Unset, false}, - {http.StatusUnavailableForLegalReasons, codes.Unset, false}, - {499, codes.Unset, false}, - {http.StatusInternalServerError, codes.Error, false}, - {http.StatusNotImplemented, codes.Error, false}, - {http.StatusBadGateway, codes.Error, false}, - {http.StatusServiceUnavailable, codes.Error, false}, - {http.StatusGatewayTimeout, codes.Error, false}, - {http.StatusHTTPVersionNotSupported, codes.Error, false}, - {http.StatusVariantAlsoNegotiates, codes.Error, false}, - {http.StatusInsufficientStorage, codes.Error, false}, - {http.StatusLoopDetected, codes.Error, false}, - {http.StatusNotExtended, codes.Error, false}, - {http.StatusNetworkAuthenticationRequired, codes.Error, false}, - {600, codes.Error, true}, - } - - for _, test := range tests { - c, msg := HTTPServerStatus(test.code) - assert.Equal(t, test.stat, c) - if test.msg && msg == "" { - t.Errorf("expected non-empty message for %d", test.code) - } else if !test.msg && msg != "" { - t.Errorf("expected empty message for %d, got: %s", test.code, msg) - } - } -} diff --git a/instrumentation/net/http/httptrace/otelhttptrace/internal/semconvutil/netconv.go b/instrumentation/net/http/httptrace/otelhttptrace/internal/semconvutil/netconv.go deleted file mode 100644 index 1cb0e4a245e..00000000000 --- a/instrumentation/net/http/httptrace/otelhttptrace/internal/semconvutil/netconv.go +++ /dev/null @@ -1,214 +0,0 @@ -// Code generated by gotmpl. DO NOT MODIFY. -// source: internal/shared/semconvutil/netconv.go.tmpl - -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -package semconvutil // import "go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace/internal/semconvutil" - -import ( - "net" - "strconv" - "strings" - - "go.opentelemetry.io/otel/attribute" - semconv "go.opentelemetry.io/otel/semconv/v1.20.0" -) - -// NetTransport returns a trace attribute describing the transport protocol of the -// passed network. See the net.Dial for information about acceptable network -// values. -func NetTransport(network string) attribute.KeyValue { - return nc.Transport(network) -} - -// netConv are the network semantic convention attributes defined for a version -// of the OpenTelemetry specification. -type netConv struct { - NetHostNameKey attribute.Key - NetHostPortKey attribute.Key - NetPeerNameKey attribute.Key - NetPeerPortKey attribute.Key - NetProtocolName attribute.Key - NetProtocolVersion attribute.Key - NetSockFamilyKey attribute.Key - NetSockPeerAddrKey attribute.Key - NetSockPeerPortKey attribute.Key - NetSockHostAddrKey attribute.Key - NetSockHostPortKey attribute.Key - NetTransportOther attribute.KeyValue - NetTransportTCP attribute.KeyValue - NetTransportUDP attribute.KeyValue - NetTransportInProc attribute.KeyValue -} - -var nc = &netConv{ - NetHostNameKey: semconv.NetHostNameKey, - NetHostPortKey: semconv.NetHostPortKey, - NetPeerNameKey: semconv.NetPeerNameKey, - NetPeerPortKey: semconv.NetPeerPortKey, - NetProtocolName: semconv.NetProtocolNameKey, - NetProtocolVersion: semconv.NetProtocolVersionKey, - NetSockFamilyKey: semconv.NetSockFamilyKey, - NetSockPeerAddrKey: semconv.NetSockPeerAddrKey, - NetSockPeerPortKey: semconv.NetSockPeerPortKey, - NetSockHostAddrKey: semconv.NetSockHostAddrKey, - NetSockHostPortKey: semconv.NetSockHostPortKey, - NetTransportOther: semconv.NetTransportOther, - NetTransportTCP: semconv.NetTransportTCP, - NetTransportUDP: semconv.NetTransportUDP, - NetTransportInProc: semconv.NetTransportInProc, -} - -func (c *netConv) Transport(network string) attribute.KeyValue { - switch network { - case "tcp", "tcp4", "tcp6": - return c.NetTransportTCP - case "udp", "udp4", "udp6": - return c.NetTransportUDP - case "unix", "unixgram", "unixpacket": - return c.NetTransportInProc - default: - // "ip:*", "ip4:*", and "ip6:*" all are considered other. - return c.NetTransportOther - } -} - -// Host returns attributes for a network host address. -func (c *netConv) Host(address string) []attribute.KeyValue { - h, p := splitHostPort(address) - var n int - if h != "" { - n++ - if p > 0 { - n++ - } - } - - if n == 0 { - return nil - } - - attrs := make([]attribute.KeyValue, 0, n) - attrs = append(attrs, c.HostName(h)) - if p > 0 { - attrs = append(attrs, c.HostPort(p)) - } - return attrs -} - -func (c *netConv) HostName(name string) attribute.KeyValue { - return c.NetHostNameKey.String(name) -} - -func (c *netConv) HostPort(port int) attribute.KeyValue { - return c.NetHostPortKey.Int(port) -} - -func family(network, address string) string { - switch network { - case "unix", "unixgram", "unixpacket": - return "unix" - default: - if ip := net.ParseIP(address); ip != nil { - if ip.To4() == nil { - return "inet6" - } - return "inet" - } - } - return "" -} - -// Peer returns attributes for a network peer address. -func (c *netConv) Peer(address string) []attribute.KeyValue { - h, p := splitHostPort(address) - var n int - if h != "" { - n++ - if p > 0 { - n++ - } - } - - if n == 0 { - return nil - } - - attrs := make([]attribute.KeyValue, 0, n) - attrs = append(attrs, c.PeerName(h)) - if p > 0 { - attrs = append(attrs, c.PeerPort(p)) - } - return attrs -} - -func (c *netConv) PeerName(name string) attribute.KeyValue { - return c.NetPeerNameKey.String(name) -} - -func (c *netConv) PeerPort(port int) attribute.KeyValue { - return c.NetPeerPortKey.Int(port) -} - -func (c *netConv) SockPeerAddr(addr string) attribute.KeyValue { - return c.NetSockPeerAddrKey.String(addr) -} - -func (c *netConv) SockPeerPort(port int) attribute.KeyValue { - return c.NetSockPeerPortKey.Int(port) -} - -// splitHostPort splits a network address hostport of the form "host", -// "host%zone", "[host]", "[host%zone], "host:port", "host%zone:port", -// "[host]:port", "[host%zone]:port", or ":port" into host or host%zone and -// port. -// -// An empty host is returned if it is not provided or unparsable. A negative -// port is returned if it is not provided or unparsable. -func splitHostPort(hostport string) (host string, port int) { - port = -1 - - if strings.HasPrefix(hostport, "[") { - addrEnd := strings.LastIndex(hostport, "]") - if addrEnd < 0 { - // Invalid hostport. - return - } - if i := strings.LastIndex(hostport[addrEnd:], ":"); i < 0 { - host = hostport[1:addrEnd] - return - } - } else { - if i := strings.LastIndex(hostport, ":"); i < 0 { - host = hostport - return - } - } - - host, pStr, err := net.SplitHostPort(hostport) - if err != nil { - return - } - - p, err := strconv.ParseUint(pStr, 10, 16) - if err != nil { - return - } - return host, int(p) // nolint: gosec // Bitsize checked to be 16 above. -} - -func netProtocol(proto string) (name string, version string) { - name, version, _ = strings.Cut(proto, "/") - switch name { - case "HTTP": - name = "http" - case "QUIC": - name = "quic" - case "SPDY": - name = "spdy" - default: - name = strings.ToLower(name) - } - return name, version -} diff --git a/instrumentation/net/http/httptrace/otelhttptrace/internal/semconvutil/netconv_test.go b/instrumentation/net/http/httptrace/otelhttptrace/internal/semconvutil/netconv_test.go deleted file mode 100644 index 4f3c1540c13..00000000000 --- a/instrumentation/net/http/httptrace/otelhttptrace/internal/semconvutil/netconv_test.go +++ /dev/null @@ -1,200 +0,0 @@ -// Code generated by gotmpl. DO NOT MODIFY. -// source: internal/shared/semconvutil/netconv_test.go.tmpl - -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -package semconvutil - -import ( - "testing" - - "github.com/stretchr/testify/assert" - - "go.opentelemetry.io/otel/attribute" -) - -const ( - addr = "127.0.0.1" - port = 1834 -) - -func TestNetTransport(t *testing.T) { - transports := map[string]attribute.KeyValue{ - "tcp": attribute.String("net.transport", "ip_tcp"), - "tcp4": attribute.String("net.transport", "ip_tcp"), - "tcp6": attribute.String("net.transport", "ip_tcp"), - "udp": attribute.String("net.transport", "ip_udp"), - "udp4": attribute.String("net.transport", "ip_udp"), - "udp6": attribute.String("net.transport", "ip_udp"), - "unix": attribute.String("net.transport", "inproc"), - "unixgram": attribute.String("net.transport", "inproc"), - "unixpacket": attribute.String("net.transport", "inproc"), - "ip:1": attribute.String("net.transport", "other"), - "ip:icmp": attribute.String("net.transport", "other"), - "ip4:proto": attribute.String("net.transport", "other"), - "ip6:proto": attribute.String("net.transport", "other"), - } - - for network, want := range transports { - assert.Equal(t, want, NetTransport(network)) - } -} - -func TestNetHost(t *testing.T) { - testAddrs(t, []addrTest{ - {address: "", expected: nil}, - {address: "192.0.0.1", expected: []attribute.KeyValue{ - nc.HostName("192.0.0.1"), - }}, - {address: "192.0.0.1:9090", expected: []attribute.KeyValue{ - nc.HostName("192.0.0.1"), - nc.HostPort(9090), - }}, - }, nc.Host) -} - -func TestNetHostName(t *testing.T) { - expected := attribute.Key("net.host.name").String(addr) - assert.Equal(t, expected, nc.HostName(addr)) -} - -func TestNetHostPort(t *testing.T) { - expected := attribute.Key("net.host.port").Int(port) - assert.Equal(t, expected, nc.HostPort(port)) -} - -func TestNetPeer(t *testing.T) { - testAddrs(t, []addrTest{ - {address: "", expected: nil}, - {address: "example.com", expected: []attribute.KeyValue{ - nc.PeerName("example.com"), - }}, - {address: "/tmp/file", expected: []attribute.KeyValue{ - nc.PeerName("/tmp/file"), - }}, - {address: "192.0.0.1", expected: []attribute.KeyValue{ - nc.PeerName("192.0.0.1"), - }}, - {address: ":9090", expected: nil}, - {address: "192.0.0.1:9090", expected: []attribute.KeyValue{ - nc.PeerName("192.0.0.1"), - nc.PeerPort(9090), - }}, - }, nc.Peer) -} - -func TestNetPeerName(t *testing.T) { - expected := attribute.Key("net.peer.name").String(addr) - assert.Equal(t, expected, nc.PeerName(addr)) -} - -func TestNetPeerPort(t *testing.T) { - expected := attribute.Key("net.peer.port").Int(port) - assert.Equal(t, expected, nc.PeerPort(port)) -} - -func TestNetSockPeerName(t *testing.T) { - expected := attribute.Key("net.sock.peer.addr").String(addr) - assert.Equal(t, expected, nc.SockPeerAddr(addr)) -} - -func TestNetSockPeerPort(t *testing.T) { - expected := attribute.Key("net.sock.peer.port").Int(port) - assert.Equal(t, expected, nc.SockPeerPort(port)) -} - -func TestNetFamily(t *testing.T) { - tests := []struct { - network string - address string - expect string - }{ - {"", "", ""}, - {"unix", "", "unix"}, - {"unix", "gibberish", "unix"}, - {"unixgram", "", "unix"}, - {"unixgram", "gibberish", "unix"}, - {"unixpacket", "gibberish", "unix"}, - {"tcp", "123.0.2.8", "inet"}, - {"tcp", "gibberish", ""}, - {"", "123.0.2.8", "inet"}, - {"", "gibberish", ""}, - {"tcp", "fe80::1", "inet6"}, - {"", "fe80::1", "inet6"}, - } - - for _, test := range tests { - got := family(test.network, test.address) - assert.Equal(t, test.expect, got, test.network+"/"+test.address) - } -} - -func TestSplitHostPort(t *testing.T) { - tests := []struct { - hostport string - host string - port int - }{ - {"", "", -1}, - {":8080", "", 8080}, - {"127.0.0.1", "127.0.0.1", -1}, - {"www.example.com", "www.example.com", -1}, - {"127.0.0.1%25en0", "127.0.0.1%25en0", -1}, - {"[]", "", -1}, // Ensure this doesn't panic. - {"[fe80::1", "", -1}, - {"[fe80::1]", "fe80::1", -1}, - {"[fe80::1%25en0]", "fe80::1%25en0", -1}, - {"[fe80::1]:8080", "fe80::1", 8080}, - {"[fe80::1]::", "", -1}, // Too many colons. - {"127.0.0.1:", "127.0.0.1", -1}, - {"127.0.0.1:port", "127.0.0.1", -1}, - {"127.0.0.1:8080", "127.0.0.1", 8080}, - {"www.example.com:8080", "www.example.com", 8080}, - {"127.0.0.1%25en0:8080", "127.0.0.1%25en0", 8080}, - } - - for _, test := range tests { - h, p := splitHostPort(test.hostport) - assert.Equal(t, test.host, h, test.hostport) - assert.Equal(t, test.port, p, test.hostport) - } -} - -type addrTest struct { - address string - expected []attribute.KeyValue -} - -func testAddrs(t *testing.T, tests []addrTest, f func(string) []attribute.KeyValue) { - t.Helper() - - for _, test := range tests { - got := f(test.address) - assert.Equal(t, cap(test.expected), cap(got), "slice capacity") - assert.ElementsMatch(t, test.expected, got, test.address) - } -} - -func TestNetProtocol(t *testing.T) { - type testCase struct { - name, version string - } - tests := map[string]testCase{ - "HTTP/1.0": {name: "http", version: "1.0"}, - "HTTP/1.1": {name: "http", version: "1.1"}, - "HTTP/2": {name: "http", version: "2"}, - "HTTP/3": {name: "http", version: "3"}, - "SPDY": {name: "spdy"}, - "SPDY/2": {name: "spdy", version: "2"}, - "QUIC": {name: "quic"}, - "unknown/proto/2": {name: "unknown", version: "proto/2"}, - "other": {name: "other"}, - } - - for proto, want := range tests { - name, version := netProtocol(proto) - assert.Equal(t, want.name, name) - assert.Equal(t, want.version, version) - } -} diff --git a/instrumentation/net/http/otelhttp/internal/semconv/env.go b/instrumentation/net/http/otelhttp/internal/semconv/env.go index 7edc8d10ff1..ce7a693eedb 100644 --- a/instrumentation/net/http/otelhttp/internal/semconv/env.go +++ b/instrumentation/net/http/otelhttp/internal/semconv/env.go @@ -10,7 +10,6 @@ import ( "context" "fmt" "net/http" - "os" "strings" "sync" @@ -33,14 +32,6 @@ type ResponseTelemetry struct { } type HTTPServer struct { - duplicate bool - - // Old metrics - requestBytesCounter metric.Int64Counter - responseBytesCounter metric.Int64Counter - serverLatencyMeasure metric.Float64Histogram - - // New metrics requestBodySizeHistogram httpconv.ServerRequestBodySize responseBodySizeHistogram httpconv.ServerResponseBodySize requestDurationHistogram httpconv.ServerRequestDuration @@ -63,20 +54,10 @@ type HTTPServer struct { // If the primary server name is not known, server should be an empty string. // The req Host will be used to determine the server instead. func (s HTTPServer) RequestTraceAttrs(server string, req *http.Request, opts RequestTraceAttrsOpts) []attribute.KeyValue { - attrs := CurrentHTTPServer{}.RequestTraceAttrs(server, req, opts) - if s.duplicate { - return OldHTTPServer{}.RequestTraceAttrs(server, req, attrs) - } - return attrs + return CurrentHTTPServer{}.RequestTraceAttrs(server, req, opts) } func (s HTTPServer) NetworkTransportAttr(network string) []attribute.KeyValue { - if s.duplicate { - return []attribute.KeyValue{ - OldHTTPServer{}.NetworkTransportAttr(network), - CurrentHTTPServer{}.NetworkTransportAttr(network), - } - } return []attribute.KeyValue{ CurrentHTTPServer{}.NetworkTransportAttr(network), } @@ -86,11 +67,7 @@ func (s HTTPServer) NetworkTransportAttr(network string) []attribute.KeyValue { // // If any of the fields in the ResponseTelemetry are not set the attribute will be omitted. func (s HTTPServer) ResponseTraceAttrs(resp ResponseTelemetry) []attribute.KeyValue { - attrs := CurrentHTTPServer{}.ResponseTraceAttrs(resp) - if s.duplicate { - return OldHTTPServer{}.ResponseTraceAttrs(resp, attrs) - } - return attrs + return CurrentHTTPServer{}.ResponseTraceAttrs(resp) } // Route returns the attribute for the route. @@ -156,18 +133,6 @@ func (s HTTPServer) RecordMetrics(ctx context.Context, md ServerMetricData) { s.requestDurationHistogram.Inst().Record(ctx, md.ElapsedTime/1000.0, o) *recordOpts = (*recordOpts)[:0] metricRecordOptionPool.Put(recordOpts) - - if s.duplicate && 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) - } } // hasOptIn returns true if the comma-separated version string contains the @@ -182,11 +147,7 @@ func hasOptIn(version, optIn string) bool { } func NewHTTPServer(meter metric.Meter) HTTPServer { - env := strings.ToLower(os.Getenv(OTelSemConvStabilityOptIn)) - duplicate := hasOptIn(env, "http/dup") - server := HTTPServer{ - duplicate: duplicate, - } + server := HTTPServer{} var err error server.requestBodySizeHistogram, err = httpconv.NewServerRequestBodySize(meter) @@ -203,32 +164,16 @@ func NewHTTPServer(meter metric.Meter) HTTPServer { ), ) handleErr(err) - - if duplicate { - server.requestBytesCounter, server.responseBytesCounter, server.serverLatencyMeasure = OldHTTPServer{}.createMeasures(meter) - } return server } type HTTPClient struct { - duplicate bool - - // old metrics - requestBytesCounter metric.Int64Counter - responseBytesCounter metric.Int64Counter - latencyMeasure metric.Float64Histogram - - // new metrics requestBodySize httpconv.ClientRequestBodySize requestDuration httpconv.ClientRequestDuration } func NewHTTPClient(meter metric.Meter) HTTPClient { - env := strings.ToLower(os.Getenv(OTelSemConvStabilityOptIn)) - duplicate := hasOptIn(env, "http/dup") - client := HTTPClient{ - duplicate: duplicate, - } + client := HTTPClient{} var err error client.requestBodySize, err = httpconv.NewClientRequestBodySize(meter) @@ -240,29 +185,17 @@ func NewHTTPClient(meter metric.Meter) HTTPClient { ) handleErr(err) - if duplicate { - client.requestBytesCounter, client.responseBytesCounter, client.latencyMeasure = OldHTTPClient{}.createMeasures(meter) - } - return client } // RequestTraceAttrs returns attributes for an HTTP request made by a client. func (c HTTPClient) RequestTraceAttrs(req *http.Request) []attribute.KeyValue { - attrs := CurrentHTTPClient{}.RequestTraceAttrs(req) - if c.duplicate { - return OldHTTPClient{}.RequestTraceAttrs(req, attrs) - } - return attrs + return CurrentHTTPClient{}.RequestTraceAttrs(req) } // ResponseTraceAttrs returns metric attributes for an HTTP request made by a client. func (c HTTPClient) ResponseTraceAttrs(resp *http.Response) []attribute.KeyValue { - attrs := CurrentHTTPClient{}.ResponseTraceAttrs(resp) - if c.duplicate { - return OldHTTPClient{}.ResponseTraceAttrs(resp, attrs) - } - return attrs + return CurrentHTTPClient{}.ResponseTraceAttrs(resp) } func (c HTTPClient) Status(code int) (codes.Code, string) { @@ -302,42 +235,14 @@ func (c HTTPClient) MetricOptions(ma MetricAttributes) map[string]MetricOpts { addOptions: set, } - if c.duplicate { - attributes := OldHTTPClient{}.MetricAttributes(ma.Req, ma.StatusCode, ma.AdditionalAttributes) - set := metric.WithAttributeSet(attribute.NewSet(attributes...)) - opts["old"] = MetricOpts{ - measurement: set, - addOptions: set, - } - } - return opts } func (s HTTPClient) RecordMetrics(ctx context.Context, md MetricData, opts map[string]MetricOpts) { s.requestBodySize.Inst().Record(ctx, md.RequestSize, opts["new"].MeasurementOption()) s.requestDuration.Inst().Record(ctx, md.ElapsedTime/1000, opts["new"].MeasurementOption()) - - if s.duplicate { - s.requestBytesCounter.Add(ctx, md.RequestSize, opts["old"].AddOptions()) - s.latencyMeasure.Record(ctx, md.ElapsedTime, opts["old"].MeasurementOption()) - } -} - -func (s HTTPClient) RecordResponseSize(ctx context.Context, responseData int64, opts map[string]MetricOpts) { - if s.responseBytesCounter == nil { - // This will happen if an HTTPClient{} is used instead of NewHTTPClient(). - return - } - - s.responseBytesCounter.Add(ctx, responseData, opts["old"].AddOptions()) } func (s HTTPClient) TraceAttributes(host string) []attribute.KeyValue { - attrs := CurrentHTTPClient{}.TraceAttributes(host) - if s.duplicate { - return OldHTTPClient{}.TraceAttributes(host, attrs) - } - - return attrs + return CurrentHTTPClient{}.TraceAttributes(host) } diff --git a/instrumentation/net/http/otelhttp/internal/semconv/env_test.go b/instrumentation/net/http/otelhttp/internal/semconv/env_test.go index 45b520854dd..11388bb8324 100644 --- a/instrumentation/net/http/otelhttp/internal/semconv/env_test.go +++ b/instrumentation/net/http/otelhttp/internal/semconv/env_test.go @@ -54,7 +54,6 @@ func TestHTTPServerDoesNotPanic(t *testing.T) { func TestServerNetworkTransportAttr(t *testing.T) { for _, tt := range []struct { name string - optinVal string network string wantAttributes []attribute.KeyValue @@ -67,29 +66,8 @@ func TestServerNetworkTransportAttr(t *testing.T) { attribute.String("network.transport", "tcp"), }, }, - { - name: "without a dup optin", - optinVal: "http/dup", - network: "tcp", - - wantAttributes: []attribute.KeyValue{ - attribute.String("net.transport", "ip_tcp"), - attribute.String("network.transport", "tcp"), - }, - }, - { - name: "with mixed categories", - optinVal: "http/dup,database", - network: "tcp", - - wantAttributes: []attribute.KeyValue{ - attribute.String("net.transport", "ip_tcp"), - attribute.String("network.transport", "tcp"), - }, - }, } { t.Run(tt.name, func(t *testing.T) { - t.Setenv(OTelSemConvStabilityOptIn, tt.optinVal) s := NewHTTPServer(nil) assert.Equal(t, tt.wantAttributes, s.NetworkTransportAttr(tt.network)) @@ -124,7 +102,6 @@ func TestHTTPClientDoesNotPanic(t *testing.T) { Req: req, StatusCode: 200, }) - tt.client.RecordResponseSize(context.Background(), 40, opts) tt.client.RecordMetrics(context.Background(), MetricData{ RequestSize: 20, ElapsedTime: 1, @@ -137,7 +114,6 @@ func TestHTTPClientDoesNotPanic(t *testing.T) { func TestHTTPClientTraceAttributes(t *testing.T) { for _, tt := range []struct { name string - optinVal string wantAttributes []attribute.KeyValue }{ @@ -148,28 +124,8 @@ func TestHTTPClientTraceAttributes(t *testing.T) { attribute.String("server.address", "example.com"), }, }, - { - name: "with optin set to duplicate", - optinVal: "http/dup", - - wantAttributes: []attribute.KeyValue{ - attribute.String("server.address", "example.com"), - attribute.String("net.host.name", "example.com"), - }, - }, - { - name: "with mixed categories", - optinVal: "http/dup,database", - - wantAttributes: []attribute.KeyValue{ - attribute.String("server.address", "example.com"), - attribute.String("net.host.name", "example.com"), - }, - }, } { t.Run(tt.name, func(t *testing.T) { - t.Setenv(OTelSemConvStabilityOptIn, tt.optinVal) - c := NewHTTPClient(nil) a := c.TraceAttributes("example.com") assert.Equal(t, tt.wantAttributes, a) @@ -180,7 +136,6 @@ func TestHTTPClientTraceAttributes(t *testing.T) { func TestClientTraceAttributes(t *testing.T) { for _, tt := range []struct { name string - optinVal string host string wantAttributes []attribute.KeyValue @@ -193,29 +148,8 @@ func TestClientTraceAttributes(t *testing.T) { attribute.String("server.address", "example.com"), }, }, - { - name: "without a dup optin", - optinVal: "http/dup", - host: "example.com", - - wantAttributes: []attribute.KeyValue{ - attribute.String("server.address", "example.com"), - attribute.String("net.host.name", "example.com"), - }, - }, - { - name: "with mixed categories", - optinVal: "http/dup", - host: "example.com", - - wantAttributes: []attribute.KeyValue{ - attribute.String("server.address", "example.com"), - attribute.String("net.host.name", "example.com"), - }, - }, } { t.Run(tt.name, func(t *testing.T) { - t.Setenv(OTelSemConvStabilityOptIn, tt.optinVal) s := NewHTTPClient(nil) assert.Equal(t, tt.wantAttributes, s.TraceAttributes(tt.host)) diff --git a/instrumentation/net/http/otelhttp/internal/semconv/gen.go b/instrumentation/net/http/otelhttp/internal/semconv/gen.go index b4036dd906a..1bb207b8092 100644 --- a/instrumentation/net/http/otelhttp/internal/semconv/gen.go +++ b/instrumentation/net/http/otelhttp/internal/semconv/gen.go @@ -13,4 +13,3 @@ package semconv // import "go.opentelemetry.io/contrib/instrumentation/net/http/ //go:generate gotmpl --body=../../../../../../internal/shared/semconv/httpconvtest_test.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=httpconvtest_test.go //go:generate gotmpl --body=../../../../../../internal/shared/semconv/util.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=util.go //go:generate gotmpl --body=../../../../../../internal/shared/semconv/util_test.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=util_test.go -//go:generate gotmpl --body=../../../../../../internal/shared/semconv/v1.20.0.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\" }" --out=v1.20.0.go diff --git a/instrumentation/net/http/otelhttp/internal/semconv/httpconvtest_test.go b/instrumentation/net/http/otelhttp/internal/semconv/httpconvtest_test.go index d085b78b49b..f8b69d5c5f8 100644 --- a/instrumentation/net/http/otelhttp/internal/semconv/httpconvtest_test.go +++ b/instrumentation/net/http/otelhttp/internal/semconv/httpconvtest_test.go @@ -28,7 +28,6 @@ import ( ) func TestNewTraceRequest(t *testing.T) { - t.Setenv("OTEL_SEMCONV_STABILITY_OPT_IN", "http/dup") serv := semconv.NewHTTPServer(nil) want := func(req testServerReq) []attribute.KeyValue { return []attribute.KeyValue{ @@ -42,16 +41,6 @@ func TestNewTraceRequest(t *testing.T) { attribute.String("client.address", req.clientIP), attribute.String("network.protocol.version", "1.1"), attribute.String("url.path", "/"), - attribute.String("http.method", "GET"), - attribute.String("http.scheme", "http"), - attribute.String("net.host.name", req.hostname), - attribute.Int("net.host.port", req.serverPort), - attribute.String("net.sock.peer.addr", req.peerAddr), - attribute.Int("net.sock.peer.port", req.peerPort), - attribute.String("user_agent.original", "Go-http-client/1.1"), - attribute.String("http.client_ip", req.clientIP), - attribute.String("net.protocol.version", "1.1"), - attribute.String("http.target", "/"), } } testTraceRequest(t, serv, want) @@ -174,13 +163,11 @@ func TestNewServerRecordMetrics(t *testing.T) { 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, + name: "No Meter", serverFunc: func(metric.MeterProvider) semconv.HTTPServer { return semconv.NewHTTPServer(nil) }, @@ -189,8 +176,7 @@ func TestNewServerRecordMetrics(t *testing.T) { }, }, { - name: "No environment variable set, but with Meter", - setEnv: false, + name: "With Meter", serverFunc: func(mp metric.MeterProvider) semconv.HTTPServer { return semconv.NewHTTPServer(mp.Meter("test")) }, @@ -202,36 +188,10 @@ func TestNewServerRecordMetrics(t *testing.T) { metricdatatest.AssertEqual(t, expectedCurrentScopeMetric, 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, expectedOldScopeMetric, 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)) @@ -312,7 +272,6 @@ func TestNewTraceResponse(t *testing.T) { } func TestNewTraceRequest_Client(t *testing.T) { - t.Setenv("OTEL_SEMCONV_STABILITY_OPT_IN", "http/dup") body := strings.NewReader("Hello, world!") url := "https://example.com:8888/foo/bar?stuff=morestuff" req := httptest.NewRequest("pOST", url, body) @@ -321,29 +280,22 @@ func TestNewTraceRequest_Client(t *testing.T) { want := []attribute.KeyValue{ attribute.String("http.request.method", "POST"), attribute.String("http.request.method_original", "pOST"), - attribute.String("http.method", "pOST"), attribute.String("url.full", url), - attribute.String("http.url", url), attribute.String("server.address", "example.com"), attribute.Int("server.port", 8888), attribute.String("network.protocol.version", "1.1"), - attribute.String("net.peer.name", "example.com"), - attribute.Int("net.peer.port", 8888), - attribute.String("user_agent.original", "go-test-agent"), - attribute.Int("http.request_content_length", 13), } client := semconv.NewHTTPClient(nil) assert.ElementsMatch(t, want, client.RequestTraceAttrs(req)) } func TestNewTraceResponse_Client(t *testing.T) { - t.Setenv("OTEL_SEMCONV_STABILITY_OPT_IN", "http/dup") testcases := []struct { resp http.Response want []attribute.KeyValue }{ - {resp: http.Response{StatusCode: 200, ContentLength: 123}, want: []attribute.KeyValue{attribute.Int("http.response.status_code", 200), attribute.Int("http.status_code", 200), attribute.Int("http.response_content_length", 123)}}, - {resp: http.Response{StatusCode: 404, ContentLength: 0}, want: []attribute.KeyValue{attribute.Int("http.response.status_code", 404), attribute.Int("http.status_code", 404), attribute.String("error.type", "404")}}, + {resp: http.Response{StatusCode: 200, ContentLength: 123}, want: []attribute.KeyValue{attribute.Int("http.response.status_code", 200)}}, + {resp: http.Response{StatusCode: 404, ContentLength: 0}, want: []attribute.KeyValue{attribute.Int("http.response.status_code", 404), attribute.String("error.type", "404")}}, } for _, tt := range testcases { @@ -401,12 +353,6 @@ func TestRequestErrorType(t *testing.T) { } func TestNewClientRecordMetrics(t *testing.T) { - oldAttrs := attribute.NewSet( - attribute.String("http.method", "POST"), - attribute.Int64("http.status_code", 301), - attribute.String("net.peer.name", "example.com"), - ) - currAttrs := attribute.NewSet( attribute.String("http.request.method", "POST"), attribute.Int64("http.response.status_code", 301), @@ -451,47 +397,13 @@ func TestNewClientRecordMetrics(t *testing.T) { }, } - // The OldHTTPClient version - expectedOldScopeMetric := expectedCurrentScopeMetric - expectedOldScopeMetric.Metrics = append(expectedOldScopeMetric.Metrics, []metricdata.Metrics{ - { - Name: "http.client.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.client.duration", - Description: "Measures the duration of outbound HTTP requests.", - Unit: "ms", - Data: metricdata.Histogram[float64]{ - Temporality: metricdata.CumulativeTemporality, - DataPoints: []metricdata.HistogramDataPoint[float64]{ - { - Attributes: oldAttrs, - }, - }, - }, - }, - }...) - tests := []struct { name string - setEnv bool clientFunc func(metric.MeterProvider) semconv.HTTPClient wantFunc func(t *testing.T, rm metricdata.ResourceMetrics) }{ { name: "No environment variable set, and no Meter", - setEnv: false, clientFunc: func(metric.MeterProvider) semconv.HTTPClient { return semconv.NewHTTPClient(nil) }, @@ -500,8 +412,7 @@ func TestNewClientRecordMetrics(t *testing.T) { }, }, { - name: "No environment variable set, but with Meter", - setEnv: false, + name: "With Meter", clientFunc: func(mp metric.MeterProvider) semconv.HTTPClient { return semconv.NewHTTPClient(mp.Meter("test")) }, @@ -512,36 +423,10 @@ func TestNewClientRecordMetrics(t *testing.T) { metricdatatest.AssertEqual(t, expectedCurrentScopeMetric, rm.ScopeMetrics[0], metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreValue()) }, }, - { - name: "Set environment variable, but no Meter", - setEnv: true, - clientFunc: func(metric.MeterProvider) semconv.HTTPClient { - return semconv.NewHTTPClient(nil) - }, - wantFunc: func(t *testing.T, rm metricdata.ResourceMetrics) { - assert.Empty(t, rm.ScopeMetrics) - }, - }, - { - name: "Set environment variable and Meter", - setEnv: true, - clientFunc: func(mp metric.MeterProvider) semconv.HTTPClient { - return semconv.NewHTTPClient(mp.Meter("test")) - }, - wantFunc: func(t *testing.T, rm metricdata.ResourceMetrics) { - require.Len(t, rm.ScopeMetrics, 1) - require.Len(t, rm.ScopeMetrics[0].Metrics, 4) - metricdatatest.AssertEqual(t, expectedOldScopeMetric, 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)) @@ -564,111 +449,6 @@ func TestNewClientRecordMetrics(t *testing.T) { } } -func TestClientRecordResponseSize(t *testing.T) { - oldAttrs := attribute.NewSet( - attribute.String("http.method", "POST"), - attribute.Int64("http.status_code", 301), - attribute.String("net.peer.name", "example.com"), - ) - - // The OldHTTPClient version - expectedOldScopeMetric := metricdata.ScopeMetrics{ - Scope: instrumentation.Scope{ - Name: "test", - }, - Metrics: []metricdata.Metrics{ - { - Name: "http.client.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, - }, - }, - }, - }, - }, - } - - tests := []struct { - name string - setEnv bool - clientFunc func(metric.MeterProvider) semconv.HTTPClient - wantFunc func(t *testing.T, rm metricdata.ResourceMetrics) - }{ - { - name: "No environment variable set, and no Meter", - setEnv: false, - clientFunc: func(metric.MeterProvider) semconv.HTTPClient { - return semconv.NewHTTPClient(nil) - }, - wantFunc: func(t *testing.T, rm metricdata.ResourceMetrics) { - assert.Empty(t, rm.ScopeMetrics) - }, - }, - { - name: "No environment variable set, but with Meter", - setEnv: false, - clientFunc: func(mp metric.MeterProvider) semconv.HTTPClient { - return semconv.NewHTTPClient(mp.Meter("test")) - }, - wantFunc: func(t *testing.T, rm metricdata.ResourceMetrics) { - require.Empty(t, rm.ScopeMetrics) - }, - }, - { - name: "Set environment variable, but no Meter", - setEnv: true, - clientFunc: func(metric.MeterProvider) semconv.HTTPClient { - return semconv.NewHTTPClient(nil) - }, - wantFunc: func(t *testing.T, rm metricdata.ResourceMetrics) { - assert.Empty(t, rm.ScopeMetrics) - }, - }, - { - name: "Set environment variable and Meter", - setEnv: true, - clientFunc: func(mp metric.MeterProvider) semconv.HTTPClient { - return semconv.NewHTTPClient(mp.Meter("test")) - }, - wantFunc: func(t *testing.T, rm metricdata.ResourceMetrics) { - require.Len(t, rm.ScopeMetrics, 1) - require.Len(t, rm.ScopeMetrics[0].Metrics, 1) - metricdatatest.AssertEqual(t, expectedOldScopeMetric, 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)) - - client := tt.clientFunc(mp) - req, err := http.NewRequest("POST", "http://example.com", nil) - assert.NoError(t, err) - - client.RecordResponseSize(context.Background(), 100, client.MetricOptions(semconv.MetricAttributes{ - Req: req, - StatusCode: 301, - })) - - rm := metricdata.ResourceMetrics{} - require.NoError(t, reader.Collect(context.Background(), &rm)) - tt.wantFunc(t, rm) - }) - } -} - type customError struct{} func (customError) Error() string { diff --git a/instrumentation/net/http/otelhttp/internal/semconv/v1.20.0.go b/instrumentation/net/http/otelhttp/internal/semconv/v1.20.0.go deleted file mode 100644 index ba7fccf1efd..00000000000 --- a/instrumentation/net/http/otelhttp/internal/semconv/v1.20.0.go +++ /dev/null @@ -1,273 +0,0 @@ -// Code generated by gotmpl. DO NOT MODIFY. -// source: internal/shared/semconv/v120.0.go.tmpl - -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -package semconv // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconv" - -import ( - "errors" - "io" - "net/http" - "slices" - - "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconvutil" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/metric" - "go.opentelemetry.io/otel/metric/noop" - semconv "go.opentelemetry.io/otel/semconv/v1.20.0" -) - -type OldHTTPServer struct{} - -// RequestTraceAttrs returns trace attributes for an HTTP request received by a -// server. -// -// The server must be the primary server name if it is known. For example this -// would be the ServerName directive -// (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache -// server, and the server_name directive -// (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an -// nginx server. More generically, the primary server name would be the host -// header value that matches the default virtual host of an HTTP server. It -// should include the host identifier and if a port is used to route to the -// server that port identifier should be included as an appropriate port -// suffix. -// -// If the primary server name is not known, server should be an empty string. -// The req Host will be used to determine the server instead. -func (o OldHTTPServer) RequestTraceAttrs(server string, req *http.Request, attrs []attribute.KeyValue) []attribute.KeyValue { - return semconvutil.HTTPServerRequest(server, req, semconvutil.HTTPServerRequestOptions{}, attrs) -} - -func (o OldHTTPServer) NetworkTransportAttr(network string) attribute.KeyValue { - return semconvutil.NetTransport(network) -} - -// ResponseTraceAttrs returns trace attributes for telemetry from an HTTP response. -// -// If any of the fields in the ResponseTelemetry are not set the attribute will be omitted. -func (o OldHTTPServer) ResponseTraceAttrs(resp ResponseTelemetry, attributes []attribute.KeyValue) []attribute.KeyValue { - if resp.ReadBytes > 0 { - attributes = append(attributes, semconv.HTTPRequestContentLength(int(resp.ReadBytes))) - } - if resp.ReadError != nil && !errors.Is(resp.ReadError, io.EOF) { - // This is not in the semantic conventions, but is historically provided - attributes = append(attributes, attribute.String("http.read_error", resp.ReadError.Error())) - } - if resp.WriteBytes > 0 { - attributes = append(attributes, semconv.HTTPResponseContentLength(int(resp.WriteBytes))) - } - if resp.StatusCode > 0 { - attributes = append(attributes, semconv.HTTPStatusCode(resp.StatusCode)) - } - if resp.WriteError != nil && !errors.Is(resp.WriteError, io.EOF) { - // This is not in the semantic conventions, but is historically provided - attributes = append(attributes, attribute.String("http.write_error", resp.WriteError.Error())) - } - - return attributes -} - -// Route returns the attribute for the route. -func (o OldHTTPServer) Route(route string) attribute.KeyValue { - return semconv.HTTPRoute(route) -} - -// HTTPStatusCode returns the attribute for the HTTP status code. -// This is a temporary function needed by metrics. This will be removed when MetricsRequest is added. -func HTTPStatusCode(status int) attribute.KeyValue { - return semconv.HTTPStatusCode(status) -} - -// Server HTTP metrics. -const ( - serverRequestSize = "http.server.request.size" // Incoming request bytes total - serverResponseSize = "http.server.response.size" // Incoming response bytes total - serverDuration = "http.server.duration" // Incoming end to end duration, milliseconds -) - -func (h OldHTTPServer) createMeasures(meter metric.Meter) (metric.Int64Counter, metric.Int64Counter, metric.Float64Histogram) { - if meter == nil { - return noop.Int64Counter{}, noop.Int64Counter{}, noop.Float64Histogram{} - } - var err error - requestBytesCounter, err := meter.Int64Counter( - serverRequestSize, - metric.WithUnit("By"), - metric.WithDescription("Measures the size of HTTP request messages."), - ) - handleErr(err) - - responseBytesCounter, err := meter.Int64Counter( - serverResponseSize, - metric.WithUnit("By"), - metric.WithDescription("Measures the size of HTTP response messages."), - ) - handleErr(err) - - serverLatencyMeasure, err := meter.Float64Histogram( - serverDuration, - metric.WithUnit("ms"), - metric.WithDescription("Measures the duration of inbound HTTP requests."), - ) - handleErr(err) - - return requestBytesCounter, responseBytesCounter, serverLatencyMeasure -} - -func (o OldHTTPServer) MetricAttributes(server string, req *http.Request, statusCode int, additionalAttributes []attribute.KeyValue) []attribute.KeyValue { - n := 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 { - n++ - } - protoName, protoVersion := netProtocol(req.Proto) - if protoName != "" { - n++ - } - if protoVersion != "" { - n++ - } - - if statusCode > 0 { - n++ - } - - attributes := slices.Grow(additionalAttributes, n) - attributes = append(attributes, - semconv.HTTPMethod(standardizeHTTPMethod(req.Method)), - o.scheme(req.TLS != nil), - semconv.NetHostName(host)) - - if hostPort > 0 { - attributes = append(attributes, semconv.NetHostPort(hostPort)) - } - if protoName != "" { - attributes = append(attributes, semconv.NetProtocolName(protoName)) - } - if protoVersion != "" { - attributes = append(attributes, semconv.NetProtocolVersion(protoVersion)) - } - - if statusCode > 0 { - attributes = append(attributes, semconv.HTTPStatusCode(statusCode)) - } - return attributes -} - -func (o OldHTTPServer) scheme(https bool) attribute.KeyValue { // nolint:revive - if https { - return semconv.HTTPSchemeHTTPS - } - return semconv.HTTPSchemeHTTP -} - -type OldHTTPClient struct{} - -func (o OldHTTPClient) RequestTraceAttrs(req *http.Request, attrs []attribute.KeyValue) []attribute.KeyValue { - return semconvutil.HTTPClientRequest(req, attrs) -} - -func (o OldHTTPClient) ResponseTraceAttrs(resp *http.Response, attrs []attribute.KeyValue) []attribute.KeyValue { - return semconvutil.HTTPClientResponse(resp, attrs) -} - -func (o OldHTTPClient) MetricAttributes(req *http.Request, statusCode int, additionalAttributes []attribute.KeyValue) []attribute.KeyValue { - /* The following semantic conventions are returned if present: - http.method string - http.status_code int - net.peer.name string - net.peer.port int - */ - - n := 2 // method, peer name. - var h string - if req.URL != nil { - h = req.URL.Host - } - var requestHost string - var requestPort int - for _, hostport := range []string{h, req.Header.Get("Host")} { - requestHost, requestPort = SplitHostPort(hostport) - if requestHost != "" || requestPort > 0 { - break - } - } - - port := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", requestPort) - if port > 0 { - n++ - } - - if statusCode > 0 { - n++ - } - - attributes := slices.Grow(additionalAttributes, n) - attributes = append(attributes, - semconv.HTTPMethod(standardizeHTTPMethod(req.Method)), - semconv.NetPeerName(requestHost), - ) - - if port > 0 { - attributes = append(attributes, semconv.NetPeerPort(port)) - } - - if statusCode > 0 { - attributes = append(attributes, semconv.HTTPStatusCode(statusCode)) - } - return attributes -} - -// Client HTTP metrics. -const ( - clientRequestSize = "http.client.request.size" // Incoming request bytes total - clientResponseSize = "http.client.response.size" // Incoming response bytes total - clientDuration = "http.client.duration" // Incoming end to end duration, milliseconds -) - -func (o OldHTTPClient) createMeasures(meter metric.Meter) (metric.Int64Counter, metric.Int64Counter, metric.Float64Histogram) { - if meter == nil { - return noop.Int64Counter{}, noop.Int64Counter{}, noop.Float64Histogram{} - } - requestBytesCounter, err := meter.Int64Counter( - clientRequestSize, - metric.WithUnit("By"), - metric.WithDescription("Measures the size of HTTP request messages."), - ) - handleErr(err) - - responseBytesCounter, err := meter.Int64Counter( - clientResponseSize, - metric.WithUnit("By"), - metric.WithDescription("Measures the size of HTTP response messages."), - ) - handleErr(err) - - latencyMeasure, err := meter.Float64Histogram( - clientDuration, - metric.WithUnit("ms"), - metric.WithDescription("Measures the duration of outbound HTTP requests."), - ) - handleErr(err) - - return requestBytesCounter, responseBytesCounter, latencyMeasure -} - -// TraceAttributes returns attributes for httptrace. -func (c OldHTTPClient) TraceAttributes(host string, attrs []attribute.KeyValue) []attribute.KeyValue { - return append(attrs, semconv.NetHostName(host)) -} diff --git a/instrumentation/net/http/otelhttp/internal/semconvutil/gen.go b/instrumentation/net/http/otelhttp/internal/semconvutil/gen.go deleted file mode 100644 index 7aa5f99e815..00000000000 --- a/instrumentation/net/http/otelhttp/internal/semconvutil/gen.go +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -package semconvutil // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconvutil" - -// Generate semconvutil package: -//go:generate gotmpl --body=../../../../../../internal/shared/semconvutil/httpconv_test.go.tmpl "--data={}" --out=httpconv_test.go -//go:generate gotmpl --body=../../../../../../internal/shared/semconvutil/httpconv.go.tmpl "--data={}" --out=httpconv.go -//go:generate gotmpl --body=../../../../../../internal/shared/semconvutil/netconv_test.go.tmpl "--data={}" --out=netconv_test.go -//go:generate gotmpl --body=../../../../../../internal/shared/semconvutil/netconv.go.tmpl "--data={}" --out=netconv.go diff --git a/instrumentation/net/http/otelhttp/internal/semconvutil/httpconv.go b/instrumentation/net/http/otelhttp/internal/semconvutil/httpconv.go deleted file mode 100644 index b9973547931..00000000000 --- a/instrumentation/net/http/otelhttp/internal/semconvutil/httpconv.go +++ /dev/null @@ -1,594 +0,0 @@ -// Code generated by gotmpl. DO NOT MODIFY. -// source: internal/shared/semconvutil/httpconv.go.tmpl - -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -// Package semconvutil provides OpenTelemetry semantic convention utilities. -package semconvutil // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconvutil" - -import ( - "fmt" - "net/http" - "slices" - "strings" - - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/codes" - semconv "go.opentelemetry.io/otel/semconv/v1.20.0" -) - -type HTTPServerRequestOptions struct { - // If set, this is used as value for the "http.client_ip" attribute. - HTTPClientIP string -} - -// HTTPClientResponse returns trace attributes for an HTTP response received by a -// client from a server. It will return the following attributes if the related -// values are defined in resp: "http.status.code", -// "http.response_content_length". -// -// This does not add all OpenTelemetry required attributes for an HTTP event, -// it assumes ClientRequest was used to create the span with a complete set of -// attributes. If a complete set of attributes can be generated using the -// request contained in resp. For example: -// -// HTTPClientResponse(resp, ClientRequest(resp.Request))) -func HTTPClientResponse(resp *http.Response, attrs []attribute.KeyValue) []attribute.KeyValue { - return hc.ClientResponse(resp, attrs) -} - -// HTTPClientRequest returns trace attributes for an HTTP request made by a client. -// The following attributes are always returned: "http.url", "http.method", -// "net.peer.name". The following attributes are returned if the related values -// are defined in req: "net.peer.port", "user_agent.original", -// "http.request_content_length". -func HTTPClientRequest(req *http.Request, attrs []attribute.KeyValue) []attribute.KeyValue { - return hc.ClientRequest(req, attrs) -} - -// HTTPClientRequestMetrics returns metric attributes for an HTTP request made by a client. -// The following attributes are always returned: "http.method", "net.peer.name". -// The following attributes are returned if the -// related values are defined in req: "net.peer.port". -func HTTPClientRequestMetrics(req *http.Request) []attribute.KeyValue { - return hc.ClientRequestMetrics(req) -} - -// HTTPClientStatus returns a span status code and message for an HTTP status code -// value received by a client. -func HTTPClientStatus(code int) (codes.Code, string) { - return hc.ClientStatus(code) -} - -// HTTPServerRequest returns trace attributes for an HTTP request received by a -// server. -// -// The server must be the primary server name if it is known. For example this -// would be the ServerName directive -// (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache -// server, and the server_name directive -// (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an -// nginx server. More generically, the primary server name would be the host -// header value that matches the default virtual host of an HTTP server. It -// should include the host identifier and if a port is used to route to the -// server that port identifier should be included as an appropriate port -// suffix. -// -// If the primary server name is not known, server should be an empty string. -// The req Host will be used to determine the server instead. -// -// The following attributes are always returned: "http.method", "http.scheme", -// "http.target", "net.host.name". The following attributes are returned if -// they related values are defined in req: "net.host.port", "net.sock.peer.addr", -// "net.sock.peer.port", "user_agent.original", "http.client_ip". -func HTTPServerRequest(server string, req *http.Request, opts HTTPServerRequestOptions, attrs []attribute.KeyValue) []attribute.KeyValue { - return hc.ServerRequest(server, req, opts, attrs) -} - -// HTTPServerRequestMetrics returns metric attributes for an HTTP request received by a -// server. -// -// The server must be the primary server name if it is known. For example this -// would be the ServerName directive -// (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache -// server, and the server_name directive -// (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an -// nginx server. More generically, the primary server name would be the host -// header value that matches the default virtual host of an HTTP server. It -// should include the host identifier and if a port is used to route to the -// server that port identifier should be included as an appropriate port -// suffix. -// -// If the primary server name is not known, server should be an empty string. -// The req Host will be used to determine the server instead. -// -// The following attributes are always returned: "http.method", "http.scheme", -// "net.host.name". The following attributes are returned if they related -// values are defined in req: "net.host.port". -func HTTPServerRequestMetrics(server string, req *http.Request) []attribute.KeyValue { - return hc.ServerRequestMetrics(server, req) -} - -// HTTPServerStatus returns a span status code and message for an HTTP status code -// value returned by a server. Status codes in the 400-499 range are not -// returned as errors. -func HTTPServerStatus(code int) (codes.Code, string) { - return hc.ServerStatus(code) -} - -// httpConv are the HTTP semantic convention attributes defined for a version -// of the OpenTelemetry specification. -type httpConv struct { - NetConv *netConv - - HTTPClientIPKey attribute.Key - HTTPMethodKey attribute.Key - HTTPRequestContentLengthKey attribute.Key - HTTPResponseContentLengthKey attribute.Key - HTTPRouteKey attribute.Key - HTTPSchemeHTTP attribute.KeyValue - HTTPSchemeHTTPS attribute.KeyValue - HTTPStatusCodeKey attribute.Key - HTTPTargetKey attribute.Key - HTTPURLKey attribute.Key - UserAgentOriginalKey attribute.Key -} - -var hc = &httpConv{ - NetConv: nc, - - HTTPClientIPKey: semconv.HTTPClientIPKey, - HTTPMethodKey: semconv.HTTPMethodKey, - HTTPRequestContentLengthKey: semconv.HTTPRequestContentLengthKey, - HTTPResponseContentLengthKey: semconv.HTTPResponseContentLengthKey, - HTTPRouteKey: semconv.HTTPRouteKey, - HTTPSchemeHTTP: semconv.HTTPSchemeHTTP, - HTTPSchemeHTTPS: semconv.HTTPSchemeHTTPS, - HTTPStatusCodeKey: semconv.HTTPStatusCodeKey, - HTTPTargetKey: semconv.HTTPTargetKey, - HTTPURLKey: semconv.HTTPURLKey, - UserAgentOriginalKey: semconv.UserAgentOriginalKey, -} - -// ClientResponse returns attributes for an HTTP response received by a client -// from a server. The following attributes are returned if the related values -// are defined in resp: "http.status.code", "http.response_content_length". -// -// This does not add all OpenTelemetry required attributes for an HTTP event, -// it assumes ClientRequest was used to create the span with a complete set of -// attributes. If a complete set of attributes can be generated using the -// request contained in resp. For example: -// -// ClientResponse(resp, ClientRequest(resp.Request)) -func (c *httpConv) ClientResponse(resp *http.Response, attrs []attribute.KeyValue) []attribute.KeyValue { - /* The following semantic conventions are returned if present: - http.status_code int - http.response_content_length int - */ - var n int - if resp.StatusCode > 0 { - n++ - } - if resp.ContentLength > 0 { - n++ - } - if n == 0 { - return attrs - } - - attrs = slices.Grow(attrs, n) - if resp.StatusCode > 0 { - attrs = append(attrs, c.HTTPStatusCodeKey.Int(resp.StatusCode)) - } - if resp.ContentLength > 0 { - attrs = append(attrs, c.HTTPResponseContentLengthKey.Int(int(resp.ContentLength))) - } - return attrs -} - -// ClientRequest returns attributes for an HTTP request made by a client. The -// following attributes are always returned: "http.url", "http.method", -// "net.peer.name". The following attributes are returned if the related values -// are defined in req: "net.peer.port", "user_agent.original", -// "http.request_content_length", "user_agent.original". -func (c *httpConv) ClientRequest(req *http.Request, attrs []attribute.KeyValue) []attribute.KeyValue { - /* The following semantic conventions are returned if present: - http.method string - user_agent.original string - http.url string - net.peer.name string - net.peer.port int - http.request_content_length int - */ - - /* The following semantic conventions are not returned: - http.status_code This requires the response. See ClientResponse. - http.response_content_length This requires the response. See ClientResponse. - net.sock.family This requires the socket used. - net.sock.peer.addr This requires the socket used. - net.sock.peer.name This requires the socket used. - net.sock.peer.port This requires the socket used. - http.resend_count This is something outside of a single request. - net.protocol.name The value is the Request is ignored, and the go client will always use "http". - net.protocol.version The value in the Request is ignored, and the go client will always use 1.1 or 2.0. - */ - n := 3 // URL, peer name, proto, and method. - var h string - if req.URL != nil { - h = req.URL.Host - } - peer, p := firstHostPort(h, req.Header.Get("Host")) - port := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", p) - if port > 0 { - n++ - } - useragent := req.UserAgent() - if useragent != "" { - n++ - } - if req.ContentLength > 0 { - n++ - } - - attrs = slices.Grow(attrs, n) - attrs = append(attrs, c.method(req.Method)) - - var u string - if req.URL != nil { - // Remove any username/password info that may be in the URL. - userinfo := req.URL.User - req.URL.User = nil - u = req.URL.String() - // Restore any username/password info that was removed. - req.URL.User = userinfo - } - attrs = append(attrs, c.HTTPURLKey.String(u)) - - attrs = append(attrs, c.NetConv.PeerName(peer)) - if port > 0 { - attrs = append(attrs, c.NetConv.PeerPort(port)) - } - - if useragent != "" { - attrs = append(attrs, c.UserAgentOriginalKey.String(useragent)) - } - - if l := req.ContentLength; l > 0 { - attrs = append(attrs, c.HTTPRequestContentLengthKey.Int64(l)) - } - - return attrs -} - -// ClientRequestMetrics returns metric attributes for an HTTP request made by a client. The -// following attributes are always returned: "http.method", "net.peer.name". -// The following attributes are returned if the related values -// are defined in req: "net.peer.port". -func (c *httpConv) ClientRequestMetrics(req *http.Request) []attribute.KeyValue { - /* The following semantic conventions are returned if present: - http.method string - net.peer.name string - net.peer.port int - */ - - n := 2 // method, peer name. - var h string - if req.URL != nil { - h = req.URL.Host - } - peer, p := firstHostPort(h, req.Header.Get("Host")) - port := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", p) - if port > 0 { - n++ - } - - attrs := make([]attribute.KeyValue, 0, n) - attrs = append(attrs, c.method(req.Method), c.NetConv.PeerName(peer)) - - if port > 0 { - attrs = append(attrs, c.NetConv.PeerPort(port)) - } - - return attrs -} - -// ServerRequest returns attributes for an HTTP request received by a server. -// -// The server must be the primary server name if it is known. For example this -// would be the ServerName directive -// (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache -// server, and the server_name directive -// (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an -// nginx server. More generically, the primary server name would be the host -// header value that matches the default virtual host of an HTTP server. It -// should include the host identifier and if a port is used to route to the -// server that port identifier should be included as an appropriate port -// suffix. -// -// If the primary server name is not known, server should be an empty string. -// The req Host will be used to determine the server instead. -// -// The following attributes are always returned: "http.method", "http.scheme", -// "http.target", "net.host.name". The following attributes are returned if they -// related values are defined in req: "net.host.port", "net.sock.peer.addr", -// "net.sock.peer.port", "user_agent.original", "http.client_ip", -// "net.protocol.name", "net.protocol.version". -func (c *httpConv) ServerRequest(server string, req *http.Request, opts HTTPServerRequestOptions, attrs []attribute.KeyValue) []attribute.KeyValue { - /* The following semantic conventions are returned if present: - http.method string - http.scheme string - net.host.name string - net.host.port int - net.sock.peer.addr string - net.sock.peer.port int - user_agent.original string - http.client_ip string - net.protocol.name string Note: not set if the value is "http". - net.protocol.version string - http.target string Note: doesn't include the query parameter. - */ - - /* The following semantic conventions are not returned: - http.status_code This requires the response. - http.request_content_length This requires the len() of body, which can mutate it. - http.response_content_length This requires the response. - http.route This is not available. - net.sock.peer.name This would require a DNS lookup. - net.sock.host.addr The request doesn't have access to the underlying socket. - net.sock.host.port The request doesn't have access to the underlying socket. - - */ - n := 4 // Method, scheme, proto, and host name. - 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 { - n++ - } - peer, peerPort := splitHostPort(req.RemoteAddr) - if peer != "" { - n++ - if peerPort > 0 { - n++ - } - } - useragent := req.UserAgent() - if useragent != "" { - n++ - } - - // For client IP, use, in order: - // 1. The value passed in the options - // 2. The value in the X-Forwarded-For header - // 3. The peer address - clientIP := opts.HTTPClientIP - if clientIP == "" { - clientIP = serverClientIP(req.Header.Get("X-Forwarded-For")) - if clientIP == "" { - clientIP = peer - } - } - if clientIP != "" { - n++ - } - - var target string - if req.URL != nil { - target = req.URL.Path - if target != "" { - n++ - } - } - protoName, protoVersion := netProtocol(req.Proto) - if protoName != "" && protoName != "http" { - n++ - } - if protoVersion != "" { - n++ - } - - attrs = slices.Grow(attrs, n) - - attrs = append(attrs, c.method(req.Method)) - attrs = append(attrs, c.scheme(req.TLS != nil)) - attrs = append(attrs, c.NetConv.HostName(host)) - - if hostPort > 0 { - attrs = append(attrs, c.NetConv.HostPort(hostPort)) - } - - if peer != "" { - // The Go HTTP server sets RemoteAddr to "IP:port", this will not be a - // file-path that would be interpreted with a sock family. - attrs = append(attrs, c.NetConv.SockPeerAddr(peer)) - if peerPort > 0 { - attrs = append(attrs, c.NetConv.SockPeerPort(peerPort)) - } - } - - if useragent != "" { - attrs = append(attrs, c.UserAgentOriginalKey.String(useragent)) - } - - if clientIP != "" { - attrs = append(attrs, c.HTTPClientIPKey.String(clientIP)) - } - - if target != "" { - attrs = append(attrs, c.HTTPTargetKey.String(target)) - } - - if protoName != "" && protoName != "http" { - attrs = append(attrs, c.NetConv.NetProtocolName.String(protoName)) - } - if protoVersion != "" { - attrs = append(attrs, c.NetConv.NetProtocolVersion.String(protoVersion)) - } - - return attrs -} - -// ServerRequestMetrics returns metric attributes for an HTTP request received -// by a server. -// -// The server must be the primary server name if it is known. For example this -// would be the ServerName directive -// (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache -// server, and the server_name directive -// (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an -// nginx server. More generically, the primary server name would be the host -// header value that matches the default virtual host of an HTTP server. It -// should include the host identifier and if a port is used to route to the -// server that port identifier should be included as an appropriate port -// suffix. -// -// If the primary server name is not known, server should be an empty string. -// The req Host will be used to determine the server instead. -// -// The following attributes are always returned: "http.method", "http.scheme", -// "net.host.name". The following attributes are returned if they related -// values are defined in req: "net.host.port". -func (c *httpConv) ServerRequestMetrics(server string, req *http.Request) []attribute.KeyValue { - /* The following semantic conventions are returned if present: - http.scheme string - http.route string - http.method string - http.status_code int - net.host.name string - net.host.port int - net.protocol.name string Note: not set if the value is "http". - net.protocol.version string - */ - - n := 3 // Method, scheme, and host name. - 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 { - n++ - } - protoName, protoVersion := netProtocol(req.Proto) - if protoName != "" { - n++ - } - if protoVersion != "" { - n++ - } - - attrs := make([]attribute.KeyValue, 0, n) - - attrs = append(attrs, c.methodMetric(req.Method)) - attrs = append(attrs, c.scheme(req.TLS != nil)) - attrs = append(attrs, c.NetConv.HostName(host)) - - if hostPort > 0 { - attrs = append(attrs, c.NetConv.HostPort(hostPort)) - } - if protoName != "" { - attrs = append(attrs, c.NetConv.NetProtocolName.String(protoName)) - } - if protoVersion != "" { - attrs = append(attrs, c.NetConv.NetProtocolVersion.String(protoVersion)) - } - - return attrs -} - -func (c *httpConv) method(method string) attribute.KeyValue { - if method == "" { - return c.HTTPMethodKey.String(http.MethodGet) - } - return c.HTTPMethodKey.String(method) -} - -func (c *httpConv) methodMetric(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 c.HTTPMethodKey.String(method) -} - -func (c *httpConv) scheme(https bool) attribute.KeyValue { // nolint:revive - if https { - return c.HTTPSchemeHTTPS - } - return c.HTTPSchemeHTTP -} - -func serverClientIP(xForwardedFor string) string { - if idx := strings.Index(xForwardedFor, ","); idx >= 0 { - xForwardedFor = xForwardedFor[:idx] - } - return xForwardedFor -} - -func requiredHTTPPort(https bool, port int) int { // nolint:revive - if https { - if port > 0 && port != 443 { - return port - } - } else { - if port > 0 && port != 80 { - return port - } - } - return -1 -} - -// Return the request host and port from the first non-empty source. -func firstHostPort(source ...string) (host string, port int) { - for _, hostport := range source { - host, port = splitHostPort(hostport) - if host != "" || port > 0 { - break - } - } - return -} - -// ClientStatus returns a span status code and message for an HTTP status code -// value received by a client. -func (c *httpConv) ClientStatus(code int) (codes.Code, string) { - if code < 100 || code >= 600 { - return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code) - } - if code >= 400 { - return codes.Error, "" - } - return codes.Unset, "" -} - -// ServerStatus returns a span status code and message for an HTTP status code -// value returned by a server. Status codes in the 400-499 range are not -// returned as errors. -func (c *httpConv) ServerStatus(code int) (codes.Code, string) { - if code < 100 || code >= 600 { - return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code) - } - if code >= 500 { - return codes.Error, "" - } - return codes.Unset, "" -} diff --git a/instrumentation/net/http/otelhttp/internal/semconvutil/httpconv_test.go b/instrumentation/net/http/otelhttp/internal/semconvutil/httpconv_test.go deleted file mode 100644 index ce3a4eb3319..00000000000 --- a/instrumentation/net/http/otelhttp/internal/semconvutil/httpconv_test.go +++ /dev/null @@ -1,561 +0,0 @@ -// Code generated by gotmpl. DO NOT MODIFY. -// source: internal/shared/semconvutil/httpconv_test.go.tmpl - -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -package semconvutil - -import ( - "net/http" - "net/http/httptest" - "net/url" - "strconv" - "testing" - "time" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/codes" -) - -func TestHTTPClientResponse(t *testing.T) { - const stat, n = 201, 397 - resp := &http.Response{ - StatusCode: stat, - ContentLength: n, - } - got := HTTPClientResponse(resp, nil) - assert.Equal(t, 2, cap(got), "slice capacity") - assert.ElementsMatch(t, []attribute.KeyValue{ - attribute.Key("http.status_code").Int(stat), - attribute.Key("http.response_content_length").Int(n), - }, got) -} - -func TestHTTPSClientRequest(t *testing.T) { - req := &http.Request{ - Method: http.MethodGet, - URL: &url.URL{ - Scheme: "https", - Host: "127.0.0.1:443", - Path: "/resource", - }, - Proto: "HTTP/1.0", - ProtoMajor: 1, - ProtoMinor: 0, - } - - assert.ElementsMatch( - t, - []attribute.KeyValue{ - attribute.String("http.method", "GET"), - attribute.String("http.url", "https://127.0.0.1:443/resource"), - attribute.String("net.peer.name", "127.0.0.1"), - }, - HTTPClientRequest(req, nil), - ) -} - -func TestHTTPSClientRequestMetrics(t *testing.T) { - req := &http.Request{ - Method: http.MethodGet, - URL: &url.URL{ - Scheme: "https", - Host: "127.0.0.1:443", - Path: "/resource", - }, - Proto: "HTTP/1.0", - ProtoMajor: 1, - ProtoMinor: 0, - } - - assert.ElementsMatch( - t, - []attribute.KeyValue{ - attribute.String("http.method", "GET"), - attribute.String("net.peer.name", "127.0.0.1"), - }, - HTTPClientRequestMetrics(req), - ) -} - -func TestHTTPClientRequest(t *testing.T) { - const ( - user = "alice" - n = 128 - agent = "Go-http-client/1.1" - ) - req := &http.Request{ - Method: http.MethodGet, - URL: &url.URL{ - Scheme: "http", - Host: "127.0.0.1:8080", - Path: "/resource", - }, - Proto: "HTTP/1.0", - ProtoMajor: 1, - ProtoMinor: 0, - Header: http.Header{ - "User-Agent": []string{agent}, - }, - ContentLength: n, - } - req.SetBasicAuth(user, "pswrd") - - assert.ElementsMatch( - t, - []attribute.KeyValue{ - attribute.String("http.method", "GET"), - attribute.String("http.url", "http://127.0.0.1:8080/resource"), - attribute.String("net.peer.name", "127.0.0.1"), - attribute.Int("net.peer.port", 8080), - attribute.String("user_agent.original", agent), - attribute.Int("http.request_content_length", n), - }, - HTTPClientRequest(req, nil), - ) -} - -func TestHTTPClientRequestMetrics(t *testing.T) { - const ( - user = "alice" - n = 128 - agent = "Go-http-client/1.1" - ) - req := &http.Request{ - Method: http.MethodGet, - URL: &url.URL{ - Scheme: "http", - Host: "127.0.0.1:8080", - Path: "/resource", - }, - Proto: "HTTP/1.0", - ProtoMajor: 1, - ProtoMinor: 0, - Header: http.Header{ - "User-Agent": []string{agent}, - }, - ContentLength: n, - } - req.SetBasicAuth(user, "pswrd") - - assert.ElementsMatch( - t, - []attribute.KeyValue{ - attribute.String("http.method", "GET"), - attribute.String("net.peer.name", "127.0.0.1"), - attribute.Int("net.peer.port", 8080), - }, - HTTPClientRequestMetrics(req), - ) -} - -func TestHTTPClientRequestRequired(t *testing.T) { - req := new(http.Request) - var got []attribute.KeyValue - assert.NotPanics(t, func() { got = HTTPClientRequest(req, nil) }) - want := []attribute.KeyValue{ - attribute.String("http.method", "GET"), - attribute.String("http.url", ""), - attribute.String("net.peer.name", ""), - } - assert.Equal(t, want, got) -} - -func TestHTTPServerRequest(t *testing.T) { - for _, tt := range []struct { - name string - requestModifierFn func(r *http.Request) - httpServerRequestOpts HTTPServerRequestOptions - - wantClientIP string - }{ - { - name: "with a client IP from the network", - wantClientIP: "1.2.3.4", - }, - { - name: "with a client IP from x-forwarded-for header", - requestModifierFn: func(r *http.Request) { - r.Header.Add("X-Forwarded-For", "5.6.7.8") - }, - wantClientIP: "5.6.7.8", - }, - { - name: "with a client IP in options", - requestModifierFn: func(r *http.Request) { - r.Header.Add("X-Forwarded-For", "5.6.7.8") - }, - httpServerRequestOpts: HTTPServerRequestOptions{ - HTTPClientIP: "9.8.7.6", - }, - wantClientIP: "9.8.7.6", - }, - } { - t.Run(tt.name, func(t *testing.T) { - reqCh := make(chan *http.Request, 1) - handler := func(w http.ResponseWriter, r *http.Request) { - r.RemoteAddr = "1.2.3.4:5678" - reqCh <- r - w.WriteHeader(http.StatusOK) - } - - srv := httptest.NewServer(http.HandlerFunc(handler)) - defer srv.Close() - - srvURL, err := url.Parse(srv.URL) - require.NoError(t, err) - srvPort, err := strconv.ParseInt(srvURL.Port(), 10, 32) - require.NoError(t, err) - - req, err := http.NewRequest(http.MethodGet, srv.URL, nil) - require.NoError(t, err) - - if tt.requestModifierFn != nil { - tt.requestModifierFn(req) - } - - resp, err := srv.Client().Do(req) - require.NoError(t, err) - require.NoError(t, resp.Body.Close()) - - var got *http.Request - select { - case got = <-reqCh: - // All good - case <-time.After(5 * time.Second): - t.Fatal("Did not receive a signal in 5s") - } - - peer, peerPort := splitHostPort(got.RemoteAddr) - - const user = "alice" - got.SetBasicAuth(user, "pswrd") - - assert.ElementsMatch(t, - []attribute.KeyValue{ - attribute.String("http.method", "GET"), - attribute.String("http.scheme", "http"), - attribute.String("net.host.name", srvURL.Hostname()), - attribute.Int("net.host.port", int(srvPort)), - attribute.String("net.sock.peer.addr", peer), - attribute.Int("net.sock.peer.port", peerPort), - attribute.String("user_agent.original", "Go-http-client/1.1"), - attribute.String("http.client_ip", tt.wantClientIP), - attribute.String("net.protocol.version", "1.1"), - attribute.String("http.target", "/"), - }, - HTTPServerRequest("", got, tt.httpServerRequestOpts, nil)) - }) - } -} - -func TestHTTPServerRequestMetrics(t *testing.T) { - got := make(chan *http.Request, 1) - handler := func(w http.ResponseWriter, r *http.Request) { - got <- r - w.WriteHeader(http.StatusOK) - } - - srv := httptest.NewServer(http.HandlerFunc(handler)) - defer srv.Close() - - srvURL, err := url.Parse(srv.URL) - require.NoError(t, err) - srvPort, err := strconv.ParseInt(srvURL.Port(), 10, 32) - require.NoError(t, err) - - resp, err := srv.Client().Get(srv.URL) - require.NoError(t, err) - require.NoError(t, resp.Body.Close()) - - var req *http.Request - select { - case req = <-got: - // All good - case <-time.After(5 * time.Second): - t.Fatal("did not receive a signal in 5s") - } - - assert.ElementsMatch(t, - []attribute.KeyValue{ - attribute.String("http.method", "GET"), - attribute.String("http.scheme", "http"), - attribute.String("net.host.name", srvURL.Hostname()), - attribute.Int("net.host.port", int(srvPort)), - attribute.String("net.protocol.name", "http"), - attribute.String("net.protocol.version", "1.1"), - }, - HTTPServerRequestMetrics("", req)) -} - -func TestHTTPServerName(t *testing.T) { - req := new(http.Request) - var got []attribute.KeyValue - const ( - host = "test.semconv.server" - port = 8080 - ) - portStr := strconv.Itoa(port) - server := host + ":" + portStr - assert.NotPanics(t, func() { got = HTTPServerRequest(server, req, HTTPServerRequestOptions{}, nil) }) - assert.Contains(t, got, attribute.String("net.host.name", host)) - assert.Contains(t, got, attribute.Int("net.host.port", port)) - - req = &http.Request{Host: "alt.host.name:" + portStr} - // The server parameter does not include a port, ServerRequest should use - // the port in the request Host field. - assert.NotPanics(t, func() { got = HTTPServerRequest(host, req, HTTPServerRequestOptions{}, nil) }) - assert.Contains(t, got, attribute.String("net.host.name", host)) - assert.Contains(t, got, attribute.Int("net.host.port", port)) -} - -func TestHTTPServerRequestFailsGracefully(t *testing.T) { - req := new(http.Request) - var got []attribute.KeyValue - assert.NotPanics(t, func() { got = HTTPServerRequest("", req, HTTPServerRequestOptions{}, nil) }) - want := []attribute.KeyValue{ - attribute.String("http.method", "GET"), - attribute.String("http.scheme", "http"), - attribute.String("net.host.name", ""), - } - assert.ElementsMatch(t, want, got) -} - -func TestHTTPMethod(t *testing.T) { - assert.Equal(t, attribute.String("http.method", "POST"), hc.method("POST")) - assert.Equal(t, attribute.String("http.method", "GET"), hc.method("")) - assert.Equal(t, attribute.String("http.method", "garbage"), hc.method("garbage")) -} - -func TestHTTPScheme(t *testing.T) { - assert.Equal(t, attribute.String("http.scheme", "http"), hc.scheme(false)) - assert.Equal(t, attribute.String("http.scheme", "https"), hc.scheme(true)) -} - -func TestHTTPServerClientIP(t *testing.T) { - tests := []struct { - xForwardedFor string - want string - }{ - {"", ""}, - {"127.0.0.1", "127.0.0.1"}, - {"127.0.0.1,127.0.0.5", "127.0.0.1"}, - } - for _, test := range tests { - got := serverClientIP(test.xForwardedFor) - assert.Equal(t, test.want, got, test.xForwardedFor) - } -} - -func TestRequiredHTTPPort(t *testing.T) { - tests := []struct { - https bool - port int - want int - }{ - {true, 443, -1}, - {true, 80, 80}, - {true, 8081, 8081}, - {false, 443, 443}, - {false, 80, -1}, - {false, 8080, 8080}, - } - for _, test := range tests { - got := requiredHTTPPort(test.https, test.port) - assert.Equalf(t, test.want, got, "HTTPS: %t, Port: %d", test.https, test.port) - } -} - -func TestFirstHostPort(t *testing.T) { - host, port := "127.0.0.1", 8080 - hostport := "127.0.0.1:8080" - sources := [][]string{ - {hostport}, - {"", hostport}, - {"", "", hostport}, - {"", "", hostport, ""}, - {"", "", hostport, "127.0.0.3:80"}, - } - - for _, src := range sources { - h, p := firstHostPort(src...) - assert.Equal(t, host, h, "%+v", src) - assert.Equal(t, port, p, "%+v", src) - } -} - -func TestHTTPClientStatus(t *testing.T) { - tests := []struct { - code int - stat codes.Code - msg bool - }{ - {0, codes.Error, true}, - {http.StatusContinue, codes.Unset, false}, - {http.StatusSwitchingProtocols, codes.Unset, false}, - {http.StatusProcessing, codes.Unset, false}, - {http.StatusEarlyHints, codes.Unset, false}, - {http.StatusOK, codes.Unset, false}, - {http.StatusCreated, codes.Unset, false}, - {http.StatusAccepted, codes.Unset, false}, - {http.StatusNonAuthoritativeInfo, codes.Unset, false}, - {http.StatusNoContent, codes.Unset, false}, - {http.StatusResetContent, codes.Unset, false}, - {http.StatusPartialContent, codes.Unset, false}, - {http.StatusMultiStatus, codes.Unset, false}, - {http.StatusAlreadyReported, codes.Unset, false}, - {http.StatusIMUsed, codes.Unset, false}, - {http.StatusMultipleChoices, codes.Unset, false}, - {http.StatusMovedPermanently, codes.Unset, false}, - {http.StatusFound, codes.Unset, false}, - {http.StatusSeeOther, codes.Unset, false}, - {http.StatusNotModified, codes.Unset, false}, - {http.StatusUseProxy, codes.Unset, false}, - {306, codes.Unset, false}, - {http.StatusTemporaryRedirect, codes.Unset, false}, - {http.StatusPermanentRedirect, codes.Unset, false}, - {http.StatusBadRequest, codes.Error, false}, - {http.StatusUnauthorized, codes.Error, false}, - {http.StatusPaymentRequired, codes.Error, false}, - {http.StatusForbidden, codes.Error, false}, - {http.StatusNotFound, codes.Error, false}, - {http.StatusMethodNotAllowed, codes.Error, false}, - {http.StatusNotAcceptable, codes.Error, false}, - {http.StatusProxyAuthRequired, codes.Error, false}, - {http.StatusRequestTimeout, codes.Error, false}, - {http.StatusConflict, codes.Error, false}, - {http.StatusGone, codes.Error, false}, - {http.StatusLengthRequired, codes.Error, false}, - {http.StatusPreconditionFailed, codes.Error, false}, - {http.StatusRequestEntityTooLarge, codes.Error, false}, - {http.StatusRequestURITooLong, codes.Error, false}, - {http.StatusUnsupportedMediaType, codes.Error, false}, - {http.StatusRequestedRangeNotSatisfiable, codes.Error, false}, - {http.StatusExpectationFailed, codes.Error, false}, - {http.StatusTeapot, codes.Error, false}, - {http.StatusMisdirectedRequest, codes.Error, false}, - {http.StatusUnprocessableEntity, codes.Error, false}, - {http.StatusLocked, codes.Error, false}, - {http.StatusFailedDependency, codes.Error, false}, - {http.StatusTooEarly, codes.Error, false}, - {http.StatusUpgradeRequired, codes.Error, false}, - {http.StatusPreconditionRequired, codes.Error, false}, - {http.StatusTooManyRequests, codes.Error, false}, - {http.StatusRequestHeaderFieldsTooLarge, codes.Error, false}, - {http.StatusUnavailableForLegalReasons, codes.Error, false}, - {499, codes.Error, false}, - {http.StatusInternalServerError, codes.Error, false}, - {http.StatusNotImplemented, codes.Error, false}, - {http.StatusBadGateway, codes.Error, false}, - {http.StatusServiceUnavailable, codes.Error, false}, - {http.StatusGatewayTimeout, codes.Error, false}, - {http.StatusHTTPVersionNotSupported, codes.Error, false}, - {http.StatusVariantAlsoNegotiates, codes.Error, false}, - {http.StatusInsufficientStorage, codes.Error, false}, - {http.StatusLoopDetected, codes.Error, false}, - {http.StatusNotExtended, codes.Error, false}, - {http.StatusNetworkAuthenticationRequired, codes.Error, false}, - {600, codes.Error, true}, - } - - for _, test := range tests { - t.Run(strconv.Itoa(test.code), func(t *testing.T) { - c, msg := HTTPClientStatus(test.code) - assert.Equal(t, test.stat, c) - if test.msg && msg == "" { - t.Errorf("expected non-empty message for %d", test.code) - } else if !test.msg && msg != "" { - t.Errorf("expected empty message for %d, got: %s", test.code, msg) - } - }) - } -} - -func TestHTTPServerStatus(t *testing.T) { - tests := []struct { - code int - stat codes.Code - msg bool - }{ - {0, codes.Error, true}, - {http.StatusContinue, codes.Unset, false}, - {http.StatusSwitchingProtocols, codes.Unset, false}, - {http.StatusProcessing, codes.Unset, false}, - {http.StatusEarlyHints, codes.Unset, false}, - {http.StatusOK, codes.Unset, false}, - {http.StatusCreated, codes.Unset, false}, - {http.StatusAccepted, codes.Unset, false}, - {http.StatusNonAuthoritativeInfo, codes.Unset, false}, - {http.StatusNoContent, codes.Unset, false}, - {http.StatusResetContent, codes.Unset, false}, - {http.StatusPartialContent, codes.Unset, false}, - {http.StatusMultiStatus, codes.Unset, false}, - {http.StatusAlreadyReported, codes.Unset, false}, - {http.StatusIMUsed, codes.Unset, false}, - {http.StatusMultipleChoices, codes.Unset, false}, - {http.StatusMovedPermanently, codes.Unset, false}, - {http.StatusFound, codes.Unset, false}, - {http.StatusSeeOther, codes.Unset, false}, - {http.StatusNotModified, codes.Unset, false}, - {http.StatusUseProxy, codes.Unset, false}, - {306, codes.Unset, false}, - {http.StatusTemporaryRedirect, codes.Unset, false}, - {http.StatusPermanentRedirect, codes.Unset, false}, - {http.StatusBadRequest, codes.Unset, false}, - {http.StatusUnauthorized, codes.Unset, false}, - {http.StatusPaymentRequired, codes.Unset, false}, - {http.StatusForbidden, codes.Unset, false}, - {http.StatusNotFound, codes.Unset, false}, - {http.StatusMethodNotAllowed, codes.Unset, false}, - {http.StatusNotAcceptable, codes.Unset, false}, - {http.StatusProxyAuthRequired, codes.Unset, false}, - {http.StatusRequestTimeout, codes.Unset, false}, - {http.StatusConflict, codes.Unset, false}, - {http.StatusGone, codes.Unset, false}, - {http.StatusLengthRequired, codes.Unset, false}, - {http.StatusPreconditionFailed, codes.Unset, false}, - {http.StatusRequestEntityTooLarge, codes.Unset, false}, - {http.StatusRequestURITooLong, codes.Unset, false}, - {http.StatusUnsupportedMediaType, codes.Unset, false}, - {http.StatusRequestedRangeNotSatisfiable, codes.Unset, false}, - {http.StatusExpectationFailed, codes.Unset, false}, - {http.StatusTeapot, codes.Unset, false}, - {http.StatusMisdirectedRequest, codes.Unset, false}, - {http.StatusUnprocessableEntity, codes.Unset, false}, - {http.StatusLocked, codes.Unset, false}, - {http.StatusFailedDependency, codes.Unset, false}, - {http.StatusTooEarly, codes.Unset, false}, - {http.StatusUpgradeRequired, codes.Unset, false}, - {http.StatusPreconditionRequired, codes.Unset, false}, - {http.StatusTooManyRequests, codes.Unset, false}, - {http.StatusRequestHeaderFieldsTooLarge, codes.Unset, false}, - {http.StatusUnavailableForLegalReasons, codes.Unset, false}, - {499, codes.Unset, false}, - {http.StatusInternalServerError, codes.Error, false}, - {http.StatusNotImplemented, codes.Error, false}, - {http.StatusBadGateway, codes.Error, false}, - {http.StatusServiceUnavailable, codes.Error, false}, - {http.StatusGatewayTimeout, codes.Error, false}, - {http.StatusHTTPVersionNotSupported, codes.Error, false}, - {http.StatusVariantAlsoNegotiates, codes.Error, false}, - {http.StatusInsufficientStorage, codes.Error, false}, - {http.StatusLoopDetected, codes.Error, false}, - {http.StatusNotExtended, codes.Error, false}, - {http.StatusNetworkAuthenticationRequired, codes.Error, false}, - {600, codes.Error, true}, - } - - for _, test := range tests { - c, msg := HTTPServerStatus(test.code) - assert.Equal(t, test.stat, c) - if test.msg && msg == "" { - t.Errorf("expected non-empty message for %d", test.code) - } else if !test.msg && msg != "" { - t.Errorf("expected empty message for %d, got: %s", test.code, msg) - } - } -} diff --git a/instrumentation/net/http/otelhttp/internal/semconvutil/netconv.go b/instrumentation/net/http/otelhttp/internal/semconvutil/netconv.go deleted file mode 100644 index df97255e418..00000000000 --- a/instrumentation/net/http/otelhttp/internal/semconvutil/netconv.go +++ /dev/null @@ -1,214 +0,0 @@ -// Code generated by gotmpl. DO NOT MODIFY. -// source: internal/shared/semconvutil/netconv.go.tmpl - -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -package semconvutil // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconvutil" - -import ( - "net" - "strconv" - "strings" - - "go.opentelemetry.io/otel/attribute" - semconv "go.opentelemetry.io/otel/semconv/v1.20.0" -) - -// NetTransport returns a trace attribute describing the transport protocol of the -// passed network. See the net.Dial for information about acceptable network -// values. -func NetTransport(network string) attribute.KeyValue { - return nc.Transport(network) -} - -// netConv are the network semantic convention attributes defined for a version -// of the OpenTelemetry specification. -type netConv struct { - NetHostNameKey attribute.Key - NetHostPortKey attribute.Key - NetPeerNameKey attribute.Key - NetPeerPortKey attribute.Key - NetProtocolName attribute.Key - NetProtocolVersion attribute.Key - NetSockFamilyKey attribute.Key - NetSockPeerAddrKey attribute.Key - NetSockPeerPortKey attribute.Key - NetSockHostAddrKey attribute.Key - NetSockHostPortKey attribute.Key - NetTransportOther attribute.KeyValue - NetTransportTCP attribute.KeyValue - NetTransportUDP attribute.KeyValue - NetTransportInProc attribute.KeyValue -} - -var nc = &netConv{ - NetHostNameKey: semconv.NetHostNameKey, - NetHostPortKey: semconv.NetHostPortKey, - NetPeerNameKey: semconv.NetPeerNameKey, - NetPeerPortKey: semconv.NetPeerPortKey, - NetProtocolName: semconv.NetProtocolNameKey, - NetProtocolVersion: semconv.NetProtocolVersionKey, - NetSockFamilyKey: semconv.NetSockFamilyKey, - NetSockPeerAddrKey: semconv.NetSockPeerAddrKey, - NetSockPeerPortKey: semconv.NetSockPeerPortKey, - NetSockHostAddrKey: semconv.NetSockHostAddrKey, - NetSockHostPortKey: semconv.NetSockHostPortKey, - NetTransportOther: semconv.NetTransportOther, - NetTransportTCP: semconv.NetTransportTCP, - NetTransportUDP: semconv.NetTransportUDP, - NetTransportInProc: semconv.NetTransportInProc, -} - -func (c *netConv) Transport(network string) attribute.KeyValue { - switch network { - case "tcp", "tcp4", "tcp6": - return c.NetTransportTCP - case "udp", "udp4", "udp6": - return c.NetTransportUDP - case "unix", "unixgram", "unixpacket": - return c.NetTransportInProc - default: - // "ip:*", "ip4:*", and "ip6:*" all are considered other. - return c.NetTransportOther - } -} - -// Host returns attributes for a network host address. -func (c *netConv) Host(address string) []attribute.KeyValue { - h, p := splitHostPort(address) - var n int - if h != "" { - n++ - if p > 0 { - n++ - } - } - - if n == 0 { - return nil - } - - attrs := make([]attribute.KeyValue, 0, n) - attrs = append(attrs, c.HostName(h)) - if p > 0 { - attrs = append(attrs, c.HostPort(p)) - } - return attrs -} - -func (c *netConv) HostName(name string) attribute.KeyValue { - return c.NetHostNameKey.String(name) -} - -func (c *netConv) HostPort(port int) attribute.KeyValue { - return c.NetHostPortKey.Int(port) -} - -func family(network, address string) string { - switch network { - case "unix", "unixgram", "unixpacket": - return "unix" - default: - if ip := net.ParseIP(address); ip != nil { - if ip.To4() == nil { - return "inet6" - } - return "inet" - } - } - return "" -} - -// Peer returns attributes for a network peer address. -func (c *netConv) Peer(address string) []attribute.KeyValue { - h, p := splitHostPort(address) - var n int - if h != "" { - n++ - if p > 0 { - n++ - } - } - - if n == 0 { - return nil - } - - attrs := make([]attribute.KeyValue, 0, n) - attrs = append(attrs, c.PeerName(h)) - if p > 0 { - attrs = append(attrs, c.PeerPort(p)) - } - return attrs -} - -func (c *netConv) PeerName(name string) attribute.KeyValue { - return c.NetPeerNameKey.String(name) -} - -func (c *netConv) PeerPort(port int) attribute.KeyValue { - return c.NetPeerPortKey.Int(port) -} - -func (c *netConv) SockPeerAddr(addr string) attribute.KeyValue { - return c.NetSockPeerAddrKey.String(addr) -} - -func (c *netConv) SockPeerPort(port int) attribute.KeyValue { - return c.NetSockPeerPortKey.Int(port) -} - -// splitHostPort splits a network address hostport of the form "host", -// "host%zone", "[host]", "[host%zone], "host:port", "host%zone:port", -// "[host]:port", "[host%zone]:port", or ":port" into host or host%zone and -// port. -// -// An empty host is returned if it is not provided or unparsable. A negative -// port is returned if it is not provided or unparsable. -func splitHostPort(hostport string) (host string, port int) { - port = -1 - - if strings.HasPrefix(hostport, "[") { - addrEnd := strings.LastIndex(hostport, "]") - if addrEnd < 0 { - // Invalid hostport. - return - } - if i := strings.LastIndex(hostport[addrEnd:], ":"); i < 0 { - host = hostport[1:addrEnd] - return - } - } else { - if i := strings.LastIndex(hostport, ":"); i < 0 { - host = hostport - return - } - } - - host, pStr, err := net.SplitHostPort(hostport) - if err != nil { - return - } - - p, err := strconv.ParseUint(pStr, 10, 16) - if err != nil { - return - } - return host, int(p) // nolint: gosec // Bitsize checked to be 16 above. -} - -func netProtocol(proto string) (name string, version string) { - name, version, _ = strings.Cut(proto, "/") - switch name { - case "HTTP": - name = "http" - case "QUIC": - name = "quic" - case "SPDY": - name = "spdy" - default: - name = strings.ToLower(name) - } - return name, version -} diff --git a/instrumentation/net/http/otelhttp/internal/semconvutil/netconv_test.go b/instrumentation/net/http/otelhttp/internal/semconvutil/netconv_test.go deleted file mode 100644 index 4f3c1540c13..00000000000 --- a/instrumentation/net/http/otelhttp/internal/semconvutil/netconv_test.go +++ /dev/null @@ -1,200 +0,0 @@ -// Code generated by gotmpl. DO NOT MODIFY. -// source: internal/shared/semconvutil/netconv_test.go.tmpl - -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -package semconvutil - -import ( - "testing" - - "github.com/stretchr/testify/assert" - - "go.opentelemetry.io/otel/attribute" -) - -const ( - addr = "127.0.0.1" - port = 1834 -) - -func TestNetTransport(t *testing.T) { - transports := map[string]attribute.KeyValue{ - "tcp": attribute.String("net.transport", "ip_tcp"), - "tcp4": attribute.String("net.transport", "ip_tcp"), - "tcp6": attribute.String("net.transport", "ip_tcp"), - "udp": attribute.String("net.transport", "ip_udp"), - "udp4": attribute.String("net.transport", "ip_udp"), - "udp6": attribute.String("net.transport", "ip_udp"), - "unix": attribute.String("net.transport", "inproc"), - "unixgram": attribute.String("net.transport", "inproc"), - "unixpacket": attribute.String("net.transport", "inproc"), - "ip:1": attribute.String("net.transport", "other"), - "ip:icmp": attribute.String("net.transport", "other"), - "ip4:proto": attribute.String("net.transport", "other"), - "ip6:proto": attribute.String("net.transport", "other"), - } - - for network, want := range transports { - assert.Equal(t, want, NetTransport(network)) - } -} - -func TestNetHost(t *testing.T) { - testAddrs(t, []addrTest{ - {address: "", expected: nil}, - {address: "192.0.0.1", expected: []attribute.KeyValue{ - nc.HostName("192.0.0.1"), - }}, - {address: "192.0.0.1:9090", expected: []attribute.KeyValue{ - nc.HostName("192.0.0.1"), - nc.HostPort(9090), - }}, - }, nc.Host) -} - -func TestNetHostName(t *testing.T) { - expected := attribute.Key("net.host.name").String(addr) - assert.Equal(t, expected, nc.HostName(addr)) -} - -func TestNetHostPort(t *testing.T) { - expected := attribute.Key("net.host.port").Int(port) - assert.Equal(t, expected, nc.HostPort(port)) -} - -func TestNetPeer(t *testing.T) { - testAddrs(t, []addrTest{ - {address: "", expected: nil}, - {address: "example.com", expected: []attribute.KeyValue{ - nc.PeerName("example.com"), - }}, - {address: "/tmp/file", expected: []attribute.KeyValue{ - nc.PeerName("/tmp/file"), - }}, - {address: "192.0.0.1", expected: []attribute.KeyValue{ - nc.PeerName("192.0.0.1"), - }}, - {address: ":9090", expected: nil}, - {address: "192.0.0.1:9090", expected: []attribute.KeyValue{ - nc.PeerName("192.0.0.1"), - nc.PeerPort(9090), - }}, - }, nc.Peer) -} - -func TestNetPeerName(t *testing.T) { - expected := attribute.Key("net.peer.name").String(addr) - assert.Equal(t, expected, nc.PeerName(addr)) -} - -func TestNetPeerPort(t *testing.T) { - expected := attribute.Key("net.peer.port").Int(port) - assert.Equal(t, expected, nc.PeerPort(port)) -} - -func TestNetSockPeerName(t *testing.T) { - expected := attribute.Key("net.sock.peer.addr").String(addr) - assert.Equal(t, expected, nc.SockPeerAddr(addr)) -} - -func TestNetSockPeerPort(t *testing.T) { - expected := attribute.Key("net.sock.peer.port").Int(port) - assert.Equal(t, expected, nc.SockPeerPort(port)) -} - -func TestNetFamily(t *testing.T) { - tests := []struct { - network string - address string - expect string - }{ - {"", "", ""}, - {"unix", "", "unix"}, - {"unix", "gibberish", "unix"}, - {"unixgram", "", "unix"}, - {"unixgram", "gibberish", "unix"}, - {"unixpacket", "gibberish", "unix"}, - {"tcp", "123.0.2.8", "inet"}, - {"tcp", "gibberish", ""}, - {"", "123.0.2.8", "inet"}, - {"", "gibberish", ""}, - {"tcp", "fe80::1", "inet6"}, - {"", "fe80::1", "inet6"}, - } - - for _, test := range tests { - got := family(test.network, test.address) - assert.Equal(t, test.expect, got, test.network+"/"+test.address) - } -} - -func TestSplitHostPort(t *testing.T) { - tests := []struct { - hostport string - host string - port int - }{ - {"", "", -1}, - {":8080", "", 8080}, - {"127.0.0.1", "127.0.0.1", -1}, - {"www.example.com", "www.example.com", -1}, - {"127.0.0.1%25en0", "127.0.0.1%25en0", -1}, - {"[]", "", -1}, // Ensure this doesn't panic. - {"[fe80::1", "", -1}, - {"[fe80::1]", "fe80::1", -1}, - {"[fe80::1%25en0]", "fe80::1%25en0", -1}, - {"[fe80::1]:8080", "fe80::1", 8080}, - {"[fe80::1]::", "", -1}, // Too many colons. - {"127.0.0.1:", "127.0.0.1", -1}, - {"127.0.0.1:port", "127.0.0.1", -1}, - {"127.0.0.1:8080", "127.0.0.1", 8080}, - {"www.example.com:8080", "www.example.com", 8080}, - {"127.0.0.1%25en0:8080", "127.0.0.1%25en0", 8080}, - } - - for _, test := range tests { - h, p := splitHostPort(test.hostport) - assert.Equal(t, test.host, h, test.hostport) - assert.Equal(t, test.port, p, test.hostport) - } -} - -type addrTest struct { - address string - expected []attribute.KeyValue -} - -func testAddrs(t *testing.T, tests []addrTest, f func(string) []attribute.KeyValue) { - t.Helper() - - for _, test := range tests { - got := f(test.address) - assert.Equal(t, cap(test.expected), cap(got), "slice capacity") - assert.ElementsMatch(t, test.expected, got, test.address) - } -} - -func TestNetProtocol(t *testing.T) { - type testCase struct { - name, version string - } - tests := map[string]testCase{ - "HTTP/1.0": {name: "http", version: "1.0"}, - "HTTP/1.1": {name: "http", version: "1.1"}, - "HTTP/2": {name: "http", version: "2"}, - "HTTP/3": {name: "http", version: "3"}, - "SPDY": {name: "spdy"}, - "SPDY/2": {name: "spdy", version: "2"}, - "QUIC": {name: "quic"}, - "unknown/proto/2": {name: "unknown", version: "proto/2"}, - "other": {name: "other"}, - } - - for proto, want := range tests { - name, version := netProtocol(proto) - assert.Equal(t, want.name, name) - assert.Equal(t, want.version, version) - } -} diff --git a/instrumentation/net/http/otelhttp/transport.go b/instrumentation/net/http/otelhttp/transport.go index e4c02a4296b..aed3014788c 100644 --- a/instrumentation/net/http/otelhttp/transport.go +++ b/instrumentation/net/http/otelhttp/transport.go @@ -147,15 +147,6 @@ func (t *Transport) RoundTrip(r *http.Request) (*http.Response, error) { RequestSize: bw.BytesRead(), } - if err == nil { - // For handling response bytes we leverage a callback when the client reads the http response - readRecordFunc := func(n int64) { - t.semconv.RecordResponseSize(ctx, n, metricOpts) - } - - res.Body = newWrappedBody(span, readRecordFunc, res.Body) - } - // Use floating point division here for higher precision (instead of Millisecond method). elapsedTime := float64(time.Since(requestStartTime)) / float64(time.Millisecond) diff --git a/internal/shared/semconv/env.go.tmpl b/internal/shared/semconv/env.go.tmpl index 8ebb9e71ba9..31fc30eb1ea 100644 --- a/internal/shared/semconv/env.go.tmpl +++ b/internal/shared/semconv/env.go.tmpl @@ -10,7 +10,6 @@ import ( "context" "fmt" "net/http" - "os" "strings" "sync" @@ -33,14 +32,6 @@ type ResponseTelemetry struct { } type HTTPServer struct { - duplicate bool - - // Old metrics - requestBytesCounter metric.Int64Counter - responseBytesCounter metric.Int64Counter - serverLatencyMeasure metric.Float64Histogram - - // New metrics requestBodySizeHistogram httpconv.ServerRequestBodySize responseBodySizeHistogram httpconv.ServerResponseBodySize requestDurationHistogram httpconv.ServerRequestDuration @@ -63,20 +54,10 @@ type HTTPServer struct { // If the primary server name is not known, server should be an empty string. // The req Host will be used to determine the server instead. func (s HTTPServer) RequestTraceAttrs(server string, req *http.Request, opts RequestTraceAttrsOpts) []attribute.KeyValue { - attrs := CurrentHTTPServer{}.RequestTraceAttrs(server, req, opts) - if s.duplicate { - return OldHTTPServer{}.RequestTraceAttrs(server, req, attrs) - } - return attrs + return CurrentHTTPServer{}.RequestTraceAttrs(server, req, opts) } func (s HTTPServer) NetworkTransportAttr(network string) []attribute.KeyValue { - if s.duplicate { - return []attribute.KeyValue{ - OldHTTPServer{}.NetworkTransportAttr(network), - CurrentHTTPServer{}.NetworkTransportAttr(network), - } - } return []attribute.KeyValue{ CurrentHTTPServer{}.NetworkTransportAttr(network), } @@ -86,11 +67,7 @@ func (s HTTPServer) NetworkTransportAttr(network string) []attribute.KeyValue { // // If any of the fields in the ResponseTelemetry are not set the attribute will be omitted. func (s HTTPServer) ResponseTraceAttrs(resp ResponseTelemetry) []attribute.KeyValue { - attrs := CurrentHTTPServer{}.ResponseTraceAttrs(resp) - if s.duplicate { - return OldHTTPServer{}.ResponseTraceAttrs(resp, attrs) - } - return attrs + return CurrentHTTPServer{}.ResponseTraceAttrs(resp) } // Route returns the attribute for the route. @@ -156,18 +133,6 @@ func (s HTTPServer) RecordMetrics(ctx context.Context, md ServerMetricData) { s.requestDurationHistogram.Inst().Record(ctx, md.ElapsedTime/1000.0, o) *recordOpts = (*recordOpts)[:0] metricRecordOptionPool.Put(recordOpts) - - if s.duplicate && 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) - } } // hasOptIn returns true if the comma-separated version string contains the @@ -182,11 +147,7 @@ func hasOptIn(version, optIn string) bool { } func NewHTTPServer(meter metric.Meter) HTTPServer { - env := strings.ToLower(os.Getenv(OTelSemConvStabilityOptIn)) - duplicate := hasOptIn(env, "http/dup") - server := HTTPServer{ - duplicate: duplicate, - } + server := HTTPServer{} var err error server.requestBodySizeHistogram, err = httpconv.NewServerRequestBodySize(meter) @@ -203,32 +164,16 @@ func NewHTTPServer(meter metric.Meter) HTTPServer { ), ) handleErr(err) - - if duplicate { - server.requestBytesCounter, server.responseBytesCounter, server.serverLatencyMeasure = OldHTTPServer{}.createMeasures(meter) - } return server } type HTTPClient struct { - duplicate bool - - // old metrics - requestBytesCounter metric.Int64Counter - responseBytesCounter metric.Int64Counter - latencyMeasure metric.Float64Histogram - - // new metrics requestBodySize httpconv.ClientRequestBodySize requestDuration httpconv.ClientRequestDuration } func NewHTTPClient(meter metric.Meter) HTTPClient { - env := strings.ToLower(os.Getenv(OTelSemConvStabilityOptIn)) - duplicate := hasOptIn(env, "http/dup") - client := HTTPClient{ - duplicate: duplicate, - } + client := HTTPClient{} var err error client.requestBodySize, err = httpconv.NewClientRequestBodySize(meter) @@ -240,29 +185,17 @@ func NewHTTPClient(meter metric.Meter) HTTPClient { ) handleErr(err) - if duplicate { - client.requestBytesCounter, client.responseBytesCounter, client.latencyMeasure = OldHTTPClient{}.createMeasures(meter) - } - return client } // RequestTraceAttrs returns attributes for an HTTP request made by a client. func (c HTTPClient) RequestTraceAttrs(req *http.Request) []attribute.KeyValue { - attrs := CurrentHTTPClient{}.RequestTraceAttrs(req) - if c.duplicate { - return OldHTTPClient{}.RequestTraceAttrs(req, attrs) - } - return attrs + return CurrentHTTPClient{}.RequestTraceAttrs(req) } // ResponseTraceAttrs returns metric attributes for an HTTP request made by a client. func (c HTTPClient) ResponseTraceAttrs(resp *http.Response) []attribute.KeyValue { - attrs := CurrentHTTPClient{}.ResponseTraceAttrs(resp) - if c.duplicate { - return OldHTTPClient{}.ResponseTraceAttrs(resp, attrs) - } - return attrs + return CurrentHTTPClient{}.ResponseTraceAttrs(resp) } func (c HTTPClient) Status(code int) (codes.Code, string) { @@ -302,42 +235,14 @@ func (c HTTPClient) MetricOptions(ma MetricAttributes) map[string]MetricOpts { addOptions: set, } - if c.duplicate { - attributes := OldHTTPClient{}.MetricAttributes(ma.Req, ma.StatusCode, ma.AdditionalAttributes) - set := metric.WithAttributeSet(attribute.NewSet(attributes...)) - opts["old"] = MetricOpts{ - measurement: set, - addOptions: set, - } - } - return opts } func (s HTTPClient) RecordMetrics(ctx context.Context, md MetricData, opts map[string]MetricOpts) { s.requestBodySize.Inst().Record(ctx, md.RequestSize, opts["new"].MeasurementOption()) s.requestDuration.Inst().Record(ctx, md.ElapsedTime/1000, opts["new"].MeasurementOption()) - - if s.duplicate { - s.requestBytesCounter.Add(ctx, md.RequestSize, opts["old"].AddOptions()) - s.latencyMeasure.Record(ctx, md.ElapsedTime, opts["old"].MeasurementOption()) - } -} - -func (s HTTPClient) RecordResponseSize(ctx context.Context, responseData int64, opts map[string]MetricOpts) { - if s.responseBytesCounter == nil { - // This will happen if an HTTPClient{} is used instead of NewHTTPClient(). - return - } - - s.responseBytesCounter.Add(ctx, responseData, opts["old"].AddOptions()) } func (s HTTPClient) TraceAttributes(host string) []attribute.KeyValue { - attrs := CurrentHTTPClient{}.TraceAttributes(host) - if s.duplicate { - return OldHTTPClient{}.TraceAttributes(host, attrs) - } - - return attrs + return CurrentHTTPClient{}.TraceAttributes(host) } diff --git a/internal/shared/semconv/env_test.go.tmpl b/internal/shared/semconv/env_test.go.tmpl index 45b520854dd..11388bb8324 100644 --- a/internal/shared/semconv/env_test.go.tmpl +++ b/internal/shared/semconv/env_test.go.tmpl @@ -54,7 +54,6 @@ func TestHTTPServerDoesNotPanic(t *testing.T) { func TestServerNetworkTransportAttr(t *testing.T) { for _, tt := range []struct { name string - optinVal string network string wantAttributes []attribute.KeyValue @@ -67,29 +66,8 @@ func TestServerNetworkTransportAttr(t *testing.T) { attribute.String("network.transport", "tcp"), }, }, - { - name: "without a dup optin", - optinVal: "http/dup", - network: "tcp", - - wantAttributes: []attribute.KeyValue{ - attribute.String("net.transport", "ip_tcp"), - attribute.String("network.transport", "tcp"), - }, - }, - { - name: "with mixed categories", - optinVal: "http/dup,database", - network: "tcp", - - wantAttributes: []attribute.KeyValue{ - attribute.String("net.transport", "ip_tcp"), - attribute.String("network.transport", "tcp"), - }, - }, } { t.Run(tt.name, func(t *testing.T) { - t.Setenv(OTelSemConvStabilityOptIn, tt.optinVal) s := NewHTTPServer(nil) assert.Equal(t, tt.wantAttributes, s.NetworkTransportAttr(tt.network)) @@ -124,7 +102,6 @@ func TestHTTPClientDoesNotPanic(t *testing.T) { Req: req, StatusCode: 200, }) - tt.client.RecordResponseSize(context.Background(), 40, opts) tt.client.RecordMetrics(context.Background(), MetricData{ RequestSize: 20, ElapsedTime: 1, @@ -137,7 +114,6 @@ func TestHTTPClientDoesNotPanic(t *testing.T) { func TestHTTPClientTraceAttributes(t *testing.T) { for _, tt := range []struct { name string - optinVal string wantAttributes []attribute.KeyValue }{ @@ -148,28 +124,8 @@ func TestHTTPClientTraceAttributes(t *testing.T) { attribute.String("server.address", "example.com"), }, }, - { - name: "with optin set to duplicate", - optinVal: "http/dup", - - wantAttributes: []attribute.KeyValue{ - attribute.String("server.address", "example.com"), - attribute.String("net.host.name", "example.com"), - }, - }, - { - name: "with mixed categories", - optinVal: "http/dup,database", - - wantAttributes: []attribute.KeyValue{ - attribute.String("server.address", "example.com"), - attribute.String("net.host.name", "example.com"), - }, - }, } { t.Run(tt.name, func(t *testing.T) { - t.Setenv(OTelSemConvStabilityOptIn, tt.optinVal) - c := NewHTTPClient(nil) a := c.TraceAttributes("example.com") assert.Equal(t, tt.wantAttributes, a) @@ -180,7 +136,6 @@ func TestHTTPClientTraceAttributes(t *testing.T) { func TestClientTraceAttributes(t *testing.T) { for _, tt := range []struct { name string - optinVal string host string wantAttributes []attribute.KeyValue @@ -193,29 +148,8 @@ func TestClientTraceAttributes(t *testing.T) { attribute.String("server.address", "example.com"), }, }, - { - name: "without a dup optin", - optinVal: "http/dup", - host: "example.com", - - wantAttributes: []attribute.KeyValue{ - attribute.String("server.address", "example.com"), - attribute.String("net.host.name", "example.com"), - }, - }, - { - name: "with mixed categories", - optinVal: "http/dup", - host: "example.com", - - wantAttributes: []attribute.KeyValue{ - attribute.String("server.address", "example.com"), - attribute.String("net.host.name", "example.com"), - }, - }, } { t.Run(tt.name, func(t *testing.T) { - t.Setenv(OTelSemConvStabilityOptIn, tt.optinVal) s := NewHTTPClient(nil) assert.Equal(t, tt.wantAttributes, s.TraceAttributes(tt.host)) diff --git a/internal/shared/semconv/httpconvtest_test.go.tmpl b/internal/shared/semconv/httpconvtest_test.go.tmpl index 9eee883eeb5..4bebfa77fca 100644 --- a/internal/shared/semconv/httpconvtest_test.go.tmpl +++ b/internal/shared/semconv/httpconvtest_test.go.tmpl @@ -28,7 +28,6 @@ import ( ) func TestNewTraceRequest(t *testing.T) { - t.Setenv("OTEL_SEMCONV_STABILITY_OPT_IN", "http/dup") serv := semconv.NewHTTPServer(nil) want := func(req testServerReq) []attribute.KeyValue { return []attribute.KeyValue{ @@ -42,16 +41,6 @@ func TestNewTraceRequest(t *testing.T) { attribute.String("client.address", req.clientIP), attribute.String("network.protocol.version", "1.1"), attribute.String("url.path", "/"), - attribute.String("http.method", "GET"), - attribute.String("http.scheme", "http"), - attribute.String("net.host.name", req.hostname), - attribute.Int("net.host.port", req.serverPort), - attribute.String("net.sock.peer.addr", req.peerAddr), - attribute.Int("net.sock.peer.port", req.peerPort), - attribute.String("user_agent.original", "Go-http-client/1.1"), - attribute.String("http.client_ip", req.clientIP), - attribute.String("net.protocol.version", "1.1"), - attribute.String("http.target", "/"), } } testTraceRequest(t, serv, want) @@ -174,13 +163,11 @@ func TestNewServerRecordMetrics(t *testing.T) { 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, + name: "No Meter", serverFunc: func(metric.MeterProvider) semconv.HTTPServer { return semconv.NewHTTPServer(nil) }, @@ -189,8 +176,7 @@ func TestNewServerRecordMetrics(t *testing.T) { }, }, { - name: "No environment variable set, but with Meter", - setEnv: false, + name: "With Meter", serverFunc: func(mp metric.MeterProvider) semconv.HTTPServer { return semconv.NewHTTPServer(mp.Meter("test")) }, @@ -202,36 +188,10 @@ func TestNewServerRecordMetrics(t *testing.T) { metricdatatest.AssertEqual(t, expectedCurrentScopeMetric, 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, expectedOldScopeMetric, 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)) @@ -312,7 +272,6 @@ func TestNewTraceResponse(t *testing.T) { } func TestNewTraceRequest_Client(t *testing.T) { - t.Setenv("OTEL_SEMCONV_STABILITY_OPT_IN", "http/dup") body := strings.NewReader("Hello, world!") url := "https://example.com:8888/foo/bar?stuff=morestuff" req := httptest.NewRequest("pOST", url, body) @@ -321,29 +280,22 @@ func TestNewTraceRequest_Client(t *testing.T) { want := []attribute.KeyValue{ attribute.String("http.request.method", "POST"), attribute.String("http.request.method_original", "pOST"), - attribute.String("http.method", "pOST"), attribute.String("url.full", url), - attribute.String("http.url", url), attribute.String("server.address", "example.com"), attribute.Int("server.port", 8888), attribute.String("network.protocol.version", "1.1"), - attribute.String("net.peer.name", "example.com"), - attribute.Int("net.peer.port", 8888), - attribute.String("user_agent.original", "go-test-agent"), - attribute.Int("http.request_content_length", 13), } client := semconv.NewHTTPClient(nil) assert.ElementsMatch(t, want, client.RequestTraceAttrs(req)) } func TestNewTraceResponse_Client(t *testing.T) { - t.Setenv("OTEL_SEMCONV_STABILITY_OPT_IN", "http/dup") testcases := []struct { resp http.Response want []attribute.KeyValue }{ - {resp: http.Response{StatusCode: 200, ContentLength: 123}, want: []attribute.KeyValue{attribute.Int("http.response.status_code", 200), attribute.Int("http.status_code", 200), attribute.Int("http.response_content_length", 123)}}, - {resp: http.Response{StatusCode: 404, ContentLength: 0}, want: []attribute.KeyValue{attribute.Int("http.response.status_code", 404), attribute.Int("http.status_code", 404), attribute.String("error.type", "404")}}, + {resp: http.Response{StatusCode: 200, ContentLength: 123}, want: []attribute.KeyValue{attribute.Int("http.response.status_code", 200)}}, + {resp: http.Response{StatusCode: 404, ContentLength: 0}, want: []attribute.KeyValue{attribute.Int("http.response.status_code", 404), attribute.String("error.type", "404")}}, } for _, tt := range testcases { @@ -401,12 +353,6 @@ func TestRequestErrorType(t *testing.T) { } func TestNewClientRecordMetrics(t *testing.T) { - oldAttrs := attribute.NewSet( - attribute.String("http.method", "POST"), - attribute.Int64("http.status_code", 301), - attribute.String("net.peer.name", "example.com"), - ) - currAttrs := attribute.NewSet( attribute.String("http.request.method", "POST"), attribute.Int64("http.response.status_code", 301), @@ -451,47 +397,13 @@ func TestNewClientRecordMetrics(t *testing.T) { }, } - // The OldHTTPClient version - expectedOldScopeMetric := expectedCurrentScopeMetric - expectedOldScopeMetric.Metrics = append(expectedOldScopeMetric.Metrics, []metricdata.Metrics{ - { - Name: "http.client.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.client.duration", - Description: "Measures the duration of outbound HTTP requests.", - Unit: "ms", - Data: metricdata.Histogram[float64]{ - Temporality: metricdata.CumulativeTemporality, - DataPoints: []metricdata.HistogramDataPoint[float64]{ - { - Attributes: oldAttrs, - }, - }, - }, - }, - }...) - tests := []struct { name string - setEnv bool clientFunc func(metric.MeterProvider) semconv.HTTPClient wantFunc func(t *testing.T, rm metricdata.ResourceMetrics) }{ { name: "No environment variable set, and no Meter", - setEnv: false, clientFunc: func(metric.MeterProvider) semconv.HTTPClient { return semconv.NewHTTPClient(nil) }, @@ -500,8 +412,7 @@ func TestNewClientRecordMetrics(t *testing.T) { }, }, { - name: "No environment variable set, but with Meter", - setEnv: false, + name: "With Meter", clientFunc: func(mp metric.MeterProvider) semconv.HTTPClient { return semconv.NewHTTPClient(mp.Meter("test")) }, @@ -512,36 +423,10 @@ func TestNewClientRecordMetrics(t *testing.T) { metricdatatest.AssertEqual(t, expectedCurrentScopeMetric, rm.ScopeMetrics[0], metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreValue()) }, }, - { - name: "Set environment variable, but no Meter", - setEnv: true, - clientFunc: func(metric.MeterProvider) semconv.HTTPClient { - return semconv.NewHTTPClient(nil) - }, - wantFunc: func(t *testing.T, rm metricdata.ResourceMetrics) { - assert.Empty(t, rm.ScopeMetrics) - }, - }, - { - name: "Set environment variable and Meter", - setEnv: true, - clientFunc: func(mp metric.MeterProvider) semconv.HTTPClient { - return semconv.NewHTTPClient(mp.Meter("test")) - }, - wantFunc: func(t *testing.T, rm metricdata.ResourceMetrics) { - require.Len(t, rm.ScopeMetrics, 1) - require.Len(t, rm.ScopeMetrics[0].Metrics, 4) - metricdatatest.AssertEqual(t, expectedOldScopeMetric, 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)) @@ -564,111 +449,6 @@ func TestNewClientRecordMetrics(t *testing.T) { } } -func TestClientRecordResponseSize(t *testing.T) { - oldAttrs := attribute.NewSet( - attribute.String("http.method", "POST"), - attribute.Int64("http.status_code", 301), - attribute.String("net.peer.name", "example.com"), - ) - - // The OldHTTPClient version - expectedOldScopeMetric := metricdata.ScopeMetrics{ - Scope: instrumentation.Scope{ - Name: "test", - }, - Metrics: []metricdata.Metrics{ - { - Name: "http.client.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, - }, - }, - }, - }, - }, - } - - tests := []struct { - name string - setEnv bool - clientFunc func(metric.MeterProvider) semconv.HTTPClient - wantFunc func(t *testing.T, rm metricdata.ResourceMetrics) - }{ - { - name: "No environment variable set, and no Meter", - setEnv: false, - clientFunc: func(metric.MeterProvider) semconv.HTTPClient { - return semconv.NewHTTPClient(nil) - }, - wantFunc: func(t *testing.T, rm metricdata.ResourceMetrics) { - assert.Empty(t, rm.ScopeMetrics) - }, - }, - { - name: "No environment variable set, but with Meter", - setEnv: false, - clientFunc: func(mp metric.MeterProvider) semconv.HTTPClient { - return semconv.NewHTTPClient(mp.Meter("test")) - }, - wantFunc: func(t *testing.T, rm metricdata.ResourceMetrics) { - require.Empty(t, rm.ScopeMetrics) - }, - }, - { - name: "Set environment variable, but no Meter", - setEnv: true, - clientFunc: func(metric.MeterProvider) semconv.HTTPClient { - return semconv.NewHTTPClient(nil) - }, - wantFunc: func(t *testing.T, rm metricdata.ResourceMetrics) { - assert.Empty(t, rm.ScopeMetrics) - }, - }, - { - name: "Set environment variable and Meter", - setEnv: true, - clientFunc: func(mp metric.MeterProvider) semconv.HTTPClient { - return semconv.NewHTTPClient(mp.Meter("test")) - }, - wantFunc: func(t *testing.T, rm metricdata.ResourceMetrics) { - require.Len(t, rm.ScopeMetrics, 1) - require.Len(t, rm.ScopeMetrics[0].Metrics, 1) - metricdatatest.AssertEqual(t, expectedOldScopeMetric, 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)) - - client := tt.clientFunc(mp) - req, err := http.NewRequest("POST", "http://example.com", nil) - assert.NoError(t, err) - - client.RecordResponseSize(context.Background(), 100, client.MetricOptions(semconv.MetricAttributes{ - Req: req, - StatusCode: 301, - })) - - rm := metricdata.ResourceMetrics{} - require.NoError(t, reader.Collect(context.Background(), &rm)) - tt.wantFunc(t, rm) - }) - } -} - type customError struct{} func (customError) Error() string { diff --git a/internal/shared/semconv/v1.20.0.go.tmpl b/internal/shared/semconv/v1.20.0.go.tmpl deleted file mode 100644 index fb66c6fe831..00000000000 --- a/internal/shared/semconv/v1.20.0.go.tmpl +++ /dev/null @@ -1,273 +0,0 @@ -// Code generated by gotmpl. DO NOT MODIFY. -// source: internal/shared/semconv/v120.0.go.tmpl - -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -package semconv - -import ( - "errors" - "io" - "net/http" - "slices" - - "{{.pkg}}/internal/semconvutil" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/metric" - "go.opentelemetry.io/otel/metric/noop" - semconv "go.opentelemetry.io/otel/semconv/v1.20.0" -) - -type OldHTTPServer struct{} - -// RequestTraceAttrs returns trace attributes for an HTTP request received by a -// server. -// -// The server must be the primary server name if it is known. For example this -// would be the ServerName directive -// (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache -// server, and the server_name directive -// (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an -// nginx server. More generically, the primary server name would be the host -// header value that matches the default virtual host of an HTTP server. It -// should include the host identifier and if a port is used to route to the -// server that port identifier should be included as an appropriate port -// suffix. -// -// If the primary server name is not known, server should be an empty string. -// The req Host will be used to determine the server instead. -func (o OldHTTPServer) RequestTraceAttrs(server string, req *http.Request, attrs []attribute.KeyValue) []attribute.KeyValue { - return semconvutil.HTTPServerRequest(server, req, semconvutil.HTTPServerRequestOptions{}, attrs) -} - -func (o OldHTTPServer) NetworkTransportAttr(network string) attribute.KeyValue { - return semconvutil.NetTransport(network) -} - -// ResponseTraceAttrs returns trace attributes for telemetry from an HTTP response. -// -// If any of the fields in the ResponseTelemetry are not set the attribute will be omitted. -func (o OldHTTPServer) ResponseTraceAttrs(resp ResponseTelemetry, attributes []attribute.KeyValue) []attribute.KeyValue { - if resp.ReadBytes > 0 { - attributes = append(attributes, semconv.HTTPRequestContentLength(int(resp.ReadBytes))) - } - if resp.ReadError != nil && !errors.Is(resp.ReadError, io.EOF) { - // This is not in the semantic conventions, but is historically provided - attributes = append(attributes, attribute.String("http.read_error", resp.ReadError.Error())) - } - if resp.WriteBytes > 0 { - attributes = append(attributes, semconv.HTTPResponseContentLength(int(resp.WriteBytes))) - } - if resp.StatusCode > 0 { - attributes = append(attributes, semconv.HTTPStatusCode(resp.StatusCode)) - } - if resp.WriteError != nil && !errors.Is(resp.WriteError, io.EOF) { - // This is not in the semantic conventions, but is historically provided - attributes = append(attributes, attribute.String("http.write_error", resp.WriteError.Error())) - } - - return attributes -} - -// Route returns the attribute for the route. -func (o OldHTTPServer) Route(route string) attribute.KeyValue { - return semconv.HTTPRoute(route) -} - -// HTTPStatusCode returns the attribute for the HTTP status code. -// This is a temporary function needed by metrics. This will be removed when MetricsRequest is added. -func HTTPStatusCode(status int) attribute.KeyValue { - return semconv.HTTPStatusCode(status) -} - -// Server HTTP metrics. -const ( - serverRequestSize = "http.server.request.size" // Incoming request bytes total - serverResponseSize = "http.server.response.size" // Incoming response bytes total - serverDuration = "http.server.duration" // Incoming end to end duration, milliseconds -) - -func (h OldHTTPServer) createMeasures(meter metric.Meter) (metric.Int64Counter, metric.Int64Counter, metric.Float64Histogram) { - if meter == nil { - return noop.Int64Counter{}, noop.Int64Counter{}, noop.Float64Histogram{} - } - var err error - requestBytesCounter, err := meter.Int64Counter( - serverRequestSize, - metric.WithUnit("By"), - metric.WithDescription("Measures the size of HTTP request messages."), - ) - handleErr(err) - - responseBytesCounter, err := meter.Int64Counter( - serverResponseSize, - metric.WithUnit("By"), - metric.WithDescription("Measures the size of HTTP response messages."), - ) - handleErr(err) - - serverLatencyMeasure, err := meter.Float64Histogram( - serverDuration, - metric.WithUnit("ms"), - metric.WithDescription("Measures the duration of inbound HTTP requests."), - ) - handleErr(err) - - return requestBytesCounter, responseBytesCounter, serverLatencyMeasure -} - -func (o OldHTTPServer) MetricAttributes(server string, req *http.Request, statusCode int, additionalAttributes []attribute.KeyValue) []attribute.KeyValue { - n := 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 { - n++ - } - protoName, protoVersion := netProtocol(req.Proto) - if protoName != "" { - n++ - } - if protoVersion != "" { - n++ - } - - if statusCode > 0 { - n++ - } - - attributes := slices.Grow(additionalAttributes, n) - attributes = append(attributes, - semconv.HTTPMethod(standardizeHTTPMethod(req.Method)), - o.scheme(req.TLS != nil), - semconv.NetHostName(host)) - - if hostPort > 0 { - attributes = append(attributes, semconv.NetHostPort(hostPort)) - } - if protoName != "" { - attributes = append(attributes, semconv.NetProtocolName(protoName)) - } - if protoVersion != "" { - attributes = append(attributes, semconv.NetProtocolVersion(protoVersion)) - } - - if statusCode > 0 { - attributes = append(attributes, semconv.HTTPStatusCode(statusCode)) - } - return attributes -} - -func (o OldHTTPServer) scheme(https bool) attribute.KeyValue { // nolint:revive - if https { - return semconv.HTTPSchemeHTTPS - } - return semconv.HTTPSchemeHTTP -} - -type OldHTTPClient struct{} - -func (o OldHTTPClient) RequestTraceAttrs(req *http.Request, attrs []attribute.KeyValue) []attribute.KeyValue { - return semconvutil.HTTPClientRequest(req, attrs) -} - -func (o OldHTTPClient) ResponseTraceAttrs(resp *http.Response, attrs []attribute.KeyValue) []attribute.KeyValue { - return semconvutil.HTTPClientResponse(resp, attrs) -} - -func (o OldHTTPClient) MetricAttributes(req *http.Request, statusCode int, additionalAttributes []attribute.KeyValue) []attribute.KeyValue { - /* The following semantic conventions are returned if present: - http.method string - http.status_code int - net.peer.name string - net.peer.port int - */ - - n := 2 // method, peer name. - var h string - if req.URL != nil { - h = req.URL.Host - } - var requestHost string - var requestPort int - for _, hostport := range []string{h, req.Header.Get("Host")} { - requestHost, requestPort = SplitHostPort(hostport) - if requestHost != "" || requestPort > 0 { - break - } - } - - port := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", requestPort) - if port > 0 { - n++ - } - - if statusCode > 0 { - n++ - } - - attributes := slices.Grow(additionalAttributes, n) - attributes = append(attributes, - semconv.HTTPMethod(standardizeHTTPMethod(req.Method)), - semconv.NetPeerName(requestHost), - ) - - if port > 0 { - attributes = append(attributes, semconv.NetPeerPort(port)) - } - - if statusCode > 0 { - attributes = append(attributes, semconv.HTTPStatusCode(statusCode)) - } - return attributes -} - -// Client HTTP metrics. -const ( - clientRequestSize = "http.client.request.size" // Incoming request bytes total - clientResponseSize = "http.client.response.size" // Incoming response bytes total - clientDuration = "http.client.duration" // Incoming end to end duration, milliseconds -) - -func (o OldHTTPClient) createMeasures(meter metric.Meter) (metric.Int64Counter, metric.Int64Counter, metric.Float64Histogram) { - if meter == nil { - return noop.Int64Counter{}, noop.Int64Counter{}, noop.Float64Histogram{} - } - requestBytesCounter, err := meter.Int64Counter( - clientRequestSize, - metric.WithUnit("By"), - metric.WithDescription("Measures the size of HTTP request messages."), - ) - handleErr(err) - - responseBytesCounter, err := meter.Int64Counter( - clientResponseSize, - metric.WithUnit("By"), - metric.WithDescription("Measures the size of HTTP response messages."), - ) - handleErr(err) - - latencyMeasure, err := meter.Float64Histogram( - clientDuration, - metric.WithUnit("ms"), - metric.WithDescription("Measures the duration of outbound HTTP requests."), - ) - handleErr(err) - - return requestBytesCounter, responseBytesCounter, latencyMeasure -} - -// TraceAttributes returns attributes for httptrace. -func (c OldHTTPClient) TraceAttributes(host string, attrs []attribute.KeyValue) []attribute.KeyValue { - return append(attrs, semconv.NetHostName(host)) -} diff --git a/internal/shared/semconvutil/httpconv.go.tmpl b/internal/shared/semconvutil/httpconv.go.tmpl deleted file mode 100644 index 86e885a36f9..00000000000 --- a/internal/shared/semconvutil/httpconv.go.tmpl +++ /dev/null @@ -1,594 +0,0 @@ -// Code generated by gotmpl. DO NOT MODIFY. -// source: internal/shared/semconvutil/httpconv.go.tmpl - -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -// Package semconvutil provides OpenTelemetry semantic convention utilities. -package semconvutil - -import ( - "fmt" - "net/http" - "slices" - "strings" - - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/codes" - semconv "go.opentelemetry.io/otel/semconv/v1.20.0" -) - -type HTTPServerRequestOptions struct { - // If set, this is used as value for the "http.client_ip" attribute. - HTTPClientIP string -} - -// HTTPClientResponse returns trace attributes for an HTTP response received by a -// client from a server. It will return the following attributes if the related -// values are defined in resp: "http.status.code", -// "http.response_content_length". -// -// This does not add all OpenTelemetry required attributes for an HTTP event, -// it assumes ClientRequest was used to create the span with a complete set of -// attributes. If a complete set of attributes can be generated using the -// request contained in resp. For example: -// -// HTTPClientResponse(resp, ClientRequest(resp.Request))) -func HTTPClientResponse(resp *http.Response, attrs []attribute.KeyValue) []attribute.KeyValue { - return hc.ClientResponse(resp, attrs) -} - -// HTTPClientRequest returns trace attributes for an HTTP request made by a client. -// The following attributes are always returned: "http.url", "http.method", -// "net.peer.name". The following attributes are returned if the related values -// are defined in req: "net.peer.port", "user_agent.original", -// "http.request_content_length". -func HTTPClientRequest(req *http.Request, attrs []attribute.KeyValue) []attribute.KeyValue { - return hc.ClientRequest(req, attrs) -} - -// HTTPClientRequestMetrics returns metric attributes for an HTTP request made by a client. -// The following attributes are always returned: "http.method", "net.peer.name". -// The following attributes are returned if the -// related values are defined in req: "net.peer.port". -func HTTPClientRequestMetrics(req *http.Request) []attribute.KeyValue { - return hc.ClientRequestMetrics(req) -} - -// HTTPClientStatus returns a span status code and message for an HTTP status code -// value received by a client. -func HTTPClientStatus(code int) (codes.Code, string) { - return hc.ClientStatus(code) -} - -// HTTPServerRequest returns trace attributes for an HTTP request received by a -// server. -// -// The server must be the primary server name if it is known. For example this -// would be the ServerName directive -// (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache -// server, and the server_name directive -// (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an -// nginx server. More generically, the primary server name would be the host -// header value that matches the default virtual host of an HTTP server. It -// should include the host identifier and if a port is used to route to the -// server that port identifier should be included as an appropriate port -// suffix. -// -// If the primary server name is not known, server should be an empty string. -// The req Host will be used to determine the server instead. -// -// The following attributes are always returned: "http.method", "http.scheme", -// "http.target", "net.host.name". The following attributes are returned if -// they related values are defined in req: "net.host.port", "net.sock.peer.addr", -// "net.sock.peer.port", "user_agent.original", "http.client_ip". -func HTTPServerRequest(server string, req *http.Request, opts HTTPServerRequestOptions, attrs []attribute.KeyValue) []attribute.KeyValue { - return hc.ServerRequest(server, req, opts, attrs) -} - -// HTTPServerRequestMetrics returns metric attributes for an HTTP request received by a -// server. -// -// The server must be the primary server name if it is known. For example this -// would be the ServerName directive -// (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache -// server, and the server_name directive -// (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an -// nginx server. More generically, the primary server name would be the host -// header value that matches the default virtual host of an HTTP server. It -// should include the host identifier and if a port is used to route to the -// server that port identifier should be included as an appropriate port -// suffix. -// -// If the primary server name is not known, server should be an empty string. -// The req Host will be used to determine the server instead. -// -// The following attributes are always returned: "http.method", "http.scheme", -// "net.host.name". The following attributes are returned if they related -// values are defined in req: "net.host.port". -func HTTPServerRequestMetrics(server string, req *http.Request) []attribute.KeyValue { - return hc.ServerRequestMetrics(server, req) -} - -// HTTPServerStatus returns a span status code and message for an HTTP status code -// value returned by a server. Status codes in the 400-499 range are not -// returned as errors. -func HTTPServerStatus(code int) (codes.Code, string) { - return hc.ServerStatus(code) -} - -// httpConv are the HTTP semantic convention attributes defined for a version -// of the OpenTelemetry specification. -type httpConv struct { - NetConv *netConv - - HTTPClientIPKey attribute.Key - HTTPMethodKey attribute.Key - HTTPRequestContentLengthKey attribute.Key - HTTPResponseContentLengthKey attribute.Key - HTTPRouteKey attribute.Key - HTTPSchemeHTTP attribute.KeyValue - HTTPSchemeHTTPS attribute.KeyValue - HTTPStatusCodeKey attribute.Key - HTTPTargetKey attribute.Key - HTTPURLKey attribute.Key - UserAgentOriginalKey attribute.Key -} - -var hc = &httpConv{ - NetConv: nc, - - HTTPClientIPKey: semconv.HTTPClientIPKey, - HTTPMethodKey: semconv.HTTPMethodKey, - HTTPRequestContentLengthKey: semconv.HTTPRequestContentLengthKey, - HTTPResponseContentLengthKey: semconv.HTTPResponseContentLengthKey, - HTTPRouteKey: semconv.HTTPRouteKey, - HTTPSchemeHTTP: semconv.HTTPSchemeHTTP, - HTTPSchemeHTTPS: semconv.HTTPSchemeHTTPS, - HTTPStatusCodeKey: semconv.HTTPStatusCodeKey, - HTTPTargetKey: semconv.HTTPTargetKey, - HTTPURLKey: semconv.HTTPURLKey, - UserAgentOriginalKey: semconv.UserAgentOriginalKey, -} - -// ClientResponse returns attributes for an HTTP response received by a client -// from a server. The following attributes are returned if the related values -// are defined in resp: "http.status.code", "http.response_content_length". -// -// This does not add all OpenTelemetry required attributes for an HTTP event, -// it assumes ClientRequest was used to create the span with a complete set of -// attributes. If a complete set of attributes can be generated using the -// request contained in resp. For example: -// -// ClientResponse(resp, ClientRequest(resp.Request)) -func (c *httpConv) ClientResponse(resp *http.Response, attrs []attribute.KeyValue) []attribute.KeyValue { - /* The following semantic conventions are returned if present: - http.status_code int - http.response_content_length int - */ - var n int - if resp.StatusCode > 0 { - n++ - } - if resp.ContentLength > 0 { - n++ - } - if n == 0 { - return attrs - } - - attrs = slices.Grow(attrs, n) - if resp.StatusCode > 0 { - attrs = append(attrs, c.HTTPStatusCodeKey.Int(resp.StatusCode)) - } - if resp.ContentLength > 0 { - attrs = append(attrs, c.HTTPResponseContentLengthKey.Int(int(resp.ContentLength))) - } - return attrs -} - -// ClientRequest returns attributes for an HTTP request made by a client. The -// following attributes are always returned: "http.url", "http.method", -// "net.peer.name". The following attributes are returned if the related values -// are defined in req: "net.peer.port", "user_agent.original", -// "http.request_content_length", "user_agent.original". -func (c *httpConv) ClientRequest(req *http.Request, attrs []attribute.KeyValue) []attribute.KeyValue { - /* The following semantic conventions are returned if present: - http.method string - user_agent.original string - http.url string - net.peer.name string - net.peer.port int - http.request_content_length int - */ - - /* The following semantic conventions are not returned: - http.status_code This requires the response. See ClientResponse. - http.response_content_length This requires the response. See ClientResponse. - net.sock.family This requires the socket used. - net.sock.peer.addr This requires the socket used. - net.sock.peer.name This requires the socket used. - net.sock.peer.port This requires the socket used. - http.resend_count This is something outside of a single request. - net.protocol.name The value is the Request is ignored, and the go client will always use "http". - net.protocol.version The value in the Request is ignored, and the go client will always use 1.1 or 2.0. - */ - n := 3 // URL, peer name, proto, and method. - var h string - if req.URL != nil { - h = req.URL.Host - } - peer, p := firstHostPort(h, req.Header.Get("Host")) - port := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", p) - if port > 0 { - n++ - } - useragent := req.UserAgent() - if useragent != "" { - n++ - } - if req.ContentLength > 0 { - n++ - } - - attrs = slices.Grow(attrs, n) - attrs = append(attrs, c.method(req.Method)) - - var u string - if req.URL != nil { - // Remove any username/password info that may be in the URL. - userinfo := req.URL.User - req.URL.User = nil - u = req.URL.String() - // Restore any username/password info that was removed. - req.URL.User = userinfo - } - attrs = append(attrs, c.HTTPURLKey.String(u)) - - attrs = append(attrs, c.NetConv.PeerName(peer)) - if port > 0 { - attrs = append(attrs, c.NetConv.PeerPort(port)) - } - - if useragent != "" { - attrs = append(attrs, c.UserAgentOriginalKey.String(useragent)) - } - - if l := req.ContentLength; l > 0 { - attrs = append(attrs, c.HTTPRequestContentLengthKey.Int64(l)) - } - - return attrs -} - -// ClientRequestMetrics returns metric attributes for an HTTP request made by a client. The -// following attributes are always returned: "http.method", "net.peer.name". -// The following attributes are returned if the related values -// are defined in req: "net.peer.port". -func (c *httpConv) ClientRequestMetrics(req *http.Request) []attribute.KeyValue { - /* The following semantic conventions are returned if present: - http.method string - net.peer.name string - net.peer.port int - */ - - n := 2 // method, peer name. - var h string - if req.URL != nil { - h = req.URL.Host - } - peer, p := firstHostPort(h, req.Header.Get("Host")) - port := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", p) - if port > 0 { - n++ - } - - attrs := make([]attribute.KeyValue, 0, n) - attrs = append(attrs, c.method(req.Method), c.NetConv.PeerName(peer)) - - if port > 0 { - attrs = append(attrs, c.NetConv.PeerPort(port)) - } - - return attrs -} - -// ServerRequest returns attributes for an HTTP request received by a server. -// -// The server must be the primary server name if it is known. For example this -// would be the ServerName directive -// (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache -// server, and the server_name directive -// (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an -// nginx server. More generically, the primary server name would be the host -// header value that matches the default virtual host of an HTTP server. It -// should include the host identifier and if a port is used to route to the -// server that port identifier should be included as an appropriate port -// suffix. -// -// If the primary server name is not known, server should be an empty string. -// The req Host will be used to determine the server instead. -// -// The following attributes are always returned: "http.method", "http.scheme", -// "http.target", "net.host.name". The following attributes are returned if they -// related values are defined in req: "net.host.port", "net.sock.peer.addr", -// "net.sock.peer.port", "user_agent.original", "http.client_ip", -// "net.protocol.name", "net.protocol.version". -func (c *httpConv) ServerRequest(server string, req *http.Request, opts HTTPServerRequestOptions, attrs []attribute.KeyValue) []attribute.KeyValue { - /* The following semantic conventions are returned if present: - http.method string - http.scheme string - net.host.name string - net.host.port int - net.sock.peer.addr string - net.sock.peer.port int - user_agent.original string - http.client_ip string - net.protocol.name string Note: not set if the value is "http". - net.protocol.version string - http.target string Note: doesn't include the query parameter. - */ - - /* The following semantic conventions are not returned: - http.status_code This requires the response. - http.request_content_length This requires the len() of body, which can mutate it. - http.response_content_length This requires the response. - http.route This is not available. - net.sock.peer.name This would require a DNS lookup. - net.sock.host.addr The request doesn't have access to the underlying socket. - net.sock.host.port The request doesn't have access to the underlying socket. - - */ - n := 4 // Method, scheme, proto, and host name. - 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 { - n++ - } - peer, peerPort := splitHostPort(req.RemoteAddr) - if peer != "" { - n++ - if peerPort > 0 { - n++ - } - } - useragent := req.UserAgent() - if useragent != "" { - n++ - } - - // For client IP, use, in order: - // 1. The value passed in the options - // 2. The value in the X-Forwarded-For header - // 3. The peer address - clientIP := opts.HTTPClientIP - if clientIP == "" { - clientIP = serverClientIP(req.Header.Get("X-Forwarded-For")) - if clientIP == "" { - clientIP = peer - } - } - if clientIP != "" { - n++ - } - - var target string - if req.URL != nil { - target = req.URL.Path - if target != "" { - n++ - } - } - protoName, protoVersion := netProtocol(req.Proto) - if protoName != "" && protoName != "http" { - n++ - } - if protoVersion != "" { - n++ - } - - attrs = slices.Grow(attrs, n) - - attrs = append(attrs, c.method(req.Method)) - attrs = append(attrs, c.scheme(req.TLS != nil)) - attrs = append(attrs, c.NetConv.HostName(host)) - - if hostPort > 0 { - attrs = append(attrs, c.NetConv.HostPort(hostPort)) - } - - if peer != "" { - // The Go HTTP server sets RemoteAddr to "IP:port", this will not be a - // file-path that would be interpreted with a sock family. - attrs = append(attrs, c.NetConv.SockPeerAddr(peer)) - if peerPort > 0 { - attrs = append(attrs, c.NetConv.SockPeerPort(peerPort)) - } - } - - if useragent != "" { - attrs = append(attrs, c.UserAgentOriginalKey.String(useragent)) - } - - if clientIP != "" { - attrs = append(attrs, c.HTTPClientIPKey.String(clientIP)) - } - - if target != "" { - attrs = append(attrs, c.HTTPTargetKey.String(target)) - } - - if protoName != "" && protoName != "http" { - attrs = append(attrs, c.NetConv.NetProtocolName.String(protoName)) - } - if protoVersion != "" { - attrs = append(attrs, c.NetConv.NetProtocolVersion.String(protoVersion)) - } - - return attrs -} - -// ServerRequestMetrics returns metric attributes for an HTTP request received -// by a server. -// -// The server must be the primary server name if it is known. For example this -// would be the ServerName directive -// (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache -// server, and the server_name directive -// (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an -// nginx server. More generically, the primary server name would be the host -// header value that matches the default virtual host of an HTTP server. It -// should include the host identifier and if a port is used to route to the -// server that port identifier should be included as an appropriate port -// suffix. -// -// If the primary server name is not known, server should be an empty string. -// The req Host will be used to determine the server instead. -// -// The following attributes are always returned: "http.method", "http.scheme", -// "net.host.name". The following attributes are returned if they related -// values are defined in req: "net.host.port". -func (c *httpConv) ServerRequestMetrics(server string, req *http.Request) []attribute.KeyValue { - /* The following semantic conventions are returned if present: - http.scheme string - http.route string - http.method string - http.status_code int - net.host.name string - net.host.port int - net.protocol.name string Note: not set if the value is "http". - net.protocol.version string - */ - - n := 3 // Method, scheme, and host name. - 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 { - n++ - } - protoName, protoVersion := netProtocol(req.Proto) - if protoName != "" { - n++ - } - if protoVersion != "" { - n++ - } - - attrs := make([]attribute.KeyValue, 0, n) - - attrs = append(attrs, c.methodMetric(req.Method)) - attrs = append(attrs, c.scheme(req.TLS != nil)) - attrs = append(attrs, c.NetConv.HostName(host)) - - if hostPort > 0 { - attrs = append(attrs, c.NetConv.HostPort(hostPort)) - } - if protoName != "" { - attrs = append(attrs, c.NetConv.NetProtocolName.String(protoName)) - } - if protoVersion != "" { - attrs = append(attrs, c.NetConv.NetProtocolVersion.String(protoVersion)) - } - - return attrs -} - -func (c *httpConv) method(method string) attribute.KeyValue { - if method == "" { - return c.HTTPMethodKey.String(http.MethodGet) - } - return c.HTTPMethodKey.String(method) -} - -func (c *httpConv) methodMetric(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 c.HTTPMethodKey.String(method) -} - -func (c *httpConv) scheme(https bool) attribute.KeyValue { // nolint:revive - if https { - return c.HTTPSchemeHTTPS - } - return c.HTTPSchemeHTTP -} - -func serverClientIP(xForwardedFor string) string { - if idx := strings.Index(xForwardedFor, ","); idx >= 0 { - xForwardedFor = xForwardedFor[:idx] - } - return xForwardedFor -} - -func requiredHTTPPort(https bool, port int) int { // nolint:revive - if https { - if port > 0 && port != 443 { - return port - } - } else { - if port > 0 && port != 80 { - return port - } - } - return -1 -} - -// Return the request host and port from the first non-empty source. -func firstHostPort(source ...string) (host string, port int) { - for _, hostport := range source { - host, port = splitHostPort(hostport) - if host != "" || port > 0 { - break - } - } - return -} - -// ClientStatus returns a span status code and message for an HTTP status code -// value received by a client. -func (c *httpConv) ClientStatus(code int) (codes.Code, string) { - if code < 100 || code >= 600 { - return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code) - } - if code >= 400 { - return codes.Error, "" - } - return codes.Unset, "" -} - -// ServerStatus returns a span status code and message for an HTTP status code -// value returned by a server. Status codes in the 400-499 range are not -// returned as errors. -func (c *httpConv) ServerStatus(code int) (codes.Code, string) { - if code < 100 || code >= 600 { - return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code) - } - if code >= 500 { - return codes.Error, "" - } - return codes.Unset, "" -} diff --git a/internal/shared/semconvutil/httpconv_test.go.tmpl b/internal/shared/semconvutil/httpconv_test.go.tmpl deleted file mode 100644 index ce3a4eb3319..00000000000 --- a/internal/shared/semconvutil/httpconv_test.go.tmpl +++ /dev/null @@ -1,561 +0,0 @@ -// Code generated by gotmpl. DO NOT MODIFY. -// source: internal/shared/semconvutil/httpconv_test.go.tmpl - -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -package semconvutil - -import ( - "net/http" - "net/http/httptest" - "net/url" - "strconv" - "testing" - "time" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/codes" -) - -func TestHTTPClientResponse(t *testing.T) { - const stat, n = 201, 397 - resp := &http.Response{ - StatusCode: stat, - ContentLength: n, - } - got := HTTPClientResponse(resp, nil) - assert.Equal(t, 2, cap(got), "slice capacity") - assert.ElementsMatch(t, []attribute.KeyValue{ - attribute.Key("http.status_code").Int(stat), - attribute.Key("http.response_content_length").Int(n), - }, got) -} - -func TestHTTPSClientRequest(t *testing.T) { - req := &http.Request{ - Method: http.MethodGet, - URL: &url.URL{ - Scheme: "https", - Host: "127.0.0.1:443", - Path: "/resource", - }, - Proto: "HTTP/1.0", - ProtoMajor: 1, - ProtoMinor: 0, - } - - assert.ElementsMatch( - t, - []attribute.KeyValue{ - attribute.String("http.method", "GET"), - attribute.String("http.url", "https://127.0.0.1:443/resource"), - attribute.String("net.peer.name", "127.0.0.1"), - }, - HTTPClientRequest(req, nil), - ) -} - -func TestHTTPSClientRequestMetrics(t *testing.T) { - req := &http.Request{ - Method: http.MethodGet, - URL: &url.URL{ - Scheme: "https", - Host: "127.0.0.1:443", - Path: "/resource", - }, - Proto: "HTTP/1.0", - ProtoMajor: 1, - ProtoMinor: 0, - } - - assert.ElementsMatch( - t, - []attribute.KeyValue{ - attribute.String("http.method", "GET"), - attribute.String("net.peer.name", "127.0.0.1"), - }, - HTTPClientRequestMetrics(req), - ) -} - -func TestHTTPClientRequest(t *testing.T) { - const ( - user = "alice" - n = 128 - agent = "Go-http-client/1.1" - ) - req := &http.Request{ - Method: http.MethodGet, - URL: &url.URL{ - Scheme: "http", - Host: "127.0.0.1:8080", - Path: "/resource", - }, - Proto: "HTTP/1.0", - ProtoMajor: 1, - ProtoMinor: 0, - Header: http.Header{ - "User-Agent": []string{agent}, - }, - ContentLength: n, - } - req.SetBasicAuth(user, "pswrd") - - assert.ElementsMatch( - t, - []attribute.KeyValue{ - attribute.String("http.method", "GET"), - attribute.String("http.url", "http://127.0.0.1:8080/resource"), - attribute.String("net.peer.name", "127.0.0.1"), - attribute.Int("net.peer.port", 8080), - attribute.String("user_agent.original", agent), - attribute.Int("http.request_content_length", n), - }, - HTTPClientRequest(req, nil), - ) -} - -func TestHTTPClientRequestMetrics(t *testing.T) { - const ( - user = "alice" - n = 128 - agent = "Go-http-client/1.1" - ) - req := &http.Request{ - Method: http.MethodGet, - URL: &url.URL{ - Scheme: "http", - Host: "127.0.0.1:8080", - Path: "/resource", - }, - Proto: "HTTP/1.0", - ProtoMajor: 1, - ProtoMinor: 0, - Header: http.Header{ - "User-Agent": []string{agent}, - }, - ContentLength: n, - } - req.SetBasicAuth(user, "pswrd") - - assert.ElementsMatch( - t, - []attribute.KeyValue{ - attribute.String("http.method", "GET"), - attribute.String("net.peer.name", "127.0.0.1"), - attribute.Int("net.peer.port", 8080), - }, - HTTPClientRequestMetrics(req), - ) -} - -func TestHTTPClientRequestRequired(t *testing.T) { - req := new(http.Request) - var got []attribute.KeyValue - assert.NotPanics(t, func() { got = HTTPClientRequest(req, nil) }) - want := []attribute.KeyValue{ - attribute.String("http.method", "GET"), - attribute.String("http.url", ""), - attribute.String("net.peer.name", ""), - } - assert.Equal(t, want, got) -} - -func TestHTTPServerRequest(t *testing.T) { - for _, tt := range []struct { - name string - requestModifierFn func(r *http.Request) - httpServerRequestOpts HTTPServerRequestOptions - - wantClientIP string - }{ - { - name: "with a client IP from the network", - wantClientIP: "1.2.3.4", - }, - { - name: "with a client IP from x-forwarded-for header", - requestModifierFn: func(r *http.Request) { - r.Header.Add("X-Forwarded-For", "5.6.7.8") - }, - wantClientIP: "5.6.7.8", - }, - { - name: "with a client IP in options", - requestModifierFn: func(r *http.Request) { - r.Header.Add("X-Forwarded-For", "5.6.7.8") - }, - httpServerRequestOpts: HTTPServerRequestOptions{ - HTTPClientIP: "9.8.7.6", - }, - wantClientIP: "9.8.7.6", - }, - } { - t.Run(tt.name, func(t *testing.T) { - reqCh := make(chan *http.Request, 1) - handler := func(w http.ResponseWriter, r *http.Request) { - r.RemoteAddr = "1.2.3.4:5678" - reqCh <- r - w.WriteHeader(http.StatusOK) - } - - srv := httptest.NewServer(http.HandlerFunc(handler)) - defer srv.Close() - - srvURL, err := url.Parse(srv.URL) - require.NoError(t, err) - srvPort, err := strconv.ParseInt(srvURL.Port(), 10, 32) - require.NoError(t, err) - - req, err := http.NewRequest(http.MethodGet, srv.URL, nil) - require.NoError(t, err) - - if tt.requestModifierFn != nil { - tt.requestModifierFn(req) - } - - resp, err := srv.Client().Do(req) - require.NoError(t, err) - require.NoError(t, resp.Body.Close()) - - var got *http.Request - select { - case got = <-reqCh: - // All good - case <-time.After(5 * time.Second): - t.Fatal("Did not receive a signal in 5s") - } - - peer, peerPort := splitHostPort(got.RemoteAddr) - - const user = "alice" - got.SetBasicAuth(user, "pswrd") - - assert.ElementsMatch(t, - []attribute.KeyValue{ - attribute.String("http.method", "GET"), - attribute.String("http.scheme", "http"), - attribute.String("net.host.name", srvURL.Hostname()), - attribute.Int("net.host.port", int(srvPort)), - attribute.String("net.sock.peer.addr", peer), - attribute.Int("net.sock.peer.port", peerPort), - attribute.String("user_agent.original", "Go-http-client/1.1"), - attribute.String("http.client_ip", tt.wantClientIP), - attribute.String("net.protocol.version", "1.1"), - attribute.String("http.target", "/"), - }, - HTTPServerRequest("", got, tt.httpServerRequestOpts, nil)) - }) - } -} - -func TestHTTPServerRequestMetrics(t *testing.T) { - got := make(chan *http.Request, 1) - handler := func(w http.ResponseWriter, r *http.Request) { - got <- r - w.WriteHeader(http.StatusOK) - } - - srv := httptest.NewServer(http.HandlerFunc(handler)) - defer srv.Close() - - srvURL, err := url.Parse(srv.URL) - require.NoError(t, err) - srvPort, err := strconv.ParseInt(srvURL.Port(), 10, 32) - require.NoError(t, err) - - resp, err := srv.Client().Get(srv.URL) - require.NoError(t, err) - require.NoError(t, resp.Body.Close()) - - var req *http.Request - select { - case req = <-got: - // All good - case <-time.After(5 * time.Second): - t.Fatal("did not receive a signal in 5s") - } - - assert.ElementsMatch(t, - []attribute.KeyValue{ - attribute.String("http.method", "GET"), - attribute.String("http.scheme", "http"), - attribute.String("net.host.name", srvURL.Hostname()), - attribute.Int("net.host.port", int(srvPort)), - attribute.String("net.protocol.name", "http"), - attribute.String("net.protocol.version", "1.1"), - }, - HTTPServerRequestMetrics("", req)) -} - -func TestHTTPServerName(t *testing.T) { - req := new(http.Request) - var got []attribute.KeyValue - const ( - host = "test.semconv.server" - port = 8080 - ) - portStr := strconv.Itoa(port) - server := host + ":" + portStr - assert.NotPanics(t, func() { got = HTTPServerRequest(server, req, HTTPServerRequestOptions{}, nil) }) - assert.Contains(t, got, attribute.String("net.host.name", host)) - assert.Contains(t, got, attribute.Int("net.host.port", port)) - - req = &http.Request{Host: "alt.host.name:" + portStr} - // The server parameter does not include a port, ServerRequest should use - // the port in the request Host field. - assert.NotPanics(t, func() { got = HTTPServerRequest(host, req, HTTPServerRequestOptions{}, nil) }) - assert.Contains(t, got, attribute.String("net.host.name", host)) - assert.Contains(t, got, attribute.Int("net.host.port", port)) -} - -func TestHTTPServerRequestFailsGracefully(t *testing.T) { - req := new(http.Request) - var got []attribute.KeyValue - assert.NotPanics(t, func() { got = HTTPServerRequest("", req, HTTPServerRequestOptions{}, nil) }) - want := []attribute.KeyValue{ - attribute.String("http.method", "GET"), - attribute.String("http.scheme", "http"), - attribute.String("net.host.name", ""), - } - assert.ElementsMatch(t, want, got) -} - -func TestHTTPMethod(t *testing.T) { - assert.Equal(t, attribute.String("http.method", "POST"), hc.method("POST")) - assert.Equal(t, attribute.String("http.method", "GET"), hc.method("")) - assert.Equal(t, attribute.String("http.method", "garbage"), hc.method("garbage")) -} - -func TestHTTPScheme(t *testing.T) { - assert.Equal(t, attribute.String("http.scheme", "http"), hc.scheme(false)) - assert.Equal(t, attribute.String("http.scheme", "https"), hc.scheme(true)) -} - -func TestHTTPServerClientIP(t *testing.T) { - tests := []struct { - xForwardedFor string - want string - }{ - {"", ""}, - {"127.0.0.1", "127.0.0.1"}, - {"127.0.0.1,127.0.0.5", "127.0.0.1"}, - } - for _, test := range tests { - got := serverClientIP(test.xForwardedFor) - assert.Equal(t, test.want, got, test.xForwardedFor) - } -} - -func TestRequiredHTTPPort(t *testing.T) { - tests := []struct { - https bool - port int - want int - }{ - {true, 443, -1}, - {true, 80, 80}, - {true, 8081, 8081}, - {false, 443, 443}, - {false, 80, -1}, - {false, 8080, 8080}, - } - for _, test := range tests { - got := requiredHTTPPort(test.https, test.port) - assert.Equalf(t, test.want, got, "HTTPS: %t, Port: %d", test.https, test.port) - } -} - -func TestFirstHostPort(t *testing.T) { - host, port := "127.0.0.1", 8080 - hostport := "127.0.0.1:8080" - sources := [][]string{ - {hostport}, - {"", hostport}, - {"", "", hostport}, - {"", "", hostport, ""}, - {"", "", hostport, "127.0.0.3:80"}, - } - - for _, src := range sources { - h, p := firstHostPort(src...) - assert.Equal(t, host, h, "%+v", src) - assert.Equal(t, port, p, "%+v", src) - } -} - -func TestHTTPClientStatus(t *testing.T) { - tests := []struct { - code int - stat codes.Code - msg bool - }{ - {0, codes.Error, true}, - {http.StatusContinue, codes.Unset, false}, - {http.StatusSwitchingProtocols, codes.Unset, false}, - {http.StatusProcessing, codes.Unset, false}, - {http.StatusEarlyHints, codes.Unset, false}, - {http.StatusOK, codes.Unset, false}, - {http.StatusCreated, codes.Unset, false}, - {http.StatusAccepted, codes.Unset, false}, - {http.StatusNonAuthoritativeInfo, codes.Unset, false}, - {http.StatusNoContent, codes.Unset, false}, - {http.StatusResetContent, codes.Unset, false}, - {http.StatusPartialContent, codes.Unset, false}, - {http.StatusMultiStatus, codes.Unset, false}, - {http.StatusAlreadyReported, codes.Unset, false}, - {http.StatusIMUsed, codes.Unset, false}, - {http.StatusMultipleChoices, codes.Unset, false}, - {http.StatusMovedPermanently, codes.Unset, false}, - {http.StatusFound, codes.Unset, false}, - {http.StatusSeeOther, codes.Unset, false}, - {http.StatusNotModified, codes.Unset, false}, - {http.StatusUseProxy, codes.Unset, false}, - {306, codes.Unset, false}, - {http.StatusTemporaryRedirect, codes.Unset, false}, - {http.StatusPermanentRedirect, codes.Unset, false}, - {http.StatusBadRequest, codes.Error, false}, - {http.StatusUnauthorized, codes.Error, false}, - {http.StatusPaymentRequired, codes.Error, false}, - {http.StatusForbidden, codes.Error, false}, - {http.StatusNotFound, codes.Error, false}, - {http.StatusMethodNotAllowed, codes.Error, false}, - {http.StatusNotAcceptable, codes.Error, false}, - {http.StatusProxyAuthRequired, codes.Error, false}, - {http.StatusRequestTimeout, codes.Error, false}, - {http.StatusConflict, codes.Error, false}, - {http.StatusGone, codes.Error, false}, - {http.StatusLengthRequired, codes.Error, false}, - {http.StatusPreconditionFailed, codes.Error, false}, - {http.StatusRequestEntityTooLarge, codes.Error, false}, - {http.StatusRequestURITooLong, codes.Error, false}, - {http.StatusUnsupportedMediaType, codes.Error, false}, - {http.StatusRequestedRangeNotSatisfiable, codes.Error, false}, - {http.StatusExpectationFailed, codes.Error, false}, - {http.StatusTeapot, codes.Error, false}, - {http.StatusMisdirectedRequest, codes.Error, false}, - {http.StatusUnprocessableEntity, codes.Error, false}, - {http.StatusLocked, codes.Error, false}, - {http.StatusFailedDependency, codes.Error, false}, - {http.StatusTooEarly, codes.Error, false}, - {http.StatusUpgradeRequired, codes.Error, false}, - {http.StatusPreconditionRequired, codes.Error, false}, - {http.StatusTooManyRequests, codes.Error, false}, - {http.StatusRequestHeaderFieldsTooLarge, codes.Error, false}, - {http.StatusUnavailableForLegalReasons, codes.Error, false}, - {499, codes.Error, false}, - {http.StatusInternalServerError, codes.Error, false}, - {http.StatusNotImplemented, codes.Error, false}, - {http.StatusBadGateway, codes.Error, false}, - {http.StatusServiceUnavailable, codes.Error, false}, - {http.StatusGatewayTimeout, codes.Error, false}, - {http.StatusHTTPVersionNotSupported, codes.Error, false}, - {http.StatusVariantAlsoNegotiates, codes.Error, false}, - {http.StatusInsufficientStorage, codes.Error, false}, - {http.StatusLoopDetected, codes.Error, false}, - {http.StatusNotExtended, codes.Error, false}, - {http.StatusNetworkAuthenticationRequired, codes.Error, false}, - {600, codes.Error, true}, - } - - for _, test := range tests { - t.Run(strconv.Itoa(test.code), func(t *testing.T) { - c, msg := HTTPClientStatus(test.code) - assert.Equal(t, test.stat, c) - if test.msg && msg == "" { - t.Errorf("expected non-empty message for %d", test.code) - } else if !test.msg && msg != "" { - t.Errorf("expected empty message for %d, got: %s", test.code, msg) - } - }) - } -} - -func TestHTTPServerStatus(t *testing.T) { - tests := []struct { - code int - stat codes.Code - msg bool - }{ - {0, codes.Error, true}, - {http.StatusContinue, codes.Unset, false}, - {http.StatusSwitchingProtocols, codes.Unset, false}, - {http.StatusProcessing, codes.Unset, false}, - {http.StatusEarlyHints, codes.Unset, false}, - {http.StatusOK, codes.Unset, false}, - {http.StatusCreated, codes.Unset, false}, - {http.StatusAccepted, codes.Unset, false}, - {http.StatusNonAuthoritativeInfo, codes.Unset, false}, - {http.StatusNoContent, codes.Unset, false}, - {http.StatusResetContent, codes.Unset, false}, - {http.StatusPartialContent, codes.Unset, false}, - {http.StatusMultiStatus, codes.Unset, false}, - {http.StatusAlreadyReported, codes.Unset, false}, - {http.StatusIMUsed, codes.Unset, false}, - {http.StatusMultipleChoices, codes.Unset, false}, - {http.StatusMovedPermanently, codes.Unset, false}, - {http.StatusFound, codes.Unset, false}, - {http.StatusSeeOther, codes.Unset, false}, - {http.StatusNotModified, codes.Unset, false}, - {http.StatusUseProxy, codes.Unset, false}, - {306, codes.Unset, false}, - {http.StatusTemporaryRedirect, codes.Unset, false}, - {http.StatusPermanentRedirect, codes.Unset, false}, - {http.StatusBadRequest, codes.Unset, false}, - {http.StatusUnauthorized, codes.Unset, false}, - {http.StatusPaymentRequired, codes.Unset, false}, - {http.StatusForbidden, codes.Unset, false}, - {http.StatusNotFound, codes.Unset, false}, - {http.StatusMethodNotAllowed, codes.Unset, false}, - {http.StatusNotAcceptable, codes.Unset, false}, - {http.StatusProxyAuthRequired, codes.Unset, false}, - {http.StatusRequestTimeout, codes.Unset, false}, - {http.StatusConflict, codes.Unset, false}, - {http.StatusGone, codes.Unset, false}, - {http.StatusLengthRequired, codes.Unset, false}, - {http.StatusPreconditionFailed, codes.Unset, false}, - {http.StatusRequestEntityTooLarge, codes.Unset, false}, - {http.StatusRequestURITooLong, codes.Unset, false}, - {http.StatusUnsupportedMediaType, codes.Unset, false}, - {http.StatusRequestedRangeNotSatisfiable, codes.Unset, false}, - {http.StatusExpectationFailed, codes.Unset, false}, - {http.StatusTeapot, codes.Unset, false}, - {http.StatusMisdirectedRequest, codes.Unset, false}, - {http.StatusUnprocessableEntity, codes.Unset, false}, - {http.StatusLocked, codes.Unset, false}, - {http.StatusFailedDependency, codes.Unset, false}, - {http.StatusTooEarly, codes.Unset, false}, - {http.StatusUpgradeRequired, codes.Unset, false}, - {http.StatusPreconditionRequired, codes.Unset, false}, - {http.StatusTooManyRequests, codes.Unset, false}, - {http.StatusRequestHeaderFieldsTooLarge, codes.Unset, false}, - {http.StatusUnavailableForLegalReasons, codes.Unset, false}, - {499, codes.Unset, false}, - {http.StatusInternalServerError, codes.Error, false}, - {http.StatusNotImplemented, codes.Error, false}, - {http.StatusBadGateway, codes.Error, false}, - {http.StatusServiceUnavailable, codes.Error, false}, - {http.StatusGatewayTimeout, codes.Error, false}, - {http.StatusHTTPVersionNotSupported, codes.Error, false}, - {http.StatusVariantAlsoNegotiates, codes.Error, false}, - {http.StatusInsufficientStorage, codes.Error, false}, - {http.StatusLoopDetected, codes.Error, false}, - {http.StatusNotExtended, codes.Error, false}, - {http.StatusNetworkAuthenticationRequired, codes.Error, false}, - {600, codes.Error, true}, - } - - for _, test := range tests { - c, msg := HTTPServerStatus(test.code) - assert.Equal(t, test.stat, c) - if test.msg && msg == "" { - t.Errorf("expected non-empty message for %d", test.code) - } else if !test.msg && msg != "" { - t.Errorf("expected empty message for %d, got: %s", test.code, msg) - } - } -} diff --git a/internal/shared/semconvutil/netconv.go.tmpl b/internal/shared/semconvutil/netconv.go.tmpl deleted file mode 100644 index 0fc495cb352..00000000000 --- a/internal/shared/semconvutil/netconv.go.tmpl +++ /dev/null @@ -1,214 +0,0 @@ -// Code generated by gotmpl. DO NOT MODIFY. -// source: internal/shared/semconvutil/netconv.go.tmpl - -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -package semconvutil // import "go.opentelemetry.io/contrib/internal/shared/semconvutil" - -import ( - "net" - "strconv" - "strings" - - "go.opentelemetry.io/otel/attribute" - semconv "go.opentelemetry.io/otel/semconv/v1.20.0" -) - -// NetTransport returns a trace attribute describing the transport protocol of the -// passed network. See the net.Dial for information about acceptable network -// values. -func NetTransport(network string) attribute.KeyValue { - return nc.Transport(network) -} - -// netConv are the network semantic convention attributes defined for a version -// of the OpenTelemetry specification. -type netConv struct { - NetHostNameKey attribute.Key - NetHostPortKey attribute.Key - NetPeerNameKey attribute.Key - NetPeerPortKey attribute.Key - NetProtocolName attribute.Key - NetProtocolVersion attribute.Key - NetSockFamilyKey attribute.Key - NetSockPeerAddrKey attribute.Key - NetSockPeerPortKey attribute.Key - NetSockHostAddrKey attribute.Key - NetSockHostPortKey attribute.Key - NetTransportOther attribute.KeyValue - NetTransportTCP attribute.KeyValue - NetTransportUDP attribute.KeyValue - NetTransportInProc attribute.KeyValue -} - -var nc = &netConv{ - NetHostNameKey: semconv.NetHostNameKey, - NetHostPortKey: semconv.NetHostPortKey, - NetPeerNameKey: semconv.NetPeerNameKey, - NetPeerPortKey: semconv.NetPeerPortKey, - NetProtocolName: semconv.NetProtocolNameKey, - NetProtocolVersion: semconv.NetProtocolVersionKey, - NetSockFamilyKey: semconv.NetSockFamilyKey, - NetSockPeerAddrKey: semconv.NetSockPeerAddrKey, - NetSockPeerPortKey: semconv.NetSockPeerPortKey, - NetSockHostAddrKey: semconv.NetSockHostAddrKey, - NetSockHostPortKey: semconv.NetSockHostPortKey, - NetTransportOther: semconv.NetTransportOther, - NetTransportTCP: semconv.NetTransportTCP, - NetTransportUDP: semconv.NetTransportUDP, - NetTransportInProc: semconv.NetTransportInProc, -} - -func (c *netConv) Transport(network string) attribute.KeyValue { - switch network { - case "tcp", "tcp4", "tcp6": - return c.NetTransportTCP - case "udp", "udp4", "udp6": - return c.NetTransportUDP - case "unix", "unixgram", "unixpacket": - return c.NetTransportInProc - default: - // "ip:*", "ip4:*", and "ip6:*" all are considered other. - return c.NetTransportOther - } -} - -// Host returns attributes for a network host address. -func (c *netConv) Host(address string) []attribute.KeyValue { - h, p := splitHostPort(address) - var n int - if h != "" { - n++ - if p > 0 { - n++ - } - } - - if n == 0 { - return nil - } - - attrs := make([]attribute.KeyValue, 0, n) - attrs = append(attrs, c.HostName(h)) - if p > 0 { - attrs = append(attrs, c.HostPort(p)) - } - return attrs -} - -func (c *netConv) HostName(name string) attribute.KeyValue { - return c.NetHostNameKey.String(name) -} - -func (c *netConv) HostPort(port int) attribute.KeyValue { - return c.NetHostPortKey.Int(port) -} - -func family(network, address string) string { - switch network { - case "unix", "unixgram", "unixpacket": - return "unix" - default: - if ip := net.ParseIP(address); ip != nil { - if ip.To4() == nil { - return "inet6" - } - return "inet" - } - } - return "" -} - -// Peer returns attributes for a network peer address. -func (c *netConv) Peer(address string) []attribute.KeyValue { - h, p := splitHostPort(address) - var n int - if h != "" { - n++ - if p > 0 { - n++ - } - } - - if n == 0 { - return nil - } - - attrs := make([]attribute.KeyValue, 0, n) - attrs = append(attrs, c.PeerName(h)) - if p > 0 { - attrs = append(attrs, c.PeerPort(p)) - } - return attrs -} - -func (c *netConv) PeerName(name string) attribute.KeyValue { - return c.NetPeerNameKey.String(name) -} - -func (c *netConv) PeerPort(port int) attribute.KeyValue { - return c.NetPeerPortKey.Int(port) -} - -func (c *netConv) SockPeerAddr(addr string) attribute.KeyValue { - return c.NetSockPeerAddrKey.String(addr) -} - -func (c *netConv) SockPeerPort(port int) attribute.KeyValue { - return c.NetSockPeerPortKey.Int(port) -} - -// splitHostPort splits a network address hostport of the form "host", -// "host%zone", "[host]", "[host%zone], "host:port", "host%zone:port", -// "[host]:port", "[host%zone]:port", or ":port" into host or host%zone and -// port. -// -// An empty host is returned if it is not provided or unparsable. A negative -// port is returned if it is not provided or unparsable. -func splitHostPort(hostport string) (host string, port int) { - port = -1 - - if strings.HasPrefix(hostport, "[") { - addrEnd := strings.LastIndex(hostport, "]") - if addrEnd < 0 { - // Invalid hostport. - return - } - if i := strings.LastIndex(hostport[addrEnd:], ":"); i < 0 { - host = hostport[1:addrEnd] - return - } - } else { - if i := strings.LastIndex(hostport, ":"); i < 0 { - host = hostport - return - } - } - - host, pStr, err := net.SplitHostPort(hostport) - if err != nil { - return - } - - p, err := strconv.ParseUint(pStr, 10, 16) - if err != nil { - return - } - return host, int(p) // nolint: gosec // Bitsize checked to be 16 above. -} - -func netProtocol(proto string) (name string, version string) { - name, version, _ = strings.Cut(proto, "/") - switch name { - case "HTTP": - name = "http" - case "QUIC": - name = "quic" - case "SPDY": - name = "spdy" - default: - name = strings.ToLower(name) - } - return name, version -} diff --git a/internal/shared/semconvutil/netconv_test.go.tmpl b/internal/shared/semconvutil/netconv_test.go.tmpl deleted file mode 100644 index 4f3c1540c13..00000000000 --- a/internal/shared/semconvutil/netconv_test.go.tmpl +++ /dev/null @@ -1,200 +0,0 @@ -// Code generated by gotmpl. DO NOT MODIFY. -// source: internal/shared/semconvutil/netconv_test.go.tmpl - -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -package semconvutil - -import ( - "testing" - - "github.com/stretchr/testify/assert" - - "go.opentelemetry.io/otel/attribute" -) - -const ( - addr = "127.0.0.1" - port = 1834 -) - -func TestNetTransport(t *testing.T) { - transports := map[string]attribute.KeyValue{ - "tcp": attribute.String("net.transport", "ip_tcp"), - "tcp4": attribute.String("net.transport", "ip_tcp"), - "tcp6": attribute.String("net.transport", "ip_tcp"), - "udp": attribute.String("net.transport", "ip_udp"), - "udp4": attribute.String("net.transport", "ip_udp"), - "udp6": attribute.String("net.transport", "ip_udp"), - "unix": attribute.String("net.transport", "inproc"), - "unixgram": attribute.String("net.transport", "inproc"), - "unixpacket": attribute.String("net.transport", "inproc"), - "ip:1": attribute.String("net.transport", "other"), - "ip:icmp": attribute.String("net.transport", "other"), - "ip4:proto": attribute.String("net.transport", "other"), - "ip6:proto": attribute.String("net.transport", "other"), - } - - for network, want := range transports { - assert.Equal(t, want, NetTransport(network)) - } -} - -func TestNetHost(t *testing.T) { - testAddrs(t, []addrTest{ - {address: "", expected: nil}, - {address: "192.0.0.1", expected: []attribute.KeyValue{ - nc.HostName("192.0.0.1"), - }}, - {address: "192.0.0.1:9090", expected: []attribute.KeyValue{ - nc.HostName("192.0.0.1"), - nc.HostPort(9090), - }}, - }, nc.Host) -} - -func TestNetHostName(t *testing.T) { - expected := attribute.Key("net.host.name").String(addr) - assert.Equal(t, expected, nc.HostName(addr)) -} - -func TestNetHostPort(t *testing.T) { - expected := attribute.Key("net.host.port").Int(port) - assert.Equal(t, expected, nc.HostPort(port)) -} - -func TestNetPeer(t *testing.T) { - testAddrs(t, []addrTest{ - {address: "", expected: nil}, - {address: "example.com", expected: []attribute.KeyValue{ - nc.PeerName("example.com"), - }}, - {address: "/tmp/file", expected: []attribute.KeyValue{ - nc.PeerName("/tmp/file"), - }}, - {address: "192.0.0.1", expected: []attribute.KeyValue{ - nc.PeerName("192.0.0.1"), - }}, - {address: ":9090", expected: nil}, - {address: "192.0.0.1:9090", expected: []attribute.KeyValue{ - nc.PeerName("192.0.0.1"), - nc.PeerPort(9090), - }}, - }, nc.Peer) -} - -func TestNetPeerName(t *testing.T) { - expected := attribute.Key("net.peer.name").String(addr) - assert.Equal(t, expected, nc.PeerName(addr)) -} - -func TestNetPeerPort(t *testing.T) { - expected := attribute.Key("net.peer.port").Int(port) - assert.Equal(t, expected, nc.PeerPort(port)) -} - -func TestNetSockPeerName(t *testing.T) { - expected := attribute.Key("net.sock.peer.addr").String(addr) - assert.Equal(t, expected, nc.SockPeerAddr(addr)) -} - -func TestNetSockPeerPort(t *testing.T) { - expected := attribute.Key("net.sock.peer.port").Int(port) - assert.Equal(t, expected, nc.SockPeerPort(port)) -} - -func TestNetFamily(t *testing.T) { - tests := []struct { - network string - address string - expect string - }{ - {"", "", ""}, - {"unix", "", "unix"}, - {"unix", "gibberish", "unix"}, - {"unixgram", "", "unix"}, - {"unixgram", "gibberish", "unix"}, - {"unixpacket", "gibberish", "unix"}, - {"tcp", "123.0.2.8", "inet"}, - {"tcp", "gibberish", ""}, - {"", "123.0.2.8", "inet"}, - {"", "gibberish", ""}, - {"tcp", "fe80::1", "inet6"}, - {"", "fe80::1", "inet6"}, - } - - for _, test := range tests { - got := family(test.network, test.address) - assert.Equal(t, test.expect, got, test.network+"/"+test.address) - } -} - -func TestSplitHostPort(t *testing.T) { - tests := []struct { - hostport string - host string - port int - }{ - {"", "", -1}, - {":8080", "", 8080}, - {"127.0.0.1", "127.0.0.1", -1}, - {"www.example.com", "www.example.com", -1}, - {"127.0.0.1%25en0", "127.0.0.1%25en0", -1}, - {"[]", "", -1}, // Ensure this doesn't panic. - {"[fe80::1", "", -1}, - {"[fe80::1]", "fe80::1", -1}, - {"[fe80::1%25en0]", "fe80::1%25en0", -1}, - {"[fe80::1]:8080", "fe80::1", 8080}, - {"[fe80::1]::", "", -1}, // Too many colons. - {"127.0.0.1:", "127.0.0.1", -1}, - {"127.0.0.1:port", "127.0.0.1", -1}, - {"127.0.0.1:8080", "127.0.0.1", 8080}, - {"www.example.com:8080", "www.example.com", 8080}, - {"127.0.0.1%25en0:8080", "127.0.0.1%25en0", 8080}, - } - - for _, test := range tests { - h, p := splitHostPort(test.hostport) - assert.Equal(t, test.host, h, test.hostport) - assert.Equal(t, test.port, p, test.hostport) - } -} - -type addrTest struct { - address string - expected []attribute.KeyValue -} - -func testAddrs(t *testing.T, tests []addrTest, f func(string) []attribute.KeyValue) { - t.Helper() - - for _, test := range tests { - got := f(test.address) - assert.Equal(t, cap(test.expected), cap(got), "slice capacity") - assert.ElementsMatch(t, test.expected, got, test.address) - } -} - -func TestNetProtocol(t *testing.T) { - type testCase struct { - name, version string - } - tests := map[string]testCase{ - "HTTP/1.0": {name: "http", version: "1.0"}, - "HTTP/1.1": {name: "http", version: "1.1"}, - "HTTP/2": {name: "http", version: "2"}, - "HTTP/3": {name: "http", version: "3"}, - "SPDY": {name: "spdy"}, - "SPDY/2": {name: "spdy", version: "2"}, - "QUIC": {name: "quic"}, - "unknown/proto/2": {name: "unknown", version: "proto/2"}, - "other": {name: "other"}, - } - - for proto, want := range tests { - name, version := netProtocol(proto) - assert.Equal(t, want.name, name) - assert.Equal(t, want.version, version) - } -} From 036c44759829aa00baadf0301633ff4e004a3faf Mon Sep 17 00:00:00 2001 From: dmathieu <42@dmathieu.com> Date: Tue, 15 Jul 2025 13:54:34 +0200 Subject: [PATCH 2/7] use semconv package in otelecho --- .../github.com/labstack/echo/otelecho/echo.go | 13 +- .../otelecho/internal/semconv/bench_test.go | 48 ++ .../otelecho/internal/semconv/common_test.go | 71 +++ .../echo/otelecho/internal/semconv/env.go | 248 +++++++++ .../otelecho/internal/semconv/env_test.go | 197 +++++++ .../echo/otelecho/internal/semconv/gen.go | 15 + .../otelecho/internal/semconv/httpconv.go | 517 ++++++++++++++++++ .../internal/semconv/httpconv_test.go | 371 +++++++++++++ .../internal/semconv/httpconvtest_test.go | 456 +++++++++++++++ .../echo/otelecho/internal/semconv/util.go | 127 +++++ .../otelecho/internal/semconv/util_test.go | 75 +++ 11 files changed, 2133 insertions(+), 5 deletions(-) create mode 100644 instrumentation/github.com/labstack/echo/otelecho/internal/semconv/bench_test.go create mode 100644 instrumentation/github.com/labstack/echo/otelecho/internal/semconv/common_test.go create mode 100644 instrumentation/github.com/labstack/echo/otelecho/internal/semconv/env.go create mode 100644 instrumentation/github.com/labstack/echo/otelecho/internal/semconv/env_test.go create mode 100644 instrumentation/github.com/labstack/echo/otelecho/internal/semconv/gen.go create mode 100644 instrumentation/github.com/labstack/echo/otelecho/internal/semconv/httpconv.go create mode 100644 instrumentation/github.com/labstack/echo/otelecho/internal/semconv/httpconv_test.go create mode 100644 instrumentation/github.com/labstack/echo/otelecho/internal/semconv/httpconvtest_test.go create mode 100644 instrumentation/github.com/labstack/echo/otelecho/internal/semconv/util.go create mode 100644 instrumentation/github.com/labstack/echo/otelecho/internal/semconv/util_test.go diff --git a/instrumentation/github.com/labstack/echo/otelecho/echo.go b/instrumentation/github.com/labstack/echo/otelecho/echo.go index fbc9aca6c48..22fbe110393 100644 --- a/instrumentation/github.com/labstack/echo/otelecho/echo.go +++ b/instrumentation/github.com/labstack/echo/otelecho/echo.go @@ -14,10 +14,9 @@ import ( "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/propagation" - semconv "go.opentelemetry.io/otel/semconv/v1.20.0" oteltrace "go.opentelemetry.io/otel/trace" - "go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho/internal/semconvutil" + "go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho/internal/semconv" ) const ( @@ -47,6 +46,8 @@ func Middleware(service string, opts ...Option) echo.MiddlewareFunc { cfg.Skipper = middleware.DefaultSkipper } + semconvSrv := semconv.NewHTTPServer(nil) + return func(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { if cfg.Skipper(c) { @@ -62,11 +63,13 @@ func Middleware(service string, opts ...Option) echo.MiddlewareFunc { }() ctx := cfg.Propagators.Extract(savedCtx, propagation.HeaderCarrier(request.Header)) opts := []oteltrace.SpanStartOption{ - oteltrace.WithAttributes(semconvutil.HTTPServerRequest(service, request, semconvutil.HTTPServerRequestOptions{}, nil)...), + oteltrace.WithAttributes( + semconvSrv.RequestTraceAttrs(service, request, semconv.RequestTraceAttrsOpts{}...), + ), oteltrace.WithSpanKind(oteltrace.SpanKindServer), } if path := c.Path(); path != "" { - rAttr := semconv.HTTPRoute(path) + rAttr := semconvSrv.Route(path) opts = append(opts, oteltrace.WithAttributes(rAttr)) } spanName := spanNameFormatter(c) @@ -88,7 +91,7 @@ func Middleware(service string, opts ...Option) echo.MiddlewareFunc { status := c.Response().Status span.SetStatus(semconvutil.HTTPServerStatus(status)) if status > 0 { - span.SetAttributes(semconv.HTTPStatusCode(status)) + span.SetAttributes(semconvSrv.StatusCode(status)) } return err diff --git a/instrumentation/github.com/labstack/echo/otelecho/internal/semconv/bench_test.go b/instrumentation/github.com/labstack/echo/otelecho/internal/semconv/bench_test.go new file mode 100644 index 00000000000..e6bc25832bd --- /dev/null +++ b/instrumentation/github.com/labstack/echo/otelecho/internal/semconv/bench_test.go @@ -0,0 +1,48 @@ +// Code generated by gotmpl. DO NOT MODIFY. +// source: internal/shared/semconv/bench_test.go.tmpl + +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package semconv + +import ( + "net/http" + "net/url" + "testing" + + "go.opentelemetry.io/otel/attribute" +) + +var benchHTTPServerRequestResults []attribute.KeyValue + +// BenchmarkHTTPServerRequest allows comparison between different version of the HTTP server. +// To use an alternative start this test with OTEL_SEMCONV_STABILITY_OPT_IN set to the +// version under test. +func BenchmarkHTTPServerRequest(b *testing.B) { + // Request was generated from TestHTTPServerRequest request. + req := &http.Request{ + Method: http.MethodGet, + URL: &url.URL{ + Path: "/", + }, + Proto: "HTTP/1.1", + ProtoMajor: 1, + ProtoMinor: 1, + Header: http.Header{ + "User-Agent": []string{"Go-http-client/1.1"}, + "Accept-Encoding": []string{"gzip"}, + }, + Body: http.NoBody, + Host: "127.0.0.1:39093", + RemoteAddr: "127.0.0.1:38738", + RequestURI: "/", + } + serv := NewHTTPServer(nil) + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + benchHTTPServerRequestResults = serv.RequestTraceAttrs("", req, RequestTraceAttrsOpts{}) + } +} diff --git a/instrumentation/github.com/labstack/echo/otelecho/internal/semconv/common_test.go b/instrumentation/github.com/labstack/echo/otelecho/internal/semconv/common_test.go new file mode 100644 index 00000000000..9669e3b9ec8 --- /dev/null +++ b/instrumentation/github.com/labstack/echo/otelecho/internal/semconv/common_test.go @@ -0,0 +1,71 @@ +// Code generated by gotmpl. DO NOT MODIFY. +// source: internal/shared/semconv/common_test.go.tmpl + +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package semconv_test + +import ( + "net/http" + "net/http/httptest" + "net/url" + "strconv" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho/internal/semconv" + "go.opentelemetry.io/otel/attribute" +) + +type testServerReq struct { + hostname string + serverPort int + peerAddr string + peerPort int + clientIP string +} + +func testTraceRequest(t *testing.T, serv semconv.HTTPServer, want func(testServerReq) []attribute.KeyValue) { + t.Helper() + + got := make(chan *http.Request, 1) + handler := func(w http.ResponseWriter, r *http.Request) { + got <- r + close(got) + w.WriteHeader(http.StatusOK) + } + + srv := httptest.NewServer(http.HandlerFunc(handler)) + defer srv.Close() + + srvURL, err := url.Parse(srv.URL) + require.NoError(t, err) + srvPort, err := strconv.ParseInt(srvURL.Port(), 10, 32) + require.NoError(t, err) + + resp, err := srv.Client().Get(srv.URL) + require.NoError(t, err) + require.NoError(t, resp.Body.Close()) + + req := <-got + peer, peerPort := semconv.SplitHostPort(req.RemoteAddr) + + const user = "alice" + req.SetBasicAuth(user, "pswrd") + + const clientIP = "127.0.0.5" + req.Header.Add("X-Forwarded-For", clientIP) + + srvReq := testServerReq{ + hostname: srvURL.Hostname(), + serverPort: int(srvPort), + peerAddr: peer, + peerPort: peerPort, + clientIP: clientIP, + } + + assert.ElementsMatch(t, want(srvReq), serv.RequestTraceAttrs("", req, semconv.RequestTraceAttrsOpts{})) +} diff --git a/instrumentation/github.com/labstack/echo/otelecho/internal/semconv/env.go b/instrumentation/github.com/labstack/echo/otelecho/internal/semconv/env.go new file mode 100644 index 00000000000..f585ab9784b --- /dev/null +++ b/instrumentation/github.com/labstack/echo/otelecho/internal/semconv/env.go @@ -0,0 +1,248 @@ +// Code generated by gotmpl. DO NOT MODIFY. +// source: internal/shared/semconv/env.go.tmpl + +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package semconv // import "go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho/internal/semconv" + +import ( + "context" + "fmt" + "net/http" + "strings" + "sync" + + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" + "go.opentelemetry.io/otel/metric" + "go.opentelemetry.io/otel/semconv/v1.34.0/httpconv" +) + +// OTelSemConvStabilityOptIn is an environment variable. +// That can be set to "http/dup" to keep getting the old HTTP semantic conventions. +const OTelSemConvStabilityOptIn = "OTEL_SEMCONV_STABILITY_OPT_IN" + +type ResponseTelemetry struct { + StatusCode int + ReadBytes int64 + ReadError error + WriteBytes int64 + WriteError error +} + +type HTTPServer struct { + requestBodySizeHistogram httpconv.ServerRequestBodySize + responseBodySizeHistogram httpconv.ServerResponseBodySize + requestDurationHistogram httpconv.ServerRequestDuration +} + +// RequestTraceAttrs returns trace attributes for an HTTP request received by a +// server. +// +// The server must be the primary server name if it is known. For example this +// would be the ServerName directive +// (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache +// server, and the server_name directive +// (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an +// nginx server. More generically, the primary server name would be the host +// header value that matches the default virtual host of an HTTP server. It +// should include the host identifier and if a port is used to route to the +// server that port identifier should be included as an appropriate port +// suffix. +// +// If the primary server name is not known, server should be an empty string. +// The req Host will be used to determine the server instead. +func (s HTTPServer) RequestTraceAttrs(server string, req *http.Request, opts RequestTraceAttrsOpts) []attribute.KeyValue { + return CurrentHTTPServer{}.RequestTraceAttrs(server, req, opts) +} + +func (s HTTPServer) NetworkTransportAttr(network string) []attribute.KeyValue { + return []attribute.KeyValue{ + CurrentHTTPServer{}.NetworkTransportAttr(network), + } +} + +// ResponseTraceAttrs returns trace attributes for telemetry from an HTTP response. +// +// If any of the fields in the ResponseTelemetry are not set the attribute will be omitted. +func (s HTTPServer) ResponseTraceAttrs(resp ResponseTelemetry) []attribute.KeyValue { + return CurrentHTTPServer{}.ResponseTraceAttrs(resp) +} + +// Route returns the attribute for the route. +func (s HTTPServer) Route(route string) attribute.KeyValue { + return CurrentHTTPServer{}.Route(route) +} + +// Status returns a span status code and message for an HTTP status code +// value returned by a server. Status codes in the 400-499 range are not +// returned as errors. +func (s HTTPServer) Status(code int) (codes.Code, string) { + if code < 100 || code >= 600 { + return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code) + } + if code >= 500 { + return codes.Error, "" + } + return codes.Unset, "" +} + +type ServerMetricData struct { + ServerName string + ResponseSize int64 + + MetricData + MetricAttributes +} + +type MetricAttributes struct { + Req *http.Request + StatusCode int + AdditionalAttributes []attribute.KeyValue +} + +type MetricData struct { + RequestSize int64 + + // The request duration, in milliseconds + ElapsedTime float64 +} + +var ( + metricAddOptionPool = &sync.Pool{ + New: func() interface{} { + return &[]metric.AddOption{} + }, + } + + metricRecordOptionPool = &sync.Pool{ + New: func() interface{} { + return &[]metric.RecordOption{} + }, + } +) + +func (s HTTPServer) RecordMetrics(ctx context.Context, md ServerMetricData) { + 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.Inst().Record(ctx, md.RequestSize, *recordOpts...) + s.responseBodySizeHistogram.Inst().Record(ctx, md.ResponseSize, *recordOpts...) + s.requestDurationHistogram.Inst().Record(ctx, md.ElapsedTime/1000.0, o) + *recordOpts = (*recordOpts)[:0] + metricRecordOptionPool.Put(recordOpts) +} + +// hasOptIn returns true if the comma-separated version string contains the +// exact optIn value. +func hasOptIn(version, optIn string) bool { + for _, v := range strings.Split(version, ",") { + if strings.TrimSpace(v) == optIn { + return true + } + } + return false +} + +func NewHTTPServer(meter metric.Meter) HTTPServer { + server := HTTPServer{} + + var err error + server.requestBodySizeHistogram, err = httpconv.NewServerRequestBodySize(meter) + handleErr(err) + + server.responseBodySizeHistogram, err = httpconv.NewServerResponseBodySize(meter) + handleErr(err) + + server.requestDurationHistogram, err = httpconv.NewServerRequestDuration( + meter, + metric.WithExplicitBucketBoundaries( + 0.005, 0.01, 0.025, 0.05, 0.075, 0.1, + 0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10, + ), + ) + handleErr(err) + return server +} + +type HTTPClient struct { + requestBodySize httpconv.ClientRequestBodySize + requestDuration httpconv.ClientRequestDuration +} + +func NewHTTPClient(meter metric.Meter) HTTPClient { + client := HTTPClient{} + + var err error + client.requestBodySize, err = httpconv.NewClientRequestBodySize(meter) + handleErr(err) + + client.requestDuration, err = httpconv.NewClientRequestDuration( + meter, + metric.WithExplicitBucketBoundaries(0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10), + ) + handleErr(err) + + return client +} + +// RequestTraceAttrs returns attributes for an HTTP request made by a client. +func (c HTTPClient) RequestTraceAttrs(req *http.Request) []attribute.KeyValue { + return CurrentHTTPClient{}.RequestTraceAttrs(req) +} + +// ResponseTraceAttrs returns metric attributes for an HTTP request made by a client. +func (c HTTPClient) ResponseTraceAttrs(resp *http.Response) []attribute.KeyValue { + return CurrentHTTPClient{}.ResponseTraceAttrs(resp) +} + +func (c HTTPClient) Status(code int) (codes.Code, string) { + if code < 100 || code >= 600 { + return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code) + } + if code >= 400 { + return codes.Error, "" + } + return codes.Unset, "" +} + +func (c HTTPClient) ErrorType(err error) attribute.KeyValue { + return CurrentHTTPClient{}.ErrorType(err) +} + +type MetricOpts struct { + measurement metric.MeasurementOption + addOptions metric.AddOption +} + +func (o MetricOpts) MeasurementOption() metric.MeasurementOption { + return o.measurement +} + +func (o MetricOpts) AddOptions() metric.AddOption { + return o.addOptions +} + +func (c HTTPClient) MetricOptions(ma MetricAttributes) map[string]MetricOpts { + opts := map[string]MetricOpts{} + + attributes := CurrentHTTPClient{}.MetricAttributes(ma.Req, ma.StatusCode, ma.AdditionalAttributes) + set := metric.WithAttributeSet(attribute.NewSet(attributes...)) + opts["new"] = MetricOpts{ + measurement: set, + addOptions: set, + } + + return opts +} + +func (s HTTPClient) RecordMetrics(ctx context.Context, md MetricData, opts map[string]MetricOpts) { + s.requestBodySize.Inst().Record(ctx, md.RequestSize, opts["new"].MeasurementOption()) + s.requestDuration.Inst().Record(ctx, md.ElapsedTime/1000, opts["new"].MeasurementOption()) +} + +func (s HTTPClient) TraceAttributes(host string) []attribute.KeyValue { + return CurrentHTTPClient{}.TraceAttributes(host) +} diff --git a/instrumentation/github.com/labstack/echo/otelecho/internal/semconv/env_test.go b/instrumentation/github.com/labstack/echo/otelecho/internal/semconv/env_test.go new file mode 100644 index 00000000000..11388bb8324 --- /dev/null +++ b/instrumentation/github.com/labstack/echo/otelecho/internal/semconv/env_test.go @@ -0,0 +1,197 @@ +// Code generated by gotmpl. DO NOT MODIFY. +// source: internal/shared/semconv/env_test.go.tmpl + +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package semconv + +import ( + "context" + "net/http" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/metric/noop" +) + +func TestHTTPServerDoesNotPanic(t *testing.T) { + testCases := []struct { + name string + server HTTPServer + }{ + { + name: "nil meter", + server: NewHTTPServer(nil), + }, + { + name: "with Meter", + server: NewHTTPServer(noop.Meter{}), + }, + } + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + require.NotPanics(t, func() { + req, err := http.NewRequest("GET", "http://example.com", nil) + require.NoError(t, err) + + _ = tt.server.RequestTraceAttrs("stuff", req, RequestTraceAttrsOpts{}) + _ = tt.server.ResponseTraceAttrs(ResponseTelemetry{StatusCode: 200}) + tt.server.RecordMetrics(context.Background(), ServerMetricData{ + ServerName: "stuff", + MetricAttributes: MetricAttributes{ + Req: req, + }, + }) + }) + }) + } +} + +func TestServerNetworkTransportAttr(t *testing.T) { + for _, tt := range []struct { + name string + network string + + wantAttributes []attribute.KeyValue + }{ + { + name: "without any opt-in", + network: "tcp", + + wantAttributes: []attribute.KeyValue{ + attribute.String("network.transport", "tcp"), + }, + }, + } { + t.Run(tt.name, func(t *testing.T) { + s := NewHTTPServer(nil) + + assert.Equal(t, tt.wantAttributes, s.NetworkTransportAttr(tt.network)) + }) + } +} + +func TestHTTPClientDoesNotPanic(t *testing.T) { + testCases := []struct { + name string + client HTTPClient + }{ + { + name: "nil meter", + client: NewHTTPClient(nil), + }, + { + name: "with Meter", + client: NewHTTPClient(noop.Meter{}), + }, + } + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + require.NotPanics(t, func() { + req, err := http.NewRequest("GET", "http://example.com", nil) + require.NoError(t, err) + + _ = tt.client.RequestTraceAttrs(req) + _ = tt.client.ResponseTraceAttrs(&http.Response{StatusCode: 200}) + + opts := tt.client.MetricOptions(MetricAttributes{ + Req: req, + StatusCode: 200, + }) + tt.client.RecordMetrics(context.Background(), MetricData{ + RequestSize: 20, + ElapsedTime: 1, + }, opts) + }) + }) + } +} + +func TestHTTPClientTraceAttributes(t *testing.T) { + for _, tt := range []struct { + name string + + wantAttributes []attribute.KeyValue + }{ + { + name: "with no optin set", + + wantAttributes: []attribute.KeyValue{ + attribute.String("server.address", "example.com"), + }, + }, + } { + t.Run(tt.name, func(t *testing.T) { + c := NewHTTPClient(nil) + a := c.TraceAttributes("example.com") + assert.Equal(t, tt.wantAttributes, a) + }) + } +} + +func TestClientTraceAttributes(t *testing.T) { + for _, tt := range []struct { + name string + host string + + wantAttributes []attribute.KeyValue + }{ + { + name: "without any opt-in", + host: "example.com", + + wantAttributes: []attribute.KeyValue{ + attribute.String("server.address", "example.com"), + }, + }, + } { + t.Run(tt.name, func(t *testing.T) { + s := NewHTTPClient(nil) + + assert.Equal(t, tt.wantAttributes, s.TraceAttributes(tt.host)) + }) + } +} + +func BenchmarkRecordMetrics(b *testing.B) { + benchmarks := []struct { + name string + server HTTPServer + }{ + { + name: "empty", + server: HTTPServer{}, + }, + { + name: "nil meter", + server: NewHTTPServer(nil), + }, + { + name: "with Meter", + server: NewHTTPServer(noop.Meter{}), + }, + } + + for _, bm := range benchmarks { + b.Run(bm.name, func(b *testing.B) { + req, _ := http.NewRequest("GET", "http://example.com", nil) + _ = bm.server.RequestTraceAttrs("stuff", req, RequestTraceAttrsOpts{}) + _ = bm.server.ResponseTraceAttrs(ResponseTelemetry{StatusCode: 200}) + ctx := context.Background() + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + bm.server.RecordMetrics(ctx, ServerMetricData{ + ServerName: bm.name, + MetricAttributes: MetricAttributes{ + Req: req, + }, + }) + } + }) + } +} diff --git a/instrumentation/github.com/labstack/echo/otelecho/internal/semconv/gen.go b/instrumentation/github.com/labstack/echo/otelecho/internal/semconv/gen.go new file mode 100644 index 00000000000..0349470dd35 --- /dev/null +++ b/instrumentation/github.com/labstack/echo/otelecho/internal/semconv/gen.go @@ -0,0 +1,15 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package semconv // import "go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho/internal/semconv" + +// Generate semconv package: +//go:generate gotmpl --body=../../../../../../../internal/shared/semconv/bench_test.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho\" }" --out=bench_test.go +//go:generate gotmpl --body=../../../../../../../internal/shared/semconv/common_test.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho\" }" --out=common_test.go +//go:generate gotmpl --body=../../../../../../../internal/shared/semconv/env.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho\" }" --out=env.go +//go:generate gotmpl --body=../../../../../../../internal/shared/semconv/env_test.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho\" }" --out=env_test.go +//go:generate gotmpl --body=../../../../../../../internal/shared/semconv/httpconv.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho\" }" --out=httpconv.go +//go:generate gotmpl --body=../../../../../../../internal/shared/semconv/httpconv_test.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho\" }" --out=httpconv_test.go +//go:generate gotmpl --body=../../../../../../../internal/shared/semconv/httpconvtest_test.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho\" }" --out=httpconvtest_test.go +//go:generate gotmpl --body=../../../../../../../internal/shared/semconv/util.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho\" }" --out=util.go +//go:generate gotmpl --body=../../../../../../../internal/shared/semconv/util_test.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/contrib/instrumentation/\" }" --out=util_test.go diff --git a/instrumentation/github.com/labstack/echo/otelecho/internal/semconv/httpconv.go b/instrumentation/github.com/labstack/echo/otelecho/internal/semconv/httpconv.go new file mode 100644 index 00000000000..5dff12edd66 --- /dev/null +++ b/instrumentation/github.com/labstack/echo/otelecho/internal/semconv/httpconv.go @@ -0,0 +1,517 @@ +// Code generated by gotmpl. DO NOT MODIFY. +// source: internal/shared/semconv/httpconv.go.tmpl + +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +// Package semconv provides OpenTelemetry semantic convention types and +// functionality. +package semconv // import "go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho/internal/semconv" + +import ( + "fmt" + "net/http" + "reflect" + "slices" + "strconv" + "strings" + + "go.opentelemetry.io/otel/attribute" + semconvNew "go.opentelemetry.io/otel/semconv/v1.34.0" +) + +type RequestTraceAttrsOpts struct { + // If set, this is used as value for the "http.client_ip" attribute. + HTTPClientIP string +} + +type CurrentHTTPServer struct{} + +// RequestTraceAttrs returns trace attributes for an HTTP request received by a +// server. +// +// The server must be the primary server name if it is known. For example this +// would be the ServerName directive +// (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache +// server, and the server_name directive +// (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an +// nginx server. More generically, the primary server name would be the host +// header value that matches the default virtual host of an HTTP server. It +// should include the host identifier and if a port is used to route to the +// server that port identifier should be included as an appropriate port +// suffix. +// +// If the primary server name is not known, server should be an empty string. +// The req Host will be used to determine the server instead. +func (n CurrentHTTPServer) RequestTraceAttrs(server string, req *http.Request, opts RequestTraceAttrsOpts) []attribute.KeyValue { + count := 3 // ServerAddress, Method, Scheme + + 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 { + count++ + } + + method, methodOriginal := n.method(req.Method) + if methodOriginal != (attribute.KeyValue{}) { + count++ + } + + scheme := n.scheme(req.TLS != nil) + + peer, peerPort := SplitHostPort(req.RemoteAddr) + if peer != "" { + // The Go HTTP server sets RemoteAddr to "IP:port", this will not be a + // file-path that would be interpreted with a sock family. + count++ + if peerPort > 0 { + count++ + } + } + + useragent := req.UserAgent() + if useragent != "" { + count++ + } + + // For client IP, use, in order: + // 1. The value passed in the options + // 2. The value in the X-Forwarded-For header + // 3. The peer address + clientIP := opts.HTTPClientIP + if clientIP == "" { + clientIP = serverClientIP(req.Header.Get("X-Forwarded-For")) + if clientIP == "" { + clientIP = peer + } + } + if clientIP != "" { + count++ + } + + if req.URL != nil && req.URL.Path != "" { + count++ + } + + protoName, protoVersion := netProtocol(req.Proto) + if protoName != "" && protoName != "http" { + count++ + } + if protoVersion != "" { + count++ + } + + route := httpRoute(req.Pattern) + if route != "" { + count++ + } + + attrs := make([]attribute.KeyValue, 0, count) + attrs = append(attrs, + semconvNew.ServerAddress(host), + method, + scheme, + ) + + if hostPort > 0 { + attrs = append(attrs, semconvNew.ServerPort(hostPort)) + } + if methodOriginal != (attribute.KeyValue{}) { + attrs = append(attrs, methodOriginal) + } + + if peer, peerPort := SplitHostPort(req.RemoteAddr); peer != "" { + // The Go HTTP server sets RemoteAddr to "IP:port", this will not be a + // file-path that would be interpreted with a sock family. + attrs = append(attrs, semconvNew.NetworkPeerAddress(peer)) + if peerPort > 0 { + attrs = append(attrs, semconvNew.NetworkPeerPort(peerPort)) + } + } + + if useragent != "" { + attrs = append(attrs, semconvNew.UserAgentOriginal(useragent)) + } + + if clientIP != "" { + attrs = append(attrs, semconvNew.ClientAddress(clientIP)) + } + + if req.URL != nil && req.URL.Path != "" { + attrs = append(attrs, semconvNew.URLPath(req.URL.Path)) + } + + if protoName != "" && protoName != "http" { + attrs = append(attrs, semconvNew.NetworkProtocolName(protoName)) + } + if protoVersion != "" { + attrs = append(attrs, semconvNew.NetworkProtocolVersion(protoVersion)) + } + + if route != "" { + attrs = append(attrs, n.Route(route)) + } + + return attrs +} + +func (n CurrentHTTPServer) NetworkTransportAttr(network string) attribute.KeyValue { + switch network { + case "tcp", "tcp4", "tcp6": + return semconvNew.NetworkTransportTCP + case "udp", "udp4", "udp6": + return semconvNew.NetworkTransportUDP + case "unix", "unixgram", "unixpacket": + return semconvNew.NetworkTransportUnix + default: + return semconvNew.NetworkTransportPipe + } +} + +func (n CurrentHTTPServer) method(method string) (attribute.KeyValue, attribute.KeyValue) { + if method == "" { + return semconvNew.HTTPRequestMethodGet, attribute.KeyValue{} + } + if attr, ok := methodLookup[method]; ok { + return attr, attribute.KeyValue{} + } + + orig := semconvNew.HTTPRequestMethodOriginal(method) + if attr, ok := methodLookup[strings.ToUpper(method)]; ok { + return attr, orig + } + return semconvNew.HTTPRequestMethodGet, orig +} + +func (n CurrentHTTPServer) scheme(https bool) attribute.KeyValue { // nolint:revive + if https { + return semconvNew.URLScheme("https") + } + return semconvNew.URLScheme("http") +} + +// ResponseTraceAttrs returns trace attributes for telemetry from an HTTP +// response. +// +// If any of the fields in the ResponseTelemetry are not set the attribute will +// be omitted. +func (n CurrentHTTPServer) ResponseTraceAttrs(resp ResponseTelemetry) []attribute.KeyValue { + var count int + + if resp.ReadBytes > 0 { + count++ + } + if resp.WriteBytes > 0 { + count++ + } + if resp.StatusCode > 0 { + count++ + } + + attributes := make([]attribute.KeyValue, 0, count) + + if resp.ReadBytes > 0 { + attributes = append(attributes, + semconvNew.HTTPRequestBodySize(int(resp.ReadBytes)), + ) + } + if resp.WriteBytes > 0 { + attributes = append(attributes, + semconvNew.HTTPResponseBodySize(int(resp.WriteBytes)), + ) + } + if resp.StatusCode > 0 { + attributes = append(attributes, + semconvNew.HTTPResponseStatusCode(resp.StatusCode), + ) + } + + return attributes +} + +// Route returns the attribute for the route. +func (n CurrentHTTPServer) Route(route string) attribute.KeyValue { + return semconvNew.HTTPRoute(route) +} + +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. +func (n CurrentHTTPClient) RequestTraceAttrs(req *http.Request) []attribute.KeyValue { + /* + below attributes are returned: + - http.request.method + - http.request.method.original + - url.full + - server.address + - server.port + - network.protocol.name + - network.protocol.version + */ + numOfAttributes := 3 // URL, server address, proto, and method. + + var urlHost string + if req.URL != nil { + urlHost = req.URL.Host + } + var requestHost string + var requestPort int + for _, hostport := range []string{urlHost, req.Header.Get("Host")} { + requestHost, requestPort = SplitHostPort(hostport) + if requestHost != "" || requestPort > 0 { + break + } + } + + eligiblePort := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", requestPort) + if eligiblePort > 0 { + numOfAttributes++ + } + useragent := req.UserAgent() + if useragent != "" { + numOfAttributes++ + } + + protoName, protoVersion := netProtocol(req.Proto) + if protoName != "" && protoName != "http" { + numOfAttributes++ + } + if protoVersion != "" { + numOfAttributes++ + } + + method, originalMethod := n.method(req.Method) + if originalMethod != (attribute.KeyValue{}) { + numOfAttributes++ + } + + attrs := make([]attribute.KeyValue, 0, numOfAttributes) + + attrs = append(attrs, method) + if originalMethod != (attribute.KeyValue{}) { + attrs = append(attrs, originalMethod) + } + + var u string + if req.URL != nil { + // Remove any username/password info that may be in the URL. + userinfo := req.URL.User + req.URL.User = nil + u = req.URL.String() + // Restore any username/password info that was removed. + req.URL.User = userinfo + } + attrs = append(attrs, semconvNew.URLFull(u)) + + attrs = append(attrs, semconvNew.ServerAddress(requestHost)) + if eligiblePort > 0 { + attrs = append(attrs, semconvNew.ServerPort(eligiblePort)) + } + + if protoName != "" && protoName != "http" { + attrs = append(attrs, semconvNew.NetworkProtocolName(protoName)) + } + if protoVersion != "" { + attrs = append(attrs, semconvNew.NetworkProtocolVersion(protoVersion)) + } + + return attrs +} + +// ResponseTraceAttrs returns trace attributes for an HTTP response made by a client. +func (n CurrentHTTPClient) ResponseTraceAttrs(resp *http.Response) []attribute.KeyValue { + /* + below attributes are returned: + - http.response.status_code + - error.type + */ + var count int + if resp.StatusCode > 0 { + count++ + } + + if isErrorStatusCode(resp.StatusCode) { + count++ + } + + attrs := make([]attribute.KeyValue, 0, count) + if resp.StatusCode > 0 { + attrs = append(attrs, semconvNew.HTTPResponseStatusCode(resp.StatusCode)) + } + + if isErrorStatusCode(resp.StatusCode) { + errorType := strconv.Itoa(resp.StatusCode) + attrs = append(attrs, semconvNew.ErrorTypeKey.String(errorType)) + } + return attrs +} + +func (n CurrentHTTPClient) ErrorType(err error) attribute.KeyValue { + t := reflect.TypeOf(err) + var value string + if t.PkgPath() == "" && t.Name() == "" { + // Likely a builtin type. + value = t.String() + } else { + value = fmt.Sprintf("%s.%s", t.PkgPath(), t.Name()) + } + + if value == "" { + return semconvNew.ErrorTypeOther + } + + return semconvNew.ErrorTypeKey.String(value) +} + +func (n CurrentHTTPClient) method(method string) (attribute.KeyValue, attribute.KeyValue) { + if method == "" { + return semconvNew.HTTPRequestMethodGet, attribute.KeyValue{} + } + if attr, ok := methodLookup[method]; ok { + return attr, attribute.KeyValue{} + } + + orig := semconvNew.HTTPRequestMethodOriginal(method) + if attr, ok := methodLookup[strings.ToUpper(method)]; ok { + return attr, orig + } + return semconvNew.HTTPRequestMethodGet, orig +} + +func (n CurrentHTTPClient) MetricAttributes(req *http.Request, statusCode int, additionalAttributes []attribute.KeyValue) []attribute.KeyValue { + num := len(additionalAttributes) + 2 + var h string + if req.URL != nil { + h = req.URL.Host + } + var requestHost string + var requestPort int + for _, hostport := range []string{h, req.Header.Get("Host")} { + requestHost, requestPort = SplitHostPort(hostport) + if requestHost != "" || requestPort > 0 { + break + } + } + + port := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", requestPort) + if port > 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)), + semconvNew.ServerAddress(requestHost), + n.scheme(req), + ) + + if port > 0 { + attributes = append(attributes, semconvNew.ServerPort(port)) + } + 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 +} + +// TraceAttributes returns attributes for httptrace. +func (n CurrentHTTPClient) TraceAttributes(host string) []attribute.KeyValue { + return []attribute.KeyValue{ + semconvNew.ServerAddress(host), + } +} + +func (n CurrentHTTPClient) scheme(req *http.Request) attribute.KeyValue { + if req.URL != nil && req.URL.Scheme != "" { + return semconvNew.URLScheme(req.URL.Scheme) + } + if req.TLS != nil { + return semconvNew.URLScheme("https") + } + return semconvNew.URLScheme("http") +} + +func isErrorStatusCode(code int) bool { + return code >= 400 || code < 100 +} diff --git a/instrumentation/github.com/labstack/echo/otelecho/internal/semconv/httpconv_test.go b/instrumentation/github.com/labstack/echo/otelecho/internal/semconv/httpconv_test.go new file mode 100644 index 00000000000..42853084fa0 --- /dev/null +++ b/instrumentation/github.com/labstack/echo/otelecho/internal/semconv/httpconv_test.go @@ -0,0 +1,371 @@ +// Code generated by gotmpl. DO NOT MODIFY. +// source: internal/shared/semconv/httpconv_test.go.tmpl + +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package semconv + +import ( + "net/http" + "net/http/httptest" + "strconv" + "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 + n int + want attribute.KeyValue + wantOrig attribute.KeyValue + }{ + { + method: http.MethodPost, + n: 1, + want: attribute.String("http.request.method", "POST"), + }, + { + method: "Put", + n: 2, + want: attribute.String("http.request.method", "PUT"), + wantOrig: attribute.String("http.request.method_original", "Put"), + }, + { + method: "Unknown", + n: 2, + want: attribute.String("http.request.method", "GET"), + wantOrig: attribute.String("http.request.method_original", "Unknown"), + }, + } + + for _, tt := range testCases { + t.Run(tt.method, func(t *testing.T) { + got, gotOrig := CurrentHTTPServer{}.method(tt.method) + assert.Equal(t, tt.want, got) + assert.Equal(t, tt.wantOrig, gotOrig) + }) + } +} + +func TestHTTPClientStatus(t *testing.T) { + tests := []struct { + code int + stat codes.Code + msg bool + }{ + {0, codes.Error, true}, + {http.StatusContinue, codes.Unset, false}, + {http.StatusSwitchingProtocols, codes.Unset, false}, + {http.StatusProcessing, codes.Unset, false}, + {http.StatusEarlyHints, codes.Unset, false}, + {http.StatusOK, codes.Unset, false}, + {http.StatusCreated, codes.Unset, false}, + {http.StatusAccepted, codes.Unset, false}, + {http.StatusNonAuthoritativeInfo, codes.Unset, false}, + {http.StatusNoContent, codes.Unset, false}, + {http.StatusResetContent, codes.Unset, false}, + {http.StatusPartialContent, codes.Unset, false}, + {http.StatusMultiStatus, codes.Unset, false}, + {http.StatusAlreadyReported, codes.Unset, false}, + {http.StatusIMUsed, codes.Unset, false}, + {http.StatusMultipleChoices, codes.Unset, false}, + {http.StatusMovedPermanently, codes.Unset, false}, + {http.StatusFound, codes.Unset, false}, + {http.StatusSeeOther, codes.Unset, false}, + {http.StatusNotModified, codes.Unset, false}, + {http.StatusUseProxy, codes.Unset, false}, + {306, codes.Unset, false}, + {http.StatusTemporaryRedirect, codes.Unset, false}, + {http.StatusPermanentRedirect, codes.Unset, false}, + {http.StatusBadRequest, codes.Error, false}, + {http.StatusUnauthorized, codes.Error, false}, + {http.StatusPaymentRequired, codes.Error, false}, + {http.StatusForbidden, codes.Error, false}, + {http.StatusNotFound, codes.Error, false}, + {http.StatusMethodNotAllowed, codes.Error, false}, + {http.StatusNotAcceptable, codes.Error, false}, + {http.StatusProxyAuthRequired, codes.Error, false}, + {http.StatusRequestTimeout, codes.Error, false}, + {http.StatusConflict, codes.Error, false}, + {http.StatusGone, codes.Error, false}, + {http.StatusLengthRequired, codes.Error, false}, + {http.StatusPreconditionFailed, codes.Error, false}, + {http.StatusRequestEntityTooLarge, codes.Error, false}, + {http.StatusRequestURITooLong, codes.Error, false}, + {http.StatusUnsupportedMediaType, codes.Error, false}, + {http.StatusRequestedRangeNotSatisfiable, codes.Error, false}, + {http.StatusExpectationFailed, codes.Error, false}, + {http.StatusTeapot, codes.Error, false}, + {http.StatusMisdirectedRequest, codes.Error, false}, + {http.StatusUnprocessableEntity, codes.Error, false}, + {http.StatusLocked, codes.Error, false}, + {http.StatusFailedDependency, codes.Error, false}, + {http.StatusTooEarly, codes.Error, false}, + {http.StatusUpgradeRequired, codes.Error, false}, + {http.StatusPreconditionRequired, codes.Error, false}, + {http.StatusTooManyRequests, codes.Error, false}, + {http.StatusRequestHeaderFieldsTooLarge, codes.Error, false}, + {http.StatusUnavailableForLegalReasons, codes.Error, false}, + {499, codes.Error, false}, + {http.StatusInternalServerError, codes.Error, false}, + {http.StatusNotImplemented, codes.Error, false}, + {http.StatusBadGateway, codes.Error, false}, + {http.StatusServiceUnavailable, codes.Error, false}, + {http.StatusGatewayTimeout, codes.Error, false}, + {http.StatusHTTPVersionNotSupported, codes.Error, false}, + {http.StatusVariantAlsoNegotiates, codes.Error, false}, + {http.StatusInsufficientStorage, codes.Error, false}, + {http.StatusLoopDetected, codes.Error, false}, + {http.StatusNotExtended, codes.Error, false}, + {http.StatusNetworkAuthenticationRequired, codes.Error, false}, + {600, codes.Error, true}, + } + + for _, test := range tests { + t.Run(strconv.Itoa(test.code), func(t *testing.T) { + c, msg := HTTPClient{}.Status(test.code) + assert.Equal(t, test.stat, c) + if test.msg && msg == "" { + t.Errorf("expected non-empty message for %d", test.code) + } else if !test.msg && msg != "" { + t.Errorf("expected empty message for %d, got: %s", test.code, msg) + } + }) + } +} + +func TestCurrentHttpClient_MetricAttributes(t *testing.T) { + defaultRequest, err := http.NewRequest("GET", "http://example.com/path?query=test", nil) + require.NoError(t, err) + httpsRequest, err := http.NewRequest("GET", "https://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", + 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("server.address", "example.com"), + attribute.String("url.scheme", "http"), + 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", + req: defaultRequest, + statusCode: 200, + additionalAttributes: nil, + wantFunc: func(t *testing.T, attrs []attribute.KeyValue) { + require.Len(t, attrs, 6) + assert.ElementsMatch(t, []attribute.KeyValue{ + attribute.String("http.request.method", "GET"), + attribute.String("server.address", "example.com"), + attribute.String("url.scheme", "http"), + attribute.String("network.protocol.name", "http"), + attribute.String("network.protocol.version", "1.1"), + attribute.Int64("http.response.status_code", 200), + }, attrs) + }, + }, + { + name: "https scheme", + req: httpsRequest, + statusCode: 200, + additionalAttributes: nil, + wantFunc: func(t *testing.T, attrs []attribute.KeyValue) { + require.Len(t, attrs, 6) + assert.ElementsMatch(t, []attribute.KeyValue{ + attribute.String("http.request.method", "GET"), + attribute.String("server.address", "example.com"), + attribute.String("url.scheme", "https"), + 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 := CurrentHTTPClient{}.MetricAttributes(tt.req, tt.statusCode, tt.additionalAttributes) + tt.wantFunc(t, got) + }) + } +} + +func TestRequestTraceAttrs_HTTPRoute(t *testing.T) { + tests := []struct { + name string + pattern string + wantRoute string + }{ + { + name: "only path", + pattern: "/path/{id}", + wantRoute: "/path/{id}", + }, + { + name: "with method", + pattern: "GET /path/{id}", + wantRoute: "/path/{id}", + }, + { + name: "with domain", + pattern: "example.com/path/{id}", + wantRoute: "/path/{id}", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + req := httptest.NewRequest(http.MethodGet, "/path/abc123", nil) + req.Pattern = tt.pattern + + attrs := (CurrentHTTPServer{}).RequestTraceAttrs("", req, RequestTraceAttrsOpts{}) + + var gotRoute string + for _, attr := range attrs { + if attr.Key == "http.route" { + gotRoute = attr.Value.AsString() + break + } + } + require.Equal(t, tt.wantRoute, gotRoute) + }) + } +} + +func TestRequestTraceAttrs_ClientIP(t *testing.T) { + for _, tt := range []struct { + name string + requestModifierFn func(r *http.Request) + requestTraceOpts RequestTraceAttrsOpts + + wantClientIP string + }{ + { + name: "with a client IP from the network", + wantClientIP: "1.2.3.4", + }, + { + name: "with a client IP from x-forwarded-for header", + requestModifierFn: func(r *http.Request) { + r.Header.Add("X-Forwarded-For", "5.6.7.8") + }, + wantClientIP: "5.6.7.8", + }, + { + name: "with a client IP in options", + requestModifierFn: func(r *http.Request) { + r.Header.Add("X-Forwarded-For", "5.6.7.8") + }, + requestTraceOpts: RequestTraceAttrsOpts{ + HTTPClientIP: "9.8.7.6", + }, + wantClientIP: "9.8.7.6", + }, + } { + t.Run(tt.name, func(t *testing.T) { + req := httptest.NewRequest("GET", "/example", nil) + req.RemoteAddr = "1.2.3.4:5678" + + if tt.requestModifierFn != nil { + tt.requestModifierFn(req) + } + + var found bool + for _, attr := range (CurrentHTTPServer{}).RequestTraceAttrs("", req, tt.requestTraceOpts) { + if attr.Key != "client.address" { + continue + } + found = true + assert.Equal(t, tt.wantClientIP, attr.Value.AsString()) + } + require.True(t, found) + }) + } +} diff --git a/instrumentation/github.com/labstack/echo/otelecho/internal/semconv/httpconvtest_test.go b/instrumentation/github.com/labstack/echo/otelecho/internal/semconv/httpconvtest_test.go new file mode 100644 index 00000000000..234585051c1 --- /dev/null +++ b/instrumentation/github.com/labstack/echo/otelecho/internal/semconv/httpconvtest_test.go @@ -0,0 +1,456 @@ +// Code generated by gotmpl. DO NOT MODIFY. +// source: internal/shared/semconv/httpconv_test.go.tmpl + +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package semconv_test + +import ( + "context" + "errors" + "fmt" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho/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) { + serv := semconv.NewHTTPServer(nil) + want := func(req testServerReq) []attribute.KeyValue { + return []attribute.KeyValue{ + attribute.String("http.request.method", "GET"), + attribute.String("url.scheme", "http"), + attribute.String("server.address", req.hostname), + attribute.Int("server.port", req.serverPort), + attribute.String("network.peer.address", req.peerAddr), + attribute.Int("network.peer.port", req.peerPort), + attribute.String("user_agent.original", "Go-http-client/1.1"), + attribute.String("client.address", req.clientIP), + attribute.String("network.protocol.version", "1.1"), + attribute.String("url.path", "/"), + } + } + testTraceRequest(t, serv, want) +} + +func TestNewServerRecordMetrics(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 CurrentHTTPServer version + expectedCurrentScopeMetric := metricdata.ScopeMetrics{ + Scope: instrumentation.Scope{ + Name: "test", + }, + 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, + }, + }, + }, + }, + }, + } + + // The OldHTTPServer version + expectedOldScopeMetric := expectedCurrentScopeMetric + expectedOldScopeMetric.Metrics = append(expectedOldScopeMetric.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, + }, + }, + }, + }, + }...) + + tests := []struct { + name string + serverFunc func(metric.MeterProvider) semconv.HTTPServer + wantFunc func(t *testing.T, rm metricdata.ResourceMetrics) + }{ + { + name: "No Meter", + serverFunc: func(metric.MeterProvider) semconv.HTTPServer { + return semconv.NewHTTPServer(nil) + }, + wantFunc: func(t *testing.T, rm metricdata.ResourceMetrics) { + assert.Empty(t, rm.ScopeMetrics) + }, + }, + { + name: "With Meter", + 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, expectedCurrentScopeMetric, rm.ScopeMetrics[0], metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreValue()) + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + 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 + resp semconv.ResponseTelemetry + want []attribute.KeyValue + }{ + { + name: "empty", + resp: semconv.ResponseTelemetry{}, + want: nil, + }, + { + name: "no errors", + resp: semconv.ResponseTelemetry{ + StatusCode: 200, + ReadBytes: 701, + WriteBytes: 802, + }, + want: []attribute.KeyValue{ + attribute.Int("http.request.body.size", 701), + attribute.Int("http.response.body.size", 802), + attribute.Int("http.response.status_code", 200), + }, + }, + { + name: "with errors", + resp: semconv.ResponseTelemetry{ + StatusCode: 200, + ReadBytes: 701, + ReadError: fmt.Errorf("read error"), + WriteBytes: 802, + WriteError: fmt.Errorf("write error"), + }, + want: []attribute.KeyValue{ + attribute.Int("http.request.body.size", 701), + attribute.Int("http.response.body.size", 802), + attribute.Int("http.response.status_code", 200), + }, + }, + } + + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + got := semconv.CurrentHTTPServer{}.ResponseTraceAttrs(tt.resp) + assert.ElementsMatch(t, tt.want, got) + }) + } +} + +func TestNewTraceRequest_Client(t *testing.T) { + body := strings.NewReader("Hello, world!") + url := "https://example.com:8888/foo/bar?stuff=morestuff" + req := httptest.NewRequest("pOST", url, body) + req.Header.Set("User-Agent", "go-test-agent") + + want := []attribute.KeyValue{ + attribute.String("http.request.method", "POST"), + attribute.String("http.request.method_original", "pOST"), + attribute.String("url.full", url), + attribute.String("server.address", "example.com"), + attribute.Int("server.port", 8888), + attribute.String("network.protocol.version", "1.1"), + } + client := semconv.NewHTTPClient(nil) + assert.ElementsMatch(t, want, client.RequestTraceAttrs(req)) +} + +func TestNewTraceResponse_Client(t *testing.T) { + testcases := []struct { + resp http.Response + want []attribute.KeyValue + }{ + {resp: http.Response{StatusCode: 200, ContentLength: 123}, want: []attribute.KeyValue{attribute.Int("http.response.status_code", 200)}}, + {resp: http.Response{StatusCode: 404, ContentLength: 0}, want: []attribute.KeyValue{attribute.Int("http.response.status_code", 404), attribute.String("error.type", "404")}}, + } + + for _, tt := range testcases { + client := semconv.NewHTTPClient(nil) + assert.ElementsMatch(t, tt.want, client.ResponseTraceAttrs(&tt.resp)) + } +} + +func TestClientRequest(t *testing.T) { + body := strings.NewReader("Hello, world!") + url := "https://example.com:8888/foo/bar?stuff=morestuff" + req := httptest.NewRequest("pOST", url, body) + req.Header.Set("User-Agent", "go-test-agent") + + want := []attribute.KeyValue{ + attribute.String("http.request.method", "POST"), + attribute.String("http.request.method_original", "pOST"), + attribute.String("url.full", url), + attribute.String("server.address", "example.com"), + attribute.Int("server.port", 8888), + attribute.String("network.protocol.version", "1.1"), + } + got := semconv.CurrentHTTPClient{}.RequestTraceAttrs(req) + assert.ElementsMatch(t, want, got) +} + +func TestClientResponse(t *testing.T) { + testcases := []struct { + resp http.Response + want []attribute.KeyValue + }{ + {resp: http.Response{StatusCode: 200, ContentLength: 123}, want: []attribute.KeyValue{attribute.Int("http.response.status_code", 200)}}, + {resp: http.Response{StatusCode: 404, ContentLength: 0}, want: []attribute.KeyValue{attribute.Int("http.response.status_code", 404), attribute.String("error.type", "404")}}, + } + + for _, tt := range testcases { + got := semconv.CurrentHTTPClient{}.ResponseTraceAttrs(&tt.resp) + assert.ElementsMatch(t, tt.want, got) + } +} + +func TestRequestErrorType(t *testing.T) { + testcases := []struct { + err error + want attribute.KeyValue + }{ + {err: errors.New("http: nil Request.URL"), want: attribute.String("error.type", "*errors.errorString")}, + {err: customError{}, want: attribute.String("error.type", "go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho/internal/semconv_test.customError")}, + } + + for _, tt := range testcases { + got := semconv.CurrentHTTPClient{}.ErrorType(tt.err) + assert.Equal(t, tt.want, got) + } +} + +func TestNewClientRecordMetrics(t *testing.T) { + currAttrs := attribute.NewSet( + attribute.String("http.request.method", "POST"), + attribute.Int64("http.response.status_code", 301), + attribute.String("network.protocol.name", "http"), + attribute.String("network.protocol.version", "1.1"), + attribute.String("server.address", "example.com"), + attribute.String("url.scheme", "http"), + ) + + // the CurrentHTTPClient version + expectedCurrentScopeMetric := metricdata.ScopeMetrics{ + Scope: instrumentation.Scope{ + Name: "test", + }, + Metrics: []metricdata.Metrics{ + { + Name: "http.client.request.body.size", + Description: "Size of HTTP client request bodies.", + Unit: "By", + Data: metricdata.Histogram[int64]{ + Temporality: metricdata.CumulativeTemporality, + DataPoints: []metricdata.HistogramDataPoint[int64]{ + { + Attributes: currAttrs, + }, + }, + }, + }, + { + Name: "http.client.request.duration", + Description: "Duration of HTTP client requests.", + Unit: "s", + Data: metricdata.Histogram[float64]{ + Temporality: metricdata.CumulativeTemporality, + DataPoints: []metricdata.HistogramDataPoint[float64]{ + { + Attributes: currAttrs, + }, + }, + }, + }, + }, + } + + tests := []struct { + name string + clientFunc func(metric.MeterProvider) semconv.HTTPClient + wantFunc func(t *testing.T, rm metricdata.ResourceMetrics) + }{ + { + name: "No environment variable set, and no Meter", + clientFunc: func(metric.MeterProvider) semconv.HTTPClient { + return semconv.NewHTTPClient(nil) + }, + wantFunc: func(t *testing.T, rm metricdata.ResourceMetrics) { + assert.Empty(t, rm.ScopeMetrics) + }, + }, + { + name: "With Meter", + clientFunc: func(mp metric.MeterProvider) semconv.HTTPClient { + return semconv.NewHTTPClient(mp.Meter("test")) + }, + wantFunc: func(t *testing.T, rm metricdata.ResourceMetrics) { + require.Len(t, rm.ScopeMetrics, 1) + + require.Len(t, rm.ScopeMetrics[0].Metrics, 2) + metricdatatest.AssertEqual(t, expectedCurrentScopeMetric, rm.ScopeMetrics[0], metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreValue()) + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + reader := sdkmetric.NewManualReader() + mp := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader)) + + client := tt.clientFunc(mp) + req, err := http.NewRequest("POST", "http://example.com", nil) + assert.NoError(t, err) + + client.RecordMetrics(context.Background(), semconv.MetricData{ + RequestSize: 100, + ElapsedTime: 300, + }, client.MetricOptions(semconv.MetricAttributes{ + Req: req, + StatusCode: 301, + })) + + rm := metricdata.ResourceMetrics{} + require.NoError(t, reader.Collect(context.Background(), &rm)) + tt.wantFunc(t, rm) + }) + } +} + +type customError struct{} + +func (customError) Error() string { + return "custom error" +} diff --git a/instrumentation/github.com/labstack/echo/otelecho/internal/semconv/util.go b/instrumentation/github.com/labstack/echo/otelecho/internal/semconv/util.go new file mode 100644 index 00000000000..477f32b1768 --- /dev/null +++ b/instrumentation/github.com/labstack/echo/otelecho/internal/semconv/util.go @@ -0,0 +1,127 @@ +// Code generated by gotmpl. DO NOT MODIFY. +// source: internal/shared/semconv/util.go.tmpl + +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package semconv // import "go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho/internal/semconv" + +import ( + "net" + "net/http" + "strconv" + "strings" + + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + semconvNew "go.opentelemetry.io/otel/semconv/v1.34.0" +) + +// SplitHostPort splits a network address hostport of the form "host", +// "host%zone", "[host]", "[host%zone], "host:port", "host%zone:port", +// "[host]:port", "[host%zone]:port", or ":port" into host or host%zone and +// port. +// +// An empty host is returned if it is not provided or unparsable. A negative +// port is returned if it is not provided or unparsable. +func SplitHostPort(hostport string) (host string, port int) { + port = -1 + + if strings.HasPrefix(hostport, "[") { + addrEnd := strings.LastIndexByte(hostport, ']') + if addrEnd < 0 { + // Invalid hostport. + return + } + if i := strings.LastIndexByte(hostport[addrEnd:], ':'); i < 0 { + host = hostport[1:addrEnd] + return + } + } else { + if i := strings.LastIndexByte(hostport, ':'); i < 0 { + host = hostport + return + } + } + + host, pStr, err := net.SplitHostPort(hostport) + if err != nil { + return + } + + p, err := strconv.ParseUint(pStr, 10, 16) + if err != nil { + return + } + return host, int(p) // nolint: gosec // Byte size checked 16 above. +} + +func requiredHTTPPort(https bool, port int) int { // nolint:revive + if https { + if port > 0 && port != 443 { + return port + } + } else { + if port > 0 && port != 80 { + return port + } + } + return -1 +} + +func serverClientIP(xForwardedFor string) string { + if idx := strings.IndexByte(xForwardedFor, ','); idx >= 0 { + xForwardedFor = xForwardedFor[:idx] + } + return xForwardedFor +} + +func httpRoute(pattern string) string { + if idx := strings.IndexByte(pattern, '/'); idx >= 0 { + return pattern[idx:] + } + return "" +} + +func netProtocol(proto string) (name string, version string) { + name, version, _ = strings.Cut(proto, "/") + switch name { + case "HTTP": + name = "http" + case "QUIC": + name = "quic" + case "SPDY": + name = "spdy" + default: + name = strings.ToLower(name) + } + return name, version +} + +var methodLookup = map[string]attribute.KeyValue{ + http.MethodConnect: semconvNew.HTTPRequestMethodConnect, + http.MethodDelete: semconvNew.HTTPRequestMethodDelete, + http.MethodGet: semconvNew.HTTPRequestMethodGet, + http.MethodHead: semconvNew.HTTPRequestMethodHead, + http.MethodOptions: semconvNew.HTTPRequestMethodOptions, + http.MethodPatch: semconvNew.HTTPRequestMethodPatch, + http.MethodPost: semconvNew.HTTPRequestMethodPost, + http.MethodPut: semconvNew.HTTPRequestMethodPut, + http.MethodTrace: semconvNew.HTTPRequestMethodTrace, +} + +func handleErr(err error) { + if err != nil { + 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/github.com/labstack/echo/otelecho/internal/semconv/util_test.go b/instrumentation/github.com/labstack/echo/otelecho/internal/semconv/util_test.go new file mode 100644 index 00000000000..9e566092752 --- /dev/null +++ b/instrumentation/github.com/labstack/echo/otelecho/internal/semconv/util_test.go @@ -0,0 +1,75 @@ +// Code generated by gotmpl. DO NOT MODIFY. +// source: internal/shared/semconv/util_test.go.tmpl + +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package semconv + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestSplitHostPort(t *testing.T) { + tests := []struct { + hostport string + host string + port int + }{ + {"", "", -1}, + {":8080", "", 8080}, + {"127.0.0.1", "127.0.0.1", -1}, + {"www.example.com", "www.example.com", -1}, + {"127.0.0.1%25en0", "127.0.0.1%25en0", -1}, + {"[]", "", -1}, // Ensure this doesn't panic. + {"[fe80::1", "", -1}, + {"[fe80::1]", "fe80::1", -1}, + {"[fe80::1%25en0]", "fe80::1%25en0", -1}, + {"[fe80::1]:8080", "fe80::1", 8080}, + {"[fe80::1]::", "", -1}, // Too many colons. + {"127.0.0.1:", "127.0.0.1", -1}, + {"127.0.0.1:port", "127.0.0.1", -1}, + {"127.0.0.1:8080", "127.0.0.1", 8080}, + {"www.example.com:8080", "www.example.com", 8080}, + {"127.0.0.1%25en0:8080", "127.0.0.1%25en0", 8080}, + } + + for _, test := range tests { + h, p := SplitHostPort(test.hostport) + assert.Equal(t, test.host, h, test.hostport) + 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)) + } +} From c1c24b4f030480ab9efb05ac86ab6b24b3abcd84 Mon Sep 17 00:00:00 2001 From: dmathieu <42@dmathieu.com> Date: Tue, 15 Jul 2025 14:35:58 +0200 Subject: [PATCH 3/7] fix missed conversions in gin and echo --- .../github.com/gin-gonic/gin/otelgin/gin.go | 14 ++++---------- .../github.com/labstack/echo/otelecho/echo.go | 10 +++++----- .../labstack/echo/otelecho/example/go.sum | 2 ++ .../github.com/labstack/echo/otelecho/go.mod | 3 ++- .../github.com/labstack/echo/otelecho/go.sum | 2 ++ 5 files changed, 15 insertions(+), 16 deletions(-) diff --git a/instrumentation/github.com/gin-gonic/gin/otelgin/gin.go b/instrumentation/github.com/gin-gonic/gin/otelgin/gin.go index a157d5d6c54..c7cf1c67c9e 100644 --- a/instrumentation/github.com/gin-gonic/gin/otelgin/gin.go +++ b/instrumentation/github.com/gin-gonic/gin/otelgin/gin.go @@ -14,7 +14,6 @@ import ( "go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconv" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/propagation" oteltrace "go.opentelemetry.io/otel/trace" @@ -113,15 +112,10 @@ func Middleware(service string, opts ...Option) gin.HandlerFunc { status := c.Writer.Status() span.SetStatus(sc.Status(status)) - if status > 0 { - span.SetAttributes(semconv.HTTPStatusCode(status)) - } - if len(c.Errors) > 0 { - span.SetStatus(codes.Error, c.Errors.String()) - for _, err := range c.Errors { - span.RecordError(err.Err) - } - } + span.SetAttributes(sc.ResponseTraceAttrs(semconv.ResponseTelemetry{ + StatusCode: status, + WriteBytes: int64(c.Writer.Size()), + })...) // Record the server-side attributes. var additionalAttributes []attribute.KeyValue diff --git a/instrumentation/github.com/labstack/echo/otelecho/echo.go b/instrumentation/github.com/labstack/echo/otelecho/echo.go index 22fbe110393..670ee787517 100644 --- a/instrumentation/github.com/labstack/echo/otelecho/echo.go +++ b/instrumentation/github.com/labstack/echo/otelecho/echo.go @@ -64,7 +64,7 @@ func Middleware(service string, opts ...Option) echo.MiddlewareFunc { ctx := cfg.Propagators.Extract(savedCtx, propagation.HeaderCarrier(request.Header)) opts := []oteltrace.SpanStartOption{ oteltrace.WithAttributes( - semconvSrv.RequestTraceAttrs(service, request, semconv.RequestTraceAttrsOpts{}...), + semconvSrv.RequestTraceAttrs(service, request, semconv.RequestTraceAttrsOpts{})..., ), oteltrace.WithSpanKind(oteltrace.SpanKindServer), } @@ -89,10 +89,10 @@ func Middleware(service string, opts ...Option) echo.MiddlewareFunc { } status := c.Response().Status - span.SetStatus(semconvutil.HTTPServerStatus(status)) - if status > 0 { - span.SetAttributes(semconvSrv.StatusCode(status)) - } + span.SetStatus(semconvSrv.Status(status)) + span.SetAttributes(semconvSrv.ResponseTraceAttrs(semconv.ResponseTelemetry{ + StatusCode: status, + })...) return err } diff --git a/instrumentation/github.com/labstack/echo/otelecho/example/go.sum b/instrumentation/github.com/labstack/echo/otelecho/example/go.sum index 92033d32dba..4ffb477659b 100644 --- a/instrumentation/github.com/labstack/echo/otelecho/example/go.sum +++ b/instrumentation/github.com/labstack/echo/otelecho/example/go.sum @@ -35,6 +35,8 @@ go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/Wgbsd go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= +go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= +go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= diff --git a/instrumentation/github.com/labstack/echo/otelecho/go.mod b/instrumentation/github.com/labstack/echo/otelecho/go.mod index dab05c6bc47..151c7b18ded 100644 --- a/instrumentation/github.com/labstack/echo/otelecho/go.mod +++ b/instrumentation/github.com/labstack/echo/otelecho/go.mod @@ -9,7 +9,9 @@ require ( github.com/stretchr/testify v1.10.0 go.opentelemetry.io/contrib/propagators/b3 v1.37.0 go.opentelemetry.io/otel v1.37.0 + go.opentelemetry.io/otel/metric v1.37.0 go.opentelemetry.io/otel/sdk v1.37.0 + go.opentelemetry.io/otel/sdk/metric v1.37.0 go.opentelemetry.io/otel/trace v1.37.0 ) @@ -25,7 +27,6 @@ require ( github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/otel/metric v1.37.0 // indirect golang.org/x/crypto v0.40.0 // indirect golang.org/x/net v0.42.0 // indirect golang.org/x/sys v0.34.0 // indirect diff --git a/instrumentation/github.com/labstack/echo/otelecho/go.sum b/instrumentation/github.com/labstack/echo/otelecho/go.sum index 6b513bb6980..811bd79f07a 100644 --- a/instrumentation/github.com/labstack/echo/otelecho/go.sum +++ b/instrumentation/github.com/labstack/echo/otelecho/go.sum @@ -39,6 +39,8 @@ go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/Wgbsd go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= +go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= +go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= From 2a170fb94ed75ab5809df820bbf2fe7d348f63af Mon Sep 17 00:00:00 2001 From: dmathieu <42@dmathieu.com> Date: Tue, 15 Jul 2025 14:38:49 +0200 Subject: [PATCH 4/7] add changelog entry --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c20ff446be..3ad0f6ebf18 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,16 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - Add the `WithLoggerProviderOptions`, `WithMeterProviderOptions` and `WithTracerProviderOptions` options to `NewSDK` to allow passing custom options to providers in `go.opentelemetry.io/contrib/otelconf`. (#7552) - Added V2 version of AWS EC2 detector `go.opentelemetry.io/contrib/detectors/aws/ec2/v2` due to deprecation of `github.com/aws/aws-sdk-go`. (#6961) +### Removed + +- Support for the `OTEL_SEMCONV_STABILITY_OPT_IN` environment variable, and support for semantic conventions v1.20.0 in the modules below. (#7584) + - `go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful` + - `go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin` + - `go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux` + - `go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho` + - `go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace` + - `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp` + From ca41630b8ffb9668c59608fd0b45175d34ba1a98 Mon Sep 17 00:00:00 2001 From: dmathieu <42@dmathieu.com> Date: Tue, 15 Jul 2025 14:49:05 +0200 Subject: [PATCH 5/7] fix echo tests --- .../github.com/gin-gonic/gin/otelgin/gin.go | 8 ++++ .../gin-gonic/gin/otelgin/gin_test.go | 48 +------------------ .../labstack/echo/otelecho/echotest_test.go | 16 +++---- 3 files changed, 18 insertions(+), 54 deletions(-) diff --git a/instrumentation/github.com/gin-gonic/gin/otelgin/gin.go b/instrumentation/github.com/gin-gonic/gin/otelgin/gin.go index c7cf1c67c9e..c5a9f9cc2b0 100644 --- a/instrumentation/github.com/gin-gonic/gin/otelgin/gin.go +++ b/instrumentation/github.com/gin-gonic/gin/otelgin/gin.go @@ -14,6 +14,7 @@ import ( "go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconv" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/propagation" oteltrace "go.opentelemetry.io/otel/trace" @@ -117,6 +118,13 @@ func Middleware(service string, opts ...Option) gin.HandlerFunc { WriteBytes: int64(c.Writer.Size()), })...) + if len(c.Errors) > 0 { + span.SetStatus(codes.Error, c.Errors.String()) + for _, err := range c.Errors { + span.RecordError(err.Err) + } + } + // Record the server-side attributes. var additionalAttributes []attribute.KeyValue if c.FullPath() != "" { diff --git a/instrumentation/github.com/gin-gonic/gin/otelgin/gin_test.go b/instrumentation/github.com/gin-gonic/gin/otelgin/gin_test.go index 6980be56602..e8af40aea5f 100644 --- a/instrumentation/github.com/gin-gonic/gin/otelgin/gin_test.go +++ b/instrumentation/github.com/gin-gonic/gin/otelgin/gin_test.go @@ -21,7 +21,6 @@ import ( "github.com/stretchr/testify/require" "go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin" - "go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconv" b3prop "go.opentelemetry.io/contrib/propagators/b3" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" @@ -167,7 +166,7 @@ func TestTrace200(t *testing.T) { assert.Equal(t, trace.SpanKindServer, span.SpanKind()) attr := span.Attributes() assert.Contains(t, attr, attribute.String("server.address", "foobar")) - assert.Contains(t, attr, attribute.Int("http.status_code", http.StatusOK)) + assert.Contains(t, attr, attribute.Int("http.response.status_code", http.StatusOK)) assert.Contains(t, attr, attribute.String("http.request.method", "GET")) assert.Contains(t, attr, attribute.String("http.route", "/user/:id")) assert.Empty(t, span.Events()) @@ -202,7 +201,7 @@ func TestError(t *testing.T) { assert.Equal(t, "GET /server_err", span.Name()) attr := span.Attributes() assert.Contains(t, attr, attribute.String("server.address", "foobar")) - assert.Contains(t, attr, attribute.Int("http.status_code", http.StatusInternalServerError)) + assert.Contains(t, attr, attribute.Int("http.response.status_code", http.StatusInternalServerError)) // verify the error events events := span.Events() @@ -620,46 +619,3 @@ func TestMetrics(t *testing.T) { }) } } - -func TestServerWithSemConvStabilityOptIn(t *testing.T) { - tests := []struct { - name string - setEnv bool - wantExistsHTTPMethod bool - }{ - { - "not set", - false, - false, - }, - { - "set to http/dup", - true, - true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.setEnv { - t.Setenv(semconv.OTelSemConvStabilityOptIn, "http/dup") - } - - attrs := semconv.NewHTTPServer(nil). - RequestTraceAttrs("foobar", - httptest.NewRequest("GET", "/user/123", nil), - semconv.RequestTraceAttrsOpts{ - HTTPClientIP: "127.0.0.1", - }) - - var existsHTTPMethod bool - for _, attr := range attrs { - if attr.Key == "http.method" { - existsHTTPMethod = true - break - } - } - assert.Equal(t, tt.wantExistsHTTPMethod, existsHTTPMethod) - }) - } -} diff --git a/instrumentation/github.com/labstack/echo/otelecho/echotest_test.go b/instrumentation/github.com/labstack/echo/otelecho/echotest_test.go index bf8c91df26f..cf9b55bf9c8 100644 --- a/instrumentation/github.com/labstack/echo/otelecho/echotest_test.go +++ b/instrumentation/github.com/labstack/echo/otelecho/echotest_test.go @@ -87,9 +87,9 @@ func TestTrace200(t *testing.T) { assert.Equal(t, "GET /user/:id", span.Name()) assert.Equal(t, oteltrace.SpanKindServer, span.SpanKind()) attrs := span.Attributes() - assert.Contains(t, attrs, attribute.String("net.host.name", "foobar")) - assert.Contains(t, attrs, attribute.Int("http.status_code", http.StatusOK)) - assert.Contains(t, attrs, attribute.String("http.method", "GET")) + assert.Contains(t, attrs, attribute.String("server.address", "foobar")) + assert.Contains(t, attrs, attribute.Int("http.response.status_code", http.StatusOK)) + assert.Contains(t, attrs, attribute.String("http.request.method", "GET")) assert.Contains(t, attrs, attribute.String("http.route", "/user/:id")) } @@ -118,8 +118,8 @@ func TestError(t *testing.T) { span := spans[0] assert.Equal(t, "GET /server_err", span.Name()) attrs := span.Attributes() - assert.Contains(t, attrs, attribute.String("net.host.name", "foobar")) - assert.Contains(t, attrs, attribute.Int("http.status_code", http.StatusInternalServerError)) + assert.Contains(t, attrs, attribute.String("server.address", "foobar")) + assert.Contains(t, attrs, attribute.Int("http.response.status_code", http.StatusInternalServerError)) assert.Contains(t, attrs, attribute.String("echo.error", "oh no")) // server errors set the status assert.Equal(t, codes.Error, span.Status().Code) @@ -179,10 +179,10 @@ func TestStatusError(t *testing.T) { assert.Equal(t, tc.spanCode, span.Status().Code) attrs := span.Attributes() - assert.Contains(t, attrs, attribute.String("net.host.name", "foobar")) + assert.Contains(t, attrs, attribute.String("server.address", "foobar")) assert.Contains(t, attrs, attribute.String("http.route", "/err")) - assert.Contains(t, attrs, attribute.String("http.method", "GET")) - assert.Contains(t, attrs, attribute.Int("http.status_code", tc.statusCode)) + assert.Contains(t, attrs, attribute.String("http.request.method", "GET")) + assert.Contains(t, attrs, attribute.Int("http.response.status_code", tc.statusCode)) assert.Contains(t, attrs, attribute.String("echo.error", tc.echoError)) }) } From 21b379fe341ef4963f33f1fe6d65dc6a743e8a27 Mon Sep 17 00:00:00 2001 From: dmathieu <42@dmathieu.com> Date: Tue, 15 Jul 2025 14:57:30 +0200 Subject: [PATCH 6/7] fix otelhttp tests --- .../net/http/otelhttp/handler_test.go | 154 ------------- .../net/http/otelhttp/transport.go | 5 + .../net/http/otelhttp/transport_test.go | 205 +----------------- 3 files changed, 6 insertions(+), 358 deletions(-) diff --git a/instrumentation/net/http/otelhttp/handler_test.go b/instrumentation/net/http/otelhttp/handler_test.go index 33f0016e004..ee32914a65b 100644 --- a/instrumentation/net/http/otelhttp/handler_test.go +++ b/instrumentation/net/http/otelhttp/handler_test.go @@ -795,160 +795,6 @@ func TestHandlerWithMetricAttributesFn(t *testing.T) { } } -func TestHandlerWithSemConvStabilityOptIn(t *testing.T) { - newSpanAttrs := []attribute.KeyValue{ - attribute.String("http.request.method", "GET"), - attribute.String("url.scheme", "http"), - attribute.String("server.address", "localhost"), - attribute.String("network.protocol.version", "1.1"), - attribute.String("url.path", "/"), - attribute.Int("http.response.status_code", 200), - } - newMetricAttrs := attribute.NewSet( - attribute.String("http.request.method", "GET"), - attribute.Int("http.response.status_code", 200), - attribute.String("network.protocol.name", "http"), - attribute.String("network.protocol.version", "1.1"), - attribute.String("server.address", "localhost"), - attribute.String("url.scheme", "http"), - ) - newMetrics := []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: newMetricAttrs}}, - }, - }, - { - 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: newMetricAttrs}}, - }, - }, - { - 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: newMetricAttrs}}, - }, - }, - } - oldSpanAttrs := []attribute.KeyValue{ - attribute.String("http.method", "GET"), - attribute.String("http.scheme", "http"), - attribute.String("net.host.name", "localhost"), - attribute.String("net.protocol.version", "1.1"), - attribute.String("http.target", "/"), - attribute.Int("http.status_code", 200), - } - oldMetricAttrs := attribute.NewSet( - attribute.String("http.method", "GET"), - attribute.String("http.scheme", "http"), - attribute.Int("http.status_code", 200), - attribute.String("net.host.name", "localhost"), - attribute.String("net.protocol.name", "http"), - attribute.String("net.protocol.version", "1.1"), - ) - oldMetrics := []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: oldMetricAttrs}}, - }, - }, - { - 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: oldMetricAttrs}}, - }, - }, - { - 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: oldMetricAttrs}}, - }, - }, - } - tests := []struct { - name string - semConvStabilityOptInValue string - wantSpanAttributes []attribute.KeyValue - wantMetrics metricdata.ScopeMetrics - }{ - { - name: "without opt-in", - semConvStabilityOptInValue: "", - wantSpanAttributes: newSpanAttrs, - wantMetrics: metricdata.ScopeMetrics{ - Scope: instrumentation.Scope{ - Name: ScopeName, - Version: Version(), - }, - Metrics: newMetrics, - }, - }, - { - name: "with http/dup opt-in", - semConvStabilityOptInValue: "http/dup", - wantSpanAttributes: append(newSpanAttrs, oldSpanAttrs...), - wantMetrics: metricdata.ScopeMetrics{ - Scope: instrumentation.Scope{ - Name: ScopeName, - Version: Version(), - }, - Metrics: append(newMetrics, oldMetrics...), - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - t.Setenv("OTEL_SEMCONV_STABILITY_OPT_IN", tt.semConvStabilityOptInValue) - metricRecorder := sdkmetric.NewManualReader() - spanRecorder := tracetest.NewSpanRecorder() - meterProvider := sdkmetric.NewMeterProvider(sdkmetric.WithReader(metricRecorder)) - traceProvider := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(spanRecorder)) - h := NewHandler( - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusOK) - }), - "test_handler", - WithTracerProvider(traceProvider), - WithMeterProvider(meterProvider), - ) - r, err := http.NewRequest(http.MethodGet, "http://localhost/", nil) - require.NoError(t, err) - h.ServeHTTP(httptest.NewRecorder(), r) - spans := spanRecorder.Ended() - require.Len(t, spans, 1) - assert.ElementsMatch(t, spans[0].Attributes(), tt.wantSpanAttributes) - rm := metricdata.ResourceMetrics{} - err = metricRecorder.Collect(context.Background(), &rm) - require.NoError(t, err) - require.Len(t, rm.ScopeMetrics, 1) - metricdatatest.AssertEqual(t, tt.wantMetrics, rm.ScopeMetrics[0], metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreValue(), metricdatatest.IgnoreExemplars()) - }) - } -} - func BenchmarkHandlerServeHTTP(b *testing.B) { tp := sdktrace.NewTracerProvider() mp := sdkmetric.NewMeterProvider() diff --git a/instrumentation/net/http/otelhttp/transport.go b/instrumentation/net/http/otelhttp/transport.go index aed3014788c..290c7bc4c0e 100644 --- a/instrumentation/net/http/otelhttp/transport.go +++ b/instrumentation/net/http/otelhttp/transport.go @@ -147,6 +147,11 @@ func (t *Transport) RoundTrip(r *http.Request) (*http.Response, error) { RequestSize: bw.BytesRead(), } + if err == nil { + readRecordFunc := func(n int64) {} + res.Body = newWrappedBody(span, readRecordFunc, res.Body) + } + // Use floating point division here for higher precision (instead of Millisecond method). elapsedTime := float64(time.Since(requestStartTime)) / float64(time.Millisecond) diff --git a/instrumentation/net/http/otelhttp/transport_test.go b/instrumentation/net/http/otelhttp/transport_test.go index fa05936ce76..d24fce8d5c8 100644 --- a/instrumentation/net/http/otelhttp/transport_test.go +++ b/instrumentation/net/http/otelhttp/transport_test.go @@ -479,6 +479,7 @@ func TestTransportUsesFormatter(t *testing.T) { require.NoError(t, res.Body.Close()) spans := spanRecorder.Ended() + require.NotEmpty(t, spans) spanName := spans[0].Name() expectedName := "HTTP GET" if spanName != expectedName { @@ -486,82 +487,6 @@ func TestTransportUsesFormatter(t *testing.T) { } } -func TestTransportWithSemConvStabilityOptIn(t *testing.T) { - tests := []struct { - name string - semConvStabilityOptInValue string - expected func(host string, port int) []attribute.KeyValue - }{ - { - name: "without opt-in", - semConvStabilityOptInValue: "", - expected: func(host string, port int) []attribute.KeyValue { - return []attribute.KeyValue{ - attribute.String("http.request.method", "GET"), - attribute.String("url.full", fmt.Sprintf("http://%s:%d", host, port)), - attribute.String("server.address", host), - attribute.Int("server.port", port), - attribute.String("network.protocol.version", "1.1"), - attribute.Int("http.response.status_code", 200), - } - }, - }, - { - name: "with http/dup opt-in", - semConvStabilityOptInValue: "http/dup", - expected: func(host string, port int) []attribute.KeyValue { - return []attribute.KeyValue{ - // New semantic conventions - attribute.String("http.request.method", "GET"), - attribute.String("url.full", fmt.Sprintf("http://%s:%d", host, port)), - attribute.String("server.address", host), - attribute.Int("server.port", port), - attribute.String("network.protocol.version", "1.1"), - // Old semantic conventions - attribute.String("http.method", "GET"), - attribute.String("http.url", fmt.Sprintf("http://%s:%d", host, port)), - attribute.String("net.peer.name", host), - attribute.Int("net.peer.port", port), - attribute.Int("http.response.status_code", 200), - attribute.Int("http.status_code", 200), - } - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - t.Setenv("OTEL_SEMCONV_STABILITY_OPT_IN", tt.semConvStabilityOptInValue) - spanRecorder := tracetest.NewSpanRecorder() - provider := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(spanRecorder)) - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - })) - defer ts.Close() - - r, err := http.NewRequest(http.MethodGet, ts.URL, nil) - require.NoError(t, err) - - host, portStr, err := net.SplitHostPort(strings.TrimPrefix(ts.URL, "http://")) - require.NoError(t, err) - port, err := strconv.Atoi(portStr) - require.NoError(t, err) - - c := http.Client{Transport: NewTransport( - http.DefaultTransport, - WithTracerProvider(provider), - )} - resp, err := c.Do(r) - require.NoError(t, err) - _ = resp.Body.Close() - - spans := spanRecorder.Ended() - require.Len(t, spans, 1) - attrs := spans[0].Attributes() - assert.ElementsMatch(t, attrs, tt.expected(host, port)) - }) - } -} - func TestTransportErrorStatus(t *testing.T) { // Prepare tracing stuff. spanRecorder := tracetest.NewSpanRecorder() @@ -931,134 +856,6 @@ func TestTransportMetrics(t *testing.T) { ) assertClientScopeMetrics(t, rm.ScopeMetrics[0], attrs) }) - - t.Run("make http request with http/dup opt-in and check both new and old metrics", func(t *testing.T) { - t.Setenv("OTEL_SEMCONV_STABILITY_OPT_IN", "http/dup") - reader := sdkmetric.NewManualReader() - meterProvider := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader)) - - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - _, err := w.Write(responseBody) - assert.NoError(t, err) - })) - defer ts.Close() - - tr := NewTransport( - http.DefaultTransport, - WithMeterProvider(meterProvider), - ) - c := http.Client{Transport: tr} - r, err := http.NewRequest(http.MethodGet, ts.URL, bytes.NewReader(requestBody)) - require.NoError(t, err) - res, err := c.Do(r) - require.NoError(t, err) - _, err = io.ReadAll(res.Body) - require.NoError(t, err) - require.NoError(t, res.Body.Close()) - host, portStr, err := net.SplitHostPort(r.Host) - require.NoError(t, err) - port, err := strconv.Atoi(portStr) - require.NoError(t, err) - - attrsNew := attribute.NewSet( - attribute.String("http.request.method", "GET"), - attribute.Int("http.response.status_code", 200), - attribute.String("server.address", host), - attribute.Int("server.port", port), - attribute.String("url.scheme", "http"), - attribute.String("network.protocol.name", "http"), - attribute.String("network.protocol.version", "1.1"), - ) - attrsOld := attribute.NewSet( - attribute.String("http.method", "GET"), - attribute.Int("http.status_code", 200), - attribute.String("net.peer.name", host), - attribute.Int("net.peer.port", port), - ) - - rm := metricdata.ResourceMetrics{} - err = reader.Collect(context.Background(), &rm) - require.NoError(t, err) - require.Len(t, rm.ScopeMetrics, 1) - - expected := metricdata.ScopeMetrics{ - Scope: instrumentation.Scope{ - Name: ScopeName, - Version: Version(), - }, - Metrics: []metricdata.Metrics{ - { - Name: "http.client.request.body.size", - Description: "Size of HTTP client request bodies.", - Unit: "By", - Data: metricdata.Histogram[int64]{ - Temporality: metricdata.CumulativeTemporality, - DataPoints: []metricdata.HistogramDataPoint[int64]{ - { - Attributes: attrsNew, - }, - }, - }, - }, - { - Name: "http.client.request.duration", - Description: "Duration of HTTP client requests.", - Unit: "s", - Data: metricdata.Histogram[float64]{ - Temporality: metricdata.CumulativeTemporality, - DataPoints: []metricdata.HistogramDataPoint[float64]{ - { - Attributes: attrsNew, - }, - }, - }, - }, - { - Name: "http.client.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: attrsOld, - }, - }, - }, - }, - { - Name: "http.client.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: attrsOld, - }, - }, - }, - }, - { - Name: "http.client.duration", - Description: "Measures the duration of outbound HTTP requests.", - Unit: "ms", - Data: metricdata.Histogram[float64]{ - Temporality: metricdata.CumulativeTemporality, - DataPoints: []metricdata.HistogramDataPoint[float64]{ - { - Attributes: attrsOld, - }, - }, - }, - }, - }, - } - metricdatatest.AssertEqual(t, expected, rm.ScopeMetrics[0], metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreValue()) - }) } func assertClientScopeMetrics(t *testing.T, sm metricdata.ScopeMetrics, attrs attribute.Set) { From 0fcaea7f8c5b16bb7b1be0f01adc6b1f3ef19b7d Mon Sep 17 00:00:00 2001 From: Damien Mathieu <42@dmathieu.com> Date: Wed, 16 Jul 2025 11:38:00 +0200 Subject: [PATCH 7/7] Update CHANGELOG.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Robert PajÄ…k --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ad0f6ebf18..1a066f1da71 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,7 +18,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Removed -- Support for the `OTEL_SEMCONV_STABILITY_OPT_IN` environment variable, and support for semantic conventions v1.20.0 in the modules below. (#7584) +- Remove support for the `OTEL_SEMCONV_STABILITY_OPT_IN` environment variable as well as support for semantic conventions v1.20.0 in the modules below. (#7584) - `go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful` - `go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin` - `go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux`