diff --git a/CHANGELOG.md b/CHANGELOG.md index 332f4965ac4..bd2a1b02c8d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,13 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - `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` +- Improve performance by reducing allocations for http request when using `OTEL_SEMCONV_STABILITY_OPT_IN=http/dup` in the modules below. (#7180) + - `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` The `OTEL_SEMCONV_STABILITY_OPT_IN=http/dup` environment variable can be still used to emit both the v1.20.0 and v1.26.0 semantic conventions. It is however impossible to emit only the 1.20.0 semantic conventions, as the next release will drop support for that environment variable. (#6899) 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 6cc309edee8..91a3767e0cd 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 @@ -62,15 +62,19 @@ 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 append(OldHTTPServer{}.RequestTraceAttrs(server, req), CurrentHTTPServer{}.RequestTraceAttrs(server, req, opts)...) + return OldHTTPServer{}.RequestTraceAttrs(server, req, attrs) } - return CurrentHTTPServer{}.RequestTraceAttrs(server, req, opts) + return attrs } func (s HTTPServer) NetworkTransportAttr(network string) []attribute.KeyValue { if s.duplicate { - return append([]attribute.KeyValue{OldHTTPServer{}.NetworkTransportAttr(network)}, CurrentHTTPServer{}.NetworkTransportAttr(network)) + return []attribute.KeyValue{ + OldHTTPServer{}.NetworkTransportAttr(network), + CurrentHTTPServer{}.NetworkTransportAttr(network), + } } return []attribute.KeyValue{ CurrentHTTPServer{}.NetworkTransportAttr(network), @@ -81,10 +85,11 @@ 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 append(OldHTTPServer{}.ResponseTraceAttrs(resp), CurrentHTTPServer{}.ResponseTraceAttrs(resp)...) + return OldHTTPServer{}.ResponseTraceAttrs(resp, attrs) } - return CurrentHTTPServer{}.ResponseTraceAttrs(resp) + return attrs } // Route returns the attribute for the route. @@ -208,19 +213,20 @@ func NewHTTPClient(meter metric.Meter) HTTPClient { // 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 append(OldHTTPClient{}.RequestTraceAttrs(req), CurrentHTTPClient{}.RequestTraceAttrs(req)...) + return OldHTTPClient{}.RequestTraceAttrs(req, attrs) } - return CurrentHTTPClient{}.RequestTraceAttrs(req) + return attrs } // 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 append(OldHTTPClient{}.ResponseTraceAttrs(resp), CurrentHTTPClient{}.ResponseTraceAttrs(resp)...) + return OldHTTPClient{}.ResponseTraceAttrs(resp, attrs) } - - return CurrentHTTPClient{}.ResponseTraceAttrs(resp) + return attrs } func (c HTTPClient) Status(code int) (codes.Code, string) { @@ -297,9 +303,10 @@ func (s HTTPClient) RecordResponseSize(ctx context.Context, responseData int64, } func (s HTTPClient) TraceAttributes(host string) []attribute.KeyValue { + attrs := CurrentHTTPClient{}.TraceAttributes(host) if s.duplicate { - return append(OldHTTPClient{}.TraceAttributes(host), CurrentHTTPClient{}.TraceAttributes(host)...) + return OldHTTPClient{}.TraceAttributes(host, attrs) } - return CurrentHTTPClient{}.TraceAttributes(host) + return attrs } 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 39b243ba915..afa6db6c20c 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 @@ -151,8 +151,8 @@ func TestHTTPClientTraceAttributes(t *testing.T) { optinVal: "http/dup", wantAttributes: []attribute.KeyValue{ - attribute.String("net.host.name", "example.com"), attribute.String("server.address", "example.com"), + attribute.String("net.host.name", "example.com"), }, }, } { @@ -188,8 +188,8 @@ func TestClientTraceAttributes(t *testing.T) { host: "example.com", wantAttributes: []attribute.KeyValue{ - attribute.String("net.host.name", "example.com"), attribute.String("server.address", "example.com"), + attribute.String("net.host.name", "example.com"), }, }, } { 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 index e3d5ff2afdb..a831dc766b5 100644 --- 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 @@ -37,8 +37,8 @@ type OldHTTPServer 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 (o OldHTTPServer) RequestTraceAttrs(server string, req *http.Request) []attribute.KeyValue { - return semconvutil.HTTPServerRequest(server, req, semconvutil.HTTPServerRequestOptions{}) +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 { @@ -48,9 +48,7 @@ func (o OldHTTPServer) NetworkTransportAttr(network string) attribute.KeyValue { // 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) []attribute.KeyValue { - attributes := []attribute.KeyValue{} - +func (o OldHTTPServer) ResponseTraceAttrs(resp ResponseTelemetry, attributes []attribute.KeyValue) []attribute.KeyValue { if resp.ReadBytes > 0 { attributes = append(attributes, semconv.HTTPRequestContentLength(int(resp.ReadBytes))) } @@ -179,12 +177,12 @@ func (o OldHTTPServer) scheme(https bool) attribute.KeyValue { // nolint:revive type OldHTTPClient struct{} -func (o OldHTTPClient) RequestTraceAttrs(req *http.Request) []attribute.KeyValue { - return semconvutil.HTTPClientRequest(req) +func (o OldHTTPClient) RequestTraceAttrs(req *http.Request, attrs []attribute.KeyValue) []attribute.KeyValue { + return semconvutil.HTTPClientRequest(req, attrs) } -func (o OldHTTPClient) ResponseTraceAttrs(resp *http.Response) []attribute.KeyValue { - return semconvutil.HTTPClientResponse(resp) +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 { @@ -270,8 +268,6 @@ func (o OldHTTPClient) createMeasures(meter metric.Meter) (metric.Int64Counter, } // TraceAttributes returns attributes for httptrace. -func (c OldHTTPClient) TraceAttributes(host string) []attribute.KeyValue { - return []attribute.KeyValue{ - semconv.NetHostName(host), - } +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/httpconv.go b/instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconvutil/httpconv.go index 0dea7a5f15b..dbe89bbeef9 100644 --- a/instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconvutil/httpconv.go +++ b/instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconvutil/httpconv.go @@ -10,6 +10,7 @@ package semconvutil // import "go.opentelemetry.io/contrib/instrumentation/githu import ( "fmt" "net/http" + "slices" "strings" "go.opentelemetry.io/otel/attribute" @@ -32,9 +33,9 @@ type HTTPServerRequestOptions struct { // attributes. If a complete set of attributes can be generated using the // request contained in resp. For example: // -// append(HTTPClientResponse(resp), ClientRequest(resp.Request)...) -func HTTPClientResponse(resp *http.Response) []attribute.KeyValue { - return hc.ClientResponse(resp) +// 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. @@ -42,8 +43,8 @@ func HTTPClientResponse(resp *http.Response) []attribute.KeyValue { // "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) []attribute.KeyValue { - return hc.ClientRequest(req) +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. @@ -81,8 +82,8 @@ func HTTPClientStatus(code int) (codes.Code, string) { // "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) []attribute.KeyValue { - return hc.ServerRequest(server, req, opts) +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 @@ -159,8 +160,8 @@ var hc = &httpConv{ // attributes. If a complete set of attributes can be generated using the // request contained in resp. For example: // -// append(ClientResponse(resp), ClientRequest(resp.Request)...) -func (c *httpConv) ClientResponse(resp *http.Response) []attribute.KeyValue { +// 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 @@ -172,8 +173,11 @@ func (c *httpConv) ClientResponse(resp *http.Response) []attribute.KeyValue { if resp.ContentLength > 0 { n++ } + if n == 0 { + return attrs + } - attrs := make([]attribute.KeyValue, 0, n) + attrs = slices.Grow(attrs, n) if resp.StatusCode > 0 { attrs = append(attrs, c.HTTPStatusCodeKey.Int(resp.StatusCode)) } @@ -188,7 +192,7 @@ func (c *httpConv) ClientResponse(resp *http.Response) []attribute.KeyValue { // "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) []attribute.KeyValue { +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 @@ -227,8 +231,7 @@ func (c *httpConv) ClientRequest(req *http.Request) []attribute.KeyValue { n++ } - attrs := make([]attribute.KeyValue, 0, n) - + attrs = slices.Grow(attrs, n) attrs = append(attrs, c.method(req.Method)) var u string @@ -311,7 +314,7 @@ func (c *httpConv) ClientRequestMetrics(req *http.Request) []attribute.KeyValue // 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) []attribute.KeyValue { +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 @@ -394,7 +397,7 @@ func (c *httpConv) ServerRequest(server string, req *http.Request, opts HTTPServ n++ } - attrs := make([]attribute.KeyValue, 0, n) + attrs = slices.Grow(attrs, n) attrs = append(attrs, c.method(req.Method)) attrs = append(attrs, c.scheme(req.TLS != nil)) 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 index 9b618b558cd..26e8f65e665 100644 --- 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 @@ -27,7 +27,7 @@ func TestHTTPClientResponse(t *testing.T) { StatusCode: stat, ContentLength: n, } - got := HTTPClientResponse(resp) + got := HTTPClientResponse(resp, nil) assert.Equal(t, 2, cap(got), "slice capacity") assert.ElementsMatch(t, []attribute.KeyValue{ attribute.Key("http.status_code").Int(stat), @@ -55,7 +55,7 @@ func TestHTTPSClientRequest(t *testing.T) { attribute.String("http.url", "https://127.0.0.1:443/resource"), attribute.String("net.peer.name", "127.0.0.1"), }, - HTTPClientRequest(req), + HTTPClientRequest(req, nil), ) } @@ -115,7 +115,7 @@ func TestHTTPClientRequest(t *testing.T) { attribute.String("user_agent.original", agent), attribute.Int("http.request_content_length", n), }, - HTTPClientRequest(req), + HTTPClientRequest(req, nil), ) } @@ -156,7 +156,7 @@ func TestHTTPClientRequestMetrics(t *testing.T) { func TestHTTPClientRequestRequired(t *testing.T) { req := new(http.Request) var got []attribute.KeyValue - assert.NotPanics(t, func() { got = HTTPClientRequest(req) }) + assert.NotPanics(t, func() { got = HTTPClientRequest(req, nil) }) want := []attribute.KeyValue{ attribute.String("http.method", "GET"), attribute.String("http.url", ""), @@ -248,7 +248,7 @@ func TestHTTPServerRequest(t *testing.T) { attribute.String("net.protocol.version", "1.1"), attribute.String("http.target", "/"), }, - HTTPServerRequest("", got, tt.httpServerRequestOpts)) + HTTPServerRequest("", got, tt.httpServerRequestOpts, nil)) }) } } @@ -301,14 +301,14 @@ func TestHTTPServerName(t *testing.T) { ) portStr := strconv.Itoa(port) server := host + ":" + portStr - assert.NotPanics(t, func() { got = HTTPServerRequest(server, req, HTTPServerRequestOptions{}) }) + 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{}) }) + 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)) } @@ -316,7 +316,7 @@ func TestHTTPServerName(t *testing.T) { func TestHTTPServerRequestFailsGracefully(t *testing.T) { req := new(http.Request) var got []attribute.KeyValue - assert.NotPanics(t, func() { got = HTTPServerRequest("", req, HTTPServerRequestOptions{}) }) + assert.NotPanics(t, func() { got = HTTPServerRequest("", req, HTTPServerRequestOptions{}, nil) }) want := []attribute.KeyValue{ attribute.String("http.method", "GET"), attribute.String("http.scheme", "http"), diff --git a/instrumentation/github.com/emicklei/go-restful/otelrestful/restful.go b/instrumentation/github.com/emicklei/go-restful/otelrestful/restful.go index 59fad1f507c..3c8d911d70a 100644 --- a/instrumentation/github.com/emicklei/go-restful/otelrestful/restful.go +++ b/instrumentation/github.com/emicklei/go-restful/otelrestful/restful.go @@ -6,11 +6,12 @@ package otelrestful // import "go.opentelemetry.io/contrib/instrumentation/githu import ( "github.com/emicklei/go-restful/v3" - "go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconv" - "go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconvutil" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/propagation" oteltrace "go.opentelemetry.io/otel/trace" + + "go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconv" + "go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful/internal/semconvutil" ) // ScopeName is the instrumentation scope name. @@ -45,7 +46,7 @@ func OTelFilter(service string, opts ...Option) restful.FilterFunction { spanName := route opts := []oteltrace.SpanStartOption{ - oteltrace.WithAttributes(semconvutil.HTTPServerRequest(service, r, semconvutil.HTTPServerRequestOptions{})...), + oteltrace.WithAttributes(semconvutil.HTTPServerRequest(service, r, semconvutil.HTTPServerRequestOptions{}, nil)...), oteltrace.WithSpanKind(oteltrace.SpanKindServer), } if route != "" { 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 f69cf817449..9e5fa87676b 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 @@ -62,15 +62,19 @@ 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 append(OldHTTPServer{}.RequestTraceAttrs(server, req), CurrentHTTPServer{}.RequestTraceAttrs(server, req, opts)...) + return OldHTTPServer{}.RequestTraceAttrs(server, req, attrs) } - return CurrentHTTPServer{}.RequestTraceAttrs(server, req, opts) + return attrs } func (s HTTPServer) NetworkTransportAttr(network string) []attribute.KeyValue { if s.duplicate { - return append([]attribute.KeyValue{OldHTTPServer{}.NetworkTransportAttr(network)}, CurrentHTTPServer{}.NetworkTransportAttr(network)) + return []attribute.KeyValue{ + OldHTTPServer{}.NetworkTransportAttr(network), + CurrentHTTPServer{}.NetworkTransportAttr(network), + } } return []attribute.KeyValue{ CurrentHTTPServer{}.NetworkTransportAttr(network), @@ -81,10 +85,11 @@ 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 append(OldHTTPServer{}.ResponseTraceAttrs(resp), CurrentHTTPServer{}.ResponseTraceAttrs(resp)...) + return OldHTTPServer{}.ResponseTraceAttrs(resp, attrs) } - return CurrentHTTPServer{}.ResponseTraceAttrs(resp) + return attrs } // Route returns the attribute for the route. @@ -208,19 +213,20 @@ func NewHTTPClient(meter metric.Meter) HTTPClient { // 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 append(OldHTTPClient{}.RequestTraceAttrs(req), CurrentHTTPClient{}.RequestTraceAttrs(req)...) + return OldHTTPClient{}.RequestTraceAttrs(req, attrs) } - return CurrentHTTPClient{}.RequestTraceAttrs(req) + return attrs } // 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 append(OldHTTPClient{}.ResponseTraceAttrs(resp), CurrentHTTPClient{}.ResponseTraceAttrs(resp)...) + return OldHTTPClient{}.ResponseTraceAttrs(resp, attrs) } - - return CurrentHTTPClient{}.ResponseTraceAttrs(resp) + return attrs } func (c HTTPClient) Status(code int) (codes.Code, string) { @@ -297,9 +303,10 @@ func (s HTTPClient) RecordResponseSize(ctx context.Context, responseData int64, } func (s HTTPClient) TraceAttributes(host string) []attribute.KeyValue { + attrs := CurrentHTTPClient{}.TraceAttributes(host) if s.duplicate { - return append(OldHTTPClient{}.TraceAttributes(host), CurrentHTTPClient{}.TraceAttributes(host)...) + return OldHTTPClient{}.TraceAttributes(host, attrs) } - return CurrentHTTPClient{}.TraceAttributes(host) + return attrs } 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 39b243ba915..afa6db6c20c 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 @@ -151,8 +151,8 @@ func TestHTTPClientTraceAttributes(t *testing.T) { optinVal: "http/dup", wantAttributes: []attribute.KeyValue{ - attribute.String("net.host.name", "example.com"), attribute.String("server.address", "example.com"), + attribute.String("net.host.name", "example.com"), }, }, } { @@ -188,8 +188,8 @@ func TestClientTraceAttributes(t *testing.T) { host: "example.com", wantAttributes: []attribute.KeyValue{ - attribute.String("net.host.name", "example.com"), attribute.String("server.address", "example.com"), + attribute.String("net.host.name", "example.com"), }, }, } { 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 index b6bfb001839..6d3889e2b99 100644 --- 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 @@ -37,8 +37,8 @@ type OldHTTPServer 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 (o OldHTTPServer) RequestTraceAttrs(server string, req *http.Request) []attribute.KeyValue { - return semconvutil.HTTPServerRequest(server, req, semconvutil.HTTPServerRequestOptions{}) +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 { @@ -48,9 +48,7 @@ func (o OldHTTPServer) NetworkTransportAttr(network string) attribute.KeyValue { // 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) []attribute.KeyValue { - attributes := []attribute.KeyValue{} - +func (o OldHTTPServer) ResponseTraceAttrs(resp ResponseTelemetry, attributes []attribute.KeyValue) []attribute.KeyValue { if resp.ReadBytes > 0 { attributes = append(attributes, semconv.HTTPRequestContentLength(int(resp.ReadBytes))) } @@ -179,12 +177,12 @@ func (o OldHTTPServer) scheme(https bool) attribute.KeyValue { // nolint:revive type OldHTTPClient struct{} -func (o OldHTTPClient) RequestTraceAttrs(req *http.Request) []attribute.KeyValue { - return semconvutil.HTTPClientRequest(req) +func (o OldHTTPClient) RequestTraceAttrs(req *http.Request, attrs []attribute.KeyValue) []attribute.KeyValue { + return semconvutil.HTTPClientRequest(req, attrs) } -func (o OldHTTPClient) ResponseTraceAttrs(resp *http.Response) []attribute.KeyValue { - return semconvutil.HTTPClientResponse(resp) +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 { @@ -270,8 +268,6 @@ func (o OldHTTPClient) createMeasures(meter metric.Meter) (metric.Int64Counter, } // TraceAttributes returns attributes for httptrace. -func (c OldHTTPClient) TraceAttributes(host string) []attribute.KeyValue { - return []attribute.KeyValue{ - semconv.NetHostName(host), - } +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/httpconv.go b/instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconvutil/httpconv.go index ee483341e89..3ed4e62dabe 100644 --- a/instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconvutil/httpconv.go +++ b/instrumentation/github.com/gin-gonic/gin/otelgin/internal/semconvutil/httpconv.go @@ -10,6 +10,7 @@ package semconvutil // import "go.opentelemetry.io/contrib/instrumentation/githu import ( "fmt" "net/http" + "slices" "strings" "go.opentelemetry.io/otel/attribute" @@ -32,9 +33,9 @@ type HTTPServerRequestOptions struct { // attributes. If a complete set of attributes can be generated using the // request contained in resp. For example: // -// append(HTTPClientResponse(resp), ClientRequest(resp.Request)...) -func HTTPClientResponse(resp *http.Response) []attribute.KeyValue { - return hc.ClientResponse(resp) +// 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. @@ -42,8 +43,8 @@ func HTTPClientResponse(resp *http.Response) []attribute.KeyValue { // "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) []attribute.KeyValue { - return hc.ClientRequest(req) +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. @@ -81,8 +82,8 @@ func HTTPClientStatus(code int) (codes.Code, string) { // "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) []attribute.KeyValue { - return hc.ServerRequest(server, req, opts) +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 @@ -159,8 +160,8 @@ var hc = &httpConv{ // attributes. If a complete set of attributes can be generated using the // request contained in resp. For example: // -// append(ClientResponse(resp), ClientRequest(resp.Request)...) -func (c *httpConv) ClientResponse(resp *http.Response) []attribute.KeyValue { +// 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 @@ -172,8 +173,11 @@ func (c *httpConv) ClientResponse(resp *http.Response) []attribute.KeyValue { if resp.ContentLength > 0 { n++ } + if n == 0 { + return attrs + } - attrs := make([]attribute.KeyValue, 0, n) + attrs = slices.Grow(attrs, n) if resp.StatusCode > 0 { attrs = append(attrs, c.HTTPStatusCodeKey.Int(resp.StatusCode)) } @@ -188,7 +192,7 @@ func (c *httpConv) ClientResponse(resp *http.Response) []attribute.KeyValue { // "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) []attribute.KeyValue { +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 @@ -227,8 +231,7 @@ func (c *httpConv) ClientRequest(req *http.Request) []attribute.KeyValue { n++ } - attrs := make([]attribute.KeyValue, 0, n) - + attrs = slices.Grow(attrs, n) attrs = append(attrs, c.method(req.Method)) var u string @@ -311,7 +314,7 @@ func (c *httpConv) ClientRequestMetrics(req *http.Request) []attribute.KeyValue // 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) []attribute.KeyValue { +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 @@ -394,7 +397,7 @@ func (c *httpConv) ServerRequest(server string, req *http.Request, opts HTTPServ n++ } - attrs := make([]attribute.KeyValue, 0, n) + attrs = slices.Grow(attrs, n) attrs = append(attrs, c.method(req.Method)) attrs = append(attrs, c.scheme(req.TLS != nil)) 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 index 9b618b558cd..26e8f65e665 100644 --- 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 @@ -27,7 +27,7 @@ func TestHTTPClientResponse(t *testing.T) { StatusCode: stat, ContentLength: n, } - got := HTTPClientResponse(resp) + got := HTTPClientResponse(resp, nil) assert.Equal(t, 2, cap(got), "slice capacity") assert.ElementsMatch(t, []attribute.KeyValue{ attribute.Key("http.status_code").Int(stat), @@ -55,7 +55,7 @@ func TestHTTPSClientRequest(t *testing.T) { attribute.String("http.url", "https://127.0.0.1:443/resource"), attribute.String("net.peer.name", "127.0.0.1"), }, - HTTPClientRequest(req), + HTTPClientRequest(req, nil), ) } @@ -115,7 +115,7 @@ func TestHTTPClientRequest(t *testing.T) { attribute.String("user_agent.original", agent), attribute.Int("http.request_content_length", n), }, - HTTPClientRequest(req), + HTTPClientRequest(req, nil), ) } @@ -156,7 +156,7 @@ func TestHTTPClientRequestMetrics(t *testing.T) { func TestHTTPClientRequestRequired(t *testing.T) { req := new(http.Request) var got []attribute.KeyValue - assert.NotPanics(t, func() { got = HTTPClientRequest(req) }) + assert.NotPanics(t, func() { got = HTTPClientRequest(req, nil) }) want := []attribute.KeyValue{ attribute.String("http.method", "GET"), attribute.String("http.url", ""), @@ -248,7 +248,7 @@ func TestHTTPServerRequest(t *testing.T) { attribute.String("net.protocol.version", "1.1"), attribute.String("http.target", "/"), }, - HTTPServerRequest("", got, tt.httpServerRequestOpts)) + HTTPServerRequest("", got, tt.httpServerRequestOpts, nil)) }) } } @@ -301,14 +301,14 @@ func TestHTTPServerName(t *testing.T) { ) portStr := strconv.Itoa(port) server := host + ":" + portStr - assert.NotPanics(t, func() { got = HTTPServerRequest(server, req, HTTPServerRequestOptions{}) }) + 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{}) }) + 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)) } @@ -316,7 +316,7 @@ func TestHTTPServerName(t *testing.T) { func TestHTTPServerRequestFailsGracefully(t *testing.T) { req := new(http.Request) var got []attribute.KeyValue - assert.NotPanics(t, func() { got = HTTPServerRequest("", req, HTTPServerRequestOptions{}) }) + assert.NotPanics(t, func() { got = HTTPServerRequest("", req, HTTPServerRequestOptions{}, nil) }) want := []attribute.KeyValue{ attribute.String("http.method", "GET"), attribute.String("http.scheme", "http"), 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 c14d86f7a4a..d9a28652dd6 100644 --- a/instrumentation/github.com/gorilla/mux/otelmux/internal/semconv/env.go +++ b/instrumentation/github.com/gorilla/mux/otelmux/internal/semconv/env.go @@ -62,15 +62,19 @@ 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 append(OldHTTPServer{}.RequestTraceAttrs(server, req), CurrentHTTPServer{}.RequestTraceAttrs(server, req, opts)...) + return OldHTTPServer{}.RequestTraceAttrs(server, req, attrs) } - return CurrentHTTPServer{}.RequestTraceAttrs(server, req, opts) + return attrs } func (s HTTPServer) NetworkTransportAttr(network string) []attribute.KeyValue { if s.duplicate { - return append([]attribute.KeyValue{OldHTTPServer{}.NetworkTransportAttr(network)}, CurrentHTTPServer{}.NetworkTransportAttr(network)) + return []attribute.KeyValue{ + OldHTTPServer{}.NetworkTransportAttr(network), + CurrentHTTPServer{}.NetworkTransportAttr(network), + } } return []attribute.KeyValue{ CurrentHTTPServer{}.NetworkTransportAttr(network), @@ -81,10 +85,11 @@ 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 append(OldHTTPServer{}.ResponseTraceAttrs(resp), CurrentHTTPServer{}.ResponseTraceAttrs(resp)...) + return OldHTTPServer{}.ResponseTraceAttrs(resp, attrs) } - return CurrentHTTPServer{}.ResponseTraceAttrs(resp) + return attrs } // Route returns the attribute for the route. @@ -208,19 +213,20 @@ func NewHTTPClient(meter metric.Meter) HTTPClient { // 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 append(OldHTTPClient{}.RequestTraceAttrs(req), CurrentHTTPClient{}.RequestTraceAttrs(req)...) + return OldHTTPClient{}.RequestTraceAttrs(req, attrs) } - return CurrentHTTPClient{}.RequestTraceAttrs(req) + return attrs } // 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 append(OldHTTPClient{}.ResponseTraceAttrs(resp), CurrentHTTPClient{}.ResponseTraceAttrs(resp)...) + return OldHTTPClient{}.ResponseTraceAttrs(resp, attrs) } - - return CurrentHTTPClient{}.ResponseTraceAttrs(resp) + return attrs } func (c HTTPClient) Status(code int) (codes.Code, string) { @@ -297,9 +303,10 @@ func (s HTTPClient) RecordResponseSize(ctx context.Context, responseData int64, } func (s HTTPClient) TraceAttributes(host string) []attribute.KeyValue { + attrs := CurrentHTTPClient{}.TraceAttributes(host) if s.duplicate { - return append(OldHTTPClient{}.TraceAttributes(host), CurrentHTTPClient{}.TraceAttributes(host)...) + return OldHTTPClient{}.TraceAttributes(host, attrs) } - return CurrentHTTPClient{}.TraceAttributes(host) + return attrs } 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 39b243ba915..afa6db6c20c 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 @@ -151,8 +151,8 @@ func TestHTTPClientTraceAttributes(t *testing.T) { optinVal: "http/dup", wantAttributes: []attribute.KeyValue{ - attribute.String("net.host.name", "example.com"), attribute.String("server.address", "example.com"), + attribute.String("net.host.name", "example.com"), }, }, } { @@ -188,8 +188,8 @@ func TestClientTraceAttributes(t *testing.T) { host: "example.com", wantAttributes: []attribute.KeyValue{ - attribute.String("net.host.name", "example.com"), attribute.String("server.address", "example.com"), + attribute.String("net.host.name", "example.com"), }, }, } { 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 index f7aa6cda9d4..a0e55a6634b 100644 --- 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 @@ -37,8 +37,8 @@ type OldHTTPServer 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 (o OldHTTPServer) RequestTraceAttrs(server string, req *http.Request) []attribute.KeyValue { - return semconvutil.HTTPServerRequest(server, req, semconvutil.HTTPServerRequestOptions{}) +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 { @@ -48,9 +48,7 @@ func (o OldHTTPServer) NetworkTransportAttr(network string) attribute.KeyValue { // 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) []attribute.KeyValue { - attributes := []attribute.KeyValue{} - +func (o OldHTTPServer) ResponseTraceAttrs(resp ResponseTelemetry, attributes []attribute.KeyValue) []attribute.KeyValue { if resp.ReadBytes > 0 { attributes = append(attributes, semconv.HTTPRequestContentLength(int(resp.ReadBytes))) } @@ -179,12 +177,12 @@ func (o OldHTTPServer) scheme(https bool) attribute.KeyValue { // nolint:revive type OldHTTPClient struct{} -func (o OldHTTPClient) RequestTraceAttrs(req *http.Request) []attribute.KeyValue { - return semconvutil.HTTPClientRequest(req) +func (o OldHTTPClient) RequestTraceAttrs(req *http.Request, attrs []attribute.KeyValue) []attribute.KeyValue { + return semconvutil.HTTPClientRequest(req, attrs) } -func (o OldHTTPClient) ResponseTraceAttrs(resp *http.Response) []attribute.KeyValue { - return semconvutil.HTTPClientResponse(resp) +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 { @@ -270,8 +268,6 @@ func (o OldHTTPClient) createMeasures(meter metric.Meter) (metric.Int64Counter, } // TraceAttributes returns attributes for httptrace. -func (c OldHTTPClient) TraceAttributes(host string) []attribute.KeyValue { - return []attribute.KeyValue{ - semconv.NetHostName(host), - } +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/httpconv.go b/instrumentation/github.com/gorilla/mux/otelmux/internal/semconvutil/httpconv.go index 2d986314762..91b51bac820 100644 --- a/instrumentation/github.com/gorilla/mux/otelmux/internal/semconvutil/httpconv.go +++ b/instrumentation/github.com/gorilla/mux/otelmux/internal/semconvutil/httpconv.go @@ -10,6 +10,7 @@ package semconvutil // import "go.opentelemetry.io/contrib/instrumentation/githu import ( "fmt" "net/http" + "slices" "strings" "go.opentelemetry.io/otel/attribute" @@ -32,9 +33,9 @@ type HTTPServerRequestOptions struct { // attributes. If a complete set of attributes can be generated using the // request contained in resp. For example: // -// append(HTTPClientResponse(resp), ClientRequest(resp.Request)...) -func HTTPClientResponse(resp *http.Response) []attribute.KeyValue { - return hc.ClientResponse(resp) +// 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. @@ -42,8 +43,8 @@ func HTTPClientResponse(resp *http.Response) []attribute.KeyValue { // "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) []attribute.KeyValue { - return hc.ClientRequest(req) +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. @@ -81,8 +82,8 @@ func HTTPClientStatus(code int) (codes.Code, string) { // "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) []attribute.KeyValue { - return hc.ServerRequest(server, req, opts) +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 @@ -159,8 +160,8 @@ var hc = &httpConv{ // attributes. If a complete set of attributes can be generated using the // request contained in resp. For example: // -// append(ClientResponse(resp), ClientRequest(resp.Request)...) -func (c *httpConv) ClientResponse(resp *http.Response) []attribute.KeyValue { +// 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 @@ -172,8 +173,11 @@ func (c *httpConv) ClientResponse(resp *http.Response) []attribute.KeyValue { if resp.ContentLength > 0 { n++ } + if n == 0 { + return attrs + } - attrs := make([]attribute.KeyValue, 0, n) + attrs = slices.Grow(attrs, n) if resp.StatusCode > 0 { attrs = append(attrs, c.HTTPStatusCodeKey.Int(resp.StatusCode)) } @@ -188,7 +192,7 @@ func (c *httpConv) ClientResponse(resp *http.Response) []attribute.KeyValue { // "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) []attribute.KeyValue { +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 @@ -227,8 +231,7 @@ func (c *httpConv) ClientRequest(req *http.Request) []attribute.KeyValue { n++ } - attrs := make([]attribute.KeyValue, 0, n) - + attrs = slices.Grow(attrs, n) attrs = append(attrs, c.method(req.Method)) var u string @@ -311,7 +314,7 @@ func (c *httpConv) ClientRequestMetrics(req *http.Request) []attribute.KeyValue // 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) []attribute.KeyValue { +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 @@ -394,7 +397,7 @@ func (c *httpConv) ServerRequest(server string, req *http.Request, opts HTTPServ n++ } - attrs := make([]attribute.KeyValue, 0, n) + attrs = slices.Grow(attrs, n) attrs = append(attrs, c.method(req.Method)) attrs = append(attrs, c.scheme(req.TLS != nil)) 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 index 9b618b558cd..26e8f65e665 100644 --- a/instrumentation/github.com/gorilla/mux/otelmux/internal/semconvutil/httpconv_test.go +++ b/instrumentation/github.com/gorilla/mux/otelmux/internal/semconvutil/httpconv_test.go @@ -27,7 +27,7 @@ func TestHTTPClientResponse(t *testing.T) { StatusCode: stat, ContentLength: n, } - got := HTTPClientResponse(resp) + got := HTTPClientResponse(resp, nil) assert.Equal(t, 2, cap(got), "slice capacity") assert.ElementsMatch(t, []attribute.KeyValue{ attribute.Key("http.status_code").Int(stat), @@ -55,7 +55,7 @@ func TestHTTPSClientRequest(t *testing.T) { attribute.String("http.url", "https://127.0.0.1:443/resource"), attribute.String("net.peer.name", "127.0.0.1"), }, - HTTPClientRequest(req), + HTTPClientRequest(req, nil), ) } @@ -115,7 +115,7 @@ func TestHTTPClientRequest(t *testing.T) { attribute.String("user_agent.original", agent), attribute.Int("http.request_content_length", n), }, - HTTPClientRequest(req), + HTTPClientRequest(req, nil), ) } @@ -156,7 +156,7 @@ func TestHTTPClientRequestMetrics(t *testing.T) { func TestHTTPClientRequestRequired(t *testing.T) { req := new(http.Request) var got []attribute.KeyValue - assert.NotPanics(t, func() { got = HTTPClientRequest(req) }) + assert.NotPanics(t, func() { got = HTTPClientRequest(req, nil) }) want := []attribute.KeyValue{ attribute.String("http.method", "GET"), attribute.String("http.url", ""), @@ -248,7 +248,7 @@ func TestHTTPServerRequest(t *testing.T) { attribute.String("net.protocol.version", "1.1"), attribute.String("http.target", "/"), }, - HTTPServerRequest("", got, tt.httpServerRequestOpts)) + HTTPServerRequest("", got, tt.httpServerRequestOpts, nil)) }) } } @@ -301,14 +301,14 @@ func TestHTTPServerName(t *testing.T) { ) portStr := strconv.Itoa(port) server := host + ":" + portStr - assert.NotPanics(t, func() { got = HTTPServerRequest(server, req, HTTPServerRequestOptions{}) }) + 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{}) }) + 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)) } @@ -316,7 +316,7 @@ func TestHTTPServerName(t *testing.T) { func TestHTTPServerRequestFailsGracefully(t *testing.T) { req := new(http.Request) var got []attribute.KeyValue - assert.NotPanics(t, func() { got = HTTPServerRequest("", req, HTTPServerRequestOptions{}) }) + assert.NotPanics(t, func() { got = HTTPServerRequest("", req, HTTPServerRequestOptions{}, nil) }) want := []attribute.KeyValue{ attribute.String("http.method", "GET"), attribute.String("http.scheme", "http"), diff --git a/instrumentation/github.com/labstack/echo/otelecho/echo.go b/instrumentation/github.com/labstack/echo/otelecho/echo.go index b3944535dbe..fbc9aca6c48 100644 --- a/instrumentation/github.com/labstack/echo/otelecho/echo.go +++ b/instrumentation/github.com/labstack/echo/otelecho/echo.go @@ -11,12 +11,13 @@ import ( "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" - "go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho/internal/semconvutil" "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" ) const ( @@ -61,7 +62,7 @@ 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{})...), + oteltrace.WithAttributes(semconvutil.HTTPServerRequest(service, request, semconvutil.HTTPServerRequestOptions{}, nil)...), oteltrace.WithSpanKind(oteltrace.SpanKindServer), } if path := c.Path(); path != "" { diff --git a/instrumentation/github.com/labstack/echo/otelecho/internal/semconvutil/httpconv.go b/instrumentation/github.com/labstack/echo/otelecho/internal/semconvutil/httpconv.go index aaee7365ec6..03d10c03060 100644 --- a/instrumentation/github.com/labstack/echo/otelecho/internal/semconvutil/httpconv.go +++ b/instrumentation/github.com/labstack/echo/otelecho/internal/semconvutil/httpconv.go @@ -10,6 +10,7 @@ package semconvutil // import "go.opentelemetry.io/contrib/instrumentation/githu import ( "fmt" "net/http" + "slices" "strings" "go.opentelemetry.io/otel/attribute" @@ -32,9 +33,9 @@ type HTTPServerRequestOptions struct { // attributes. If a complete set of attributes can be generated using the // request contained in resp. For example: // -// append(HTTPClientResponse(resp), ClientRequest(resp.Request)...) -func HTTPClientResponse(resp *http.Response) []attribute.KeyValue { - return hc.ClientResponse(resp) +// 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. @@ -42,8 +43,8 @@ func HTTPClientResponse(resp *http.Response) []attribute.KeyValue { // "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) []attribute.KeyValue { - return hc.ClientRequest(req) +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. @@ -81,8 +82,8 @@ func HTTPClientStatus(code int) (codes.Code, string) { // "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) []attribute.KeyValue { - return hc.ServerRequest(server, req, opts) +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 @@ -159,8 +160,8 @@ var hc = &httpConv{ // attributes. If a complete set of attributes can be generated using the // request contained in resp. For example: // -// append(ClientResponse(resp), ClientRequest(resp.Request)...) -func (c *httpConv) ClientResponse(resp *http.Response) []attribute.KeyValue { +// 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 @@ -172,8 +173,11 @@ func (c *httpConv) ClientResponse(resp *http.Response) []attribute.KeyValue { if resp.ContentLength > 0 { n++ } + if n == 0 { + return attrs + } - attrs := make([]attribute.KeyValue, 0, n) + attrs = slices.Grow(attrs, n) if resp.StatusCode > 0 { attrs = append(attrs, c.HTTPStatusCodeKey.Int(resp.StatusCode)) } @@ -188,7 +192,7 @@ func (c *httpConv) ClientResponse(resp *http.Response) []attribute.KeyValue { // "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) []attribute.KeyValue { +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 @@ -227,8 +231,7 @@ func (c *httpConv) ClientRequest(req *http.Request) []attribute.KeyValue { n++ } - attrs := make([]attribute.KeyValue, 0, n) - + attrs = slices.Grow(attrs, n) attrs = append(attrs, c.method(req.Method)) var u string @@ -311,7 +314,7 @@ func (c *httpConv) ClientRequestMetrics(req *http.Request) []attribute.KeyValue // 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) []attribute.KeyValue { +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 @@ -394,7 +397,7 @@ func (c *httpConv) ServerRequest(server string, req *http.Request, opts HTTPServ n++ } - attrs := make([]attribute.KeyValue, 0, n) + attrs = slices.Grow(attrs, n) attrs = append(attrs, c.method(req.Method)) attrs = append(attrs, c.scheme(req.TLS != nil)) 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 index 9b618b558cd..26e8f65e665 100644 --- a/instrumentation/github.com/labstack/echo/otelecho/internal/semconvutil/httpconv_test.go +++ b/instrumentation/github.com/labstack/echo/otelecho/internal/semconvutil/httpconv_test.go @@ -27,7 +27,7 @@ func TestHTTPClientResponse(t *testing.T) { StatusCode: stat, ContentLength: n, } - got := HTTPClientResponse(resp) + got := HTTPClientResponse(resp, nil) assert.Equal(t, 2, cap(got), "slice capacity") assert.ElementsMatch(t, []attribute.KeyValue{ attribute.Key("http.status_code").Int(stat), @@ -55,7 +55,7 @@ func TestHTTPSClientRequest(t *testing.T) { attribute.String("http.url", "https://127.0.0.1:443/resource"), attribute.String("net.peer.name", "127.0.0.1"), }, - HTTPClientRequest(req), + HTTPClientRequest(req, nil), ) } @@ -115,7 +115,7 @@ func TestHTTPClientRequest(t *testing.T) { attribute.String("user_agent.original", agent), attribute.Int("http.request_content_length", n), }, - HTTPClientRequest(req), + HTTPClientRequest(req, nil), ) } @@ -156,7 +156,7 @@ func TestHTTPClientRequestMetrics(t *testing.T) { func TestHTTPClientRequestRequired(t *testing.T) { req := new(http.Request) var got []attribute.KeyValue - assert.NotPanics(t, func() { got = HTTPClientRequest(req) }) + assert.NotPanics(t, func() { got = HTTPClientRequest(req, nil) }) want := []attribute.KeyValue{ attribute.String("http.method", "GET"), attribute.String("http.url", ""), @@ -248,7 +248,7 @@ func TestHTTPServerRequest(t *testing.T) { attribute.String("net.protocol.version", "1.1"), attribute.String("http.target", "/"), }, - HTTPServerRequest("", got, tt.httpServerRequestOpts)) + HTTPServerRequest("", got, tt.httpServerRequestOpts, nil)) }) } } @@ -301,14 +301,14 @@ func TestHTTPServerName(t *testing.T) { ) portStr := strconv.Itoa(port) server := host + ":" + portStr - assert.NotPanics(t, func() { got = HTTPServerRequest(server, req, HTTPServerRequestOptions{}) }) + 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{}) }) + 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)) } @@ -316,7 +316,7 @@ func TestHTTPServerName(t *testing.T) { func TestHTTPServerRequestFailsGracefully(t *testing.T) { req := new(http.Request) var got []attribute.KeyValue - assert.NotPanics(t, func() { got = HTTPServerRequest("", req, HTTPServerRequestOptions{}) }) + assert.NotPanics(t, func() { got = HTTPServerRequest("", req, HTTPServerRequestOptions{}, nil) }) want := []attribute.KeyValue{ attribute.String("http.method", "GET"), attribute.String("http.scheme", "http"), diff --git a/instrumentation/net/http/httptrace/otelhttptrace/internal/semconv/env.go b/instrumentation/net/http/httptrace/otelhttptrace/internal/semconv/env.go index 2e053e999f2..e8cbc0f2587 100644 --- a/instrumentation/net/http/httptrace/otelhttptrace/internal/semconv/env.go +++ b/instrumentation/net/http/httptrace/otelhttptrace/internal/semconv/env.go @@ -62,15 +62,19 @@ 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 append(OldHTTPServer{}.RequestTraceAttrs(server, req), CurrentHTTPServer{}.RequestTraceAttrs(server, req, opts)...) + return OldHTTPServer{}.RequestTraceAttrs(server, req, attrs) } - return CurrentHTTPServer{}.RequestTraceAttrs(server, req, opts) + return attrs } func (s HTTPServer) NetworkTransportAttr(network string) []attribute.KeyValue { if s.duplicate { - return append([]attribute.KeyValue{OldHTTPServer{}.NetworkTransportAttr(network)}, CurrentHTTPServer{}.NetworkTransportAttr(network)) + return []attribute.KeyValue{ + OldHTTPServer{}.NetworkTransportAttr(network), + CurrentHTTPServer{}.NetworkTransportAttr(network), + } } return []attribute.KeyValue{ CurrentHTTPServer{}.NetworkTransportAttr(network), @@ -81,10 +85,11 @@ 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 append(OldHTTPServer{}.ResponseTraceAttrs(resp), CurrentHTTPServer{}.ResponseTraceAttrs(resp)...) + return OldHTTPServer{}.ResponseTraceAttrs(resp, attrs) } - return CurrentHTTPServer{}.ResponseTraceAttrs(resp) + return attrs } // Route returns the attribute for the route. @@ -208,19 +213,20 @@ func NewHTTPClient(meter metric.Meter) HTTPClient { // 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 append(OldHTTPClient{}.RequestTraceAttrs(req), CurrentHTTPClient{}.RequestTraceAttrs(req)...) + return OldHTTPClient{}.RequestTraceAttrs(req, attrs) } - return CurrentHTTPClient{}.RequestTraceAttrs(req) + return attrs } // 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 append(OldHTTPClient{}.ResponseTraceAttrs(resp), CurrentHTTPClient{}.ResponseTraceAttrs(resp)...) + return OldHTTPClient{}.ResponseTraceAttrs(resp, attrs) } - - return CurrentHTTPClient{}.ResponseTraceAttrs(resp) + return attrs } func (c HTTPClient) Status(code int) (codes.Code, string) { @@ -297,9 +303,10 @@ func (s HTTPClient) RecordResponseSize(ctx context.Context, responseData int64, } func (s HTTPClient) TraceAttributes(host string) []attribute.KeyValue { + attrs := CurrentHTTPClient{}.TraceAttributes(host) if s.duplicate { - return append(OldHTTPClient{}.TraceAttributes(host), CurrentHTTPClient{}.TraceAttributes(host)...) + return OldHTTPClient{}.TraceAttributes(host, attrs) } - return CurrentHTTPClient{}.TraceAttributes(host) + return attrs } 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 39b243ba915..afa6db6c20c 100644 --- a/instrumentation/net/http/httptrace/otelhttptrace/internal/semconv/env_test.go +++ b/instrumentation/net/http/httptrace/otelhttptrace/internal/semconv/env_test.go @@ -151,8 +151,8 @@ func TestHTTPClientTraceAttributes(t *testing.T) { optinVal: "http/dup", wantAttributes: []attribute.KeyValue{ - attribute.String("net.host.name", "example.com"), attribute.String("server.address", "example.com"), + attribute.String("net.host.name", "example.com"), }, }, } { @@ -188,8 +188,8 @@ func TestClientTraceAttributes(t *testing.T) { host: "example.com", wantAttributes: []attribute.KeyValue{ - attribute.String("net.host.name", "example.com"), attribute.String("server.address", "example.com"), + attribute.String("net.host.name", "example.com"), }, }, } { 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 index 92fd81c22e6..7475aed3e19 100644 --- a/instrumentation/net/http/httptrace/otelhttptrace/internal/semconv/v1.20.0.go +++ b/instrumentation/net/http/httptrace/otelhttptrace/internal/semconv/v1.20.0.go @@ -37,8 +37,8 @@ type OldHTTPServer 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 (o OldHTTPServer) RequestTraceAttrs(server string, req *http.Request) []attribute.KeyValue { - return semconvutil.HTTPServerRequest(server, req, semconvutil.HTTPServerRequestOptions{}) +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 { @@ -48,9 +48,7 @@ func (o OldHTTPServer) NetworkTransportAttr(network string) attribute.KeyValue { // 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) []attribute.KeyValue { - attributes := []attribute.KeyValue{} - +func (o OldHTTPServer) ResponseTraceAttrs(resp ResponseTelemetry, attributes []attribute.KeyValue) []attribute.KeyValue { if resp.ReadBytes > 0 { attributes = append(attributes, semconv.HTTPRequestContentLength(int(resp.ReadBytes))) } @@ -179,12 +177,12 @@ func (o OldHTTPServer) scheme(https bool) attribute.KeyValue { // nolint:revive type OldHTTPClient struct{} -func (o OldHTTPClient) RequestTraceAttrs(req *http.Request) []attribute.KeyValue { - return semconvutil.HTTPClientRequest(req) +func (o OldHTTPClient) RequestTraceAttrs(req *http.Request, attrs []attribute.KeyValue) []attribute.KeyValue { + return semconvutil.HTTPClientRequest(req, attrs) } -func (o OldHTTPClient) ResponseTraceAttrs(resp *http.Response) []attribute.KeyValue { - return semconvutil.HTTPClientResponse(resp) +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 { @@ -270,8 +268,6 @@ func (o OldHTTPClient) createMeasures(meter metric.Meter) (metric.Int64Counter, } // TraceAttributes returns attributes for httptrace. -func (c OldHTTPClient) TraceAttributes(host string) []attribute.KeyValue { - return []attribute.KeyValue{ - semconv.NetHostName(host), - } +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/httpconv.go b/instrumentation/net/http/httptrace/otelhttptrace/internal/semconvutil/httpconv.go index db4ca1df1e6..091f9a287a0 100644 --- a/instrumentation/net/http/httptrace/otelhttptrace/internal/semconvutil/httpconv.go +++ b/instrumentation/net/http/httptrace/otelhttptrace/internal/semconvutil/httpconv.go @@ -10,6 +10,7 @@ package semconvutil // import "go.opentelemetry.io/contrib/instrumentation/net/h import ( "fmt" "net/http" + "slices" "strings" "go.opentelemetry.io/otel/attribute" @@ -32,9 +33,9 @@ type HTTPServerRequestOptions struct { // attributes. If a complete set of attributes can be generated using the // request contained in resp. For example: // -// append(HTTPClientResponse(resp), ClientRequest(resp.Request)...) -func HTTPClientResponse(resp *http.Response) []attribute.KeyValue { - return hc.ClientResponse(resp) +// 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. @@ -42,8 +43,8 @@ func HTTPClientResponse(resp *http.Response) []attribute.KeyValue { // "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) []attribute.KeyValue { - return hc.ClientRequest(req) +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. @@ -81,8 +82,8 @@ func HTTPClientStatus(code int) (codes.Code, string) { // "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) []attribute.KeyValue { - return hc.ServerRequest(server, req, opts) +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 @@ -159,8 +160,8 @@ var hc = &httpConv{ // attributes. If a complete set of attributes can be generated using the // request contained in resp. For example: // -// append(ClientResponse(resp), ClientRequest(resp.Request)...) -func (c *httpConv) ClientResponse(resp *http.Response) []attribute.KeyValue { +// 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 @@ -172,8 +173,11 @@ func (c *httpConv) ClientResponse(resp *http.Response) []attribute.KeyValue { if resp.ContentLength > 0 { n++ } + if n == 0 { + return attrs + } - attrs := make([]attribute.KeyValue, 0, n) + attrs = slices.Grow(attrs, n) if resp.StatusCode > 0 { attrs = append(attrs, c.HTTPStatusCodeKey.Int(resp.StatusCode)) } @@ -188,7 +192,7 @@ func (c *httpConv) ClientResponse(resp *http.Response) []attribute.KeyValue { // "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) []attribute.KeyValue { +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 @@ -227,8 +231,7 @@ func (c *httpConv) ClientRequest(req *http.Request) []attribute.KeyValue { n++ } - attrs := make([]attribute.KeyValue, 0, n) - + attrs = slices.Grow(attrs, n) attrs = append(attrs, c.method(req.Method)) var u string @@ -311,7 +314,7 @@ func (c *httpConv) ClientRequestMetrics(req *http.Request) []attribute.KeyValue // 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) []attribute.KeyValue { +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 @@ -394,7 +397,7 @@ func (c *httpConv) ServerRequest(server string, req *http.Request, opts HTTPServ n++ } - attrs := make([]attribute.KeyValue, 0, n) + attrs = slices.Grow(attrs, n) attrs = append(attrs, c.method(req.Method)) attrs = append(attrs, c.scheme(req.TLS != nil)) diff --git a/instrumentation/net/http/httptrace/otelhttptrace/internal/semconvutil/httpconv_test.go b/instrumentation/net/http/httptrace/otelhttptrace/internal/semconvutil/httpconv_test.go index 9b618b558cd..26e8f65e665 100644 --- a/instrumentation/net/http/httptrace/otelhttptrace/internal/semconvutil/httpconv_test.go +++ b/instrumentation/net/http/httptrace/otelhttptrace/internal/semconvutil/httpconv_test.go @@ -27,7 +27,7 @@ func TestHTTPClientResponse(t *testing.T) { StatusCode: stat, ContentLength: n, } - got := HTTPClientResponse(resp) + got := HTTPClientResponse(resp, nil) assert.Equal(t, 2, cap(got), "slice capacity") assert.ElementsMatch(t, []attribute.KeyValue{ attribute.Key("http.status_code").Int(stat), @@ -55,7 +55,7 @@ func TestHTTPSClientRequest(t *testing.T) { attribute.String("http.url", "https://127.0.0.1:443/resource"), attribute.String("net.peer.name", "127.0.0.1"), }, - HTTPClientRequest(req), + HTTPClientRequest(req, nil), ) } @@ -115,7 +115,7 @@ func TestHTTPClientRequest(t *testing.T) { attribute.String("user_agent.original", agent), attribute.Int("http.request_content_length", n), }, - HTTPClientRequest(req), + HTTPClientRequest(req, nil), ) } @@ -156,7 +156,7 @@ func TestHTTPClientRequestMetrics(t *testing.T) { func TestHTTPClientRequestRequired(t *testing.T) { req := new(http.Request) var got []attribute.KeyValue - assert.NotPanics(t, func() { got = HTTPClientRequest(req) }) + assert.NotPanics(t, func() { got = HTTPClientRequest(req, nil) }) want := []attribute.KeyValue{ attribute.String("http.method", "GET"), attribute.String("http.url", ""), @@ -248,7 +248,7 @@ func TestHTTPServerRequest(t *testing.T) { attribute.String("net.protocol.version", "1.1"), attribute.String("http.target", "/"), }, - HTTPServerRequest("", got, tt.httpServerRequestOpts)) + HTTPServerRequest("", got, tt.httpServerRequestOpts, nil)) }) } } @@ -301,14 +301,14 @@ func TestHTTPServerName(t *testing.T) { ) portStr := strconv.Itoa(port) server := host + ":" + portStr - assert.NotPanics(t, func() { got = HTTPServerRequest(server, req, HTTPServerRequestOptions{}) }) + 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{}) }) + 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)) } @@ -316,7 +316,7 @@ func TestHTTPServerName(t *testing.T) { func TestHTTPServerRequestFailsGracefully(t *testing.T) { req := new(http.Request) var got []attribute.KeyValue - assert.NotPanics(t, func() { got = HTTPServerRequest("", req, HTTPServerRequestOptions{}) }) + assert.NotPanics(t, func() { got = HTTPServerRequest("", req, HTTPServerRequestOptions{}, nil) }) want := []attribute.KeyValue{ attribute.String("http.method", "GET"), attribute.String("http.scheme", "http"), diff --git a/instrumentation/net/http/otelhttp/internal/semconv/env.go b/instrumentation/net/http/otelhttp/internal/semconv/env.go index a738691f178..b6fb5ad42d7 100644 --- a/instrumentation/net/http/otelhttp/internal/semconv/env.go +++ b/instrumentation/net/http/otelhttp/internal/semconv/env.go @@ -62,15 +62,19 @@ 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 append(OldHTTPServer{}.RequestTraceAttrs(server, req), CurrentHTTPServer{}.RequestTraceAttrs(server, req, opts)...) + return OldHTTPServer{}.RequestTraceAttrs(server, req, attrs) } - return CurrentHTTPServer{}.RequestTraceAttrs(server, req, opts) + return attrs } func (s HTTPServer) NetworkTransportAttr(network string) []attribute.KeyValue { if s.duplicate { - return append([]attribute.KeyValue{OldHTTPServer{}.NetworkTransportAttr(network)}, CurrentHTTPServer{}.NetworkTransportAttr(network)) + return []attribute.KeyValue{ + OldHTTPServer{}.NetworkTransportAttr(network), + CurrentHTTPServer{}.NetworkTransportAttr(network), + } } return []attribute.KeyValue{ CurrentHTTPServer{}.NetworkTransportAttr(network), @@ -81,10 +85,11 @@ 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 append(OldHTTPServer{}.ResponseTraceAttrs(resp), CurrentHTTPServer{}.ResponseTraceAttrs(resp)...) + return OldHTTPServer{}.ResponseTraceAttrs(resp, attrs) } - return CurrentHTTPServer{}.ResponseTraceAttrs(resp) + return attrs } // Route returns the attribute for the route. @@ -208,19 +213,20 @@ func NewHTTPClient(meter metric.Meter) HTTPClient { // 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 append(OldHTTPClient{}.RequestTraceAttrs(req), CurrentHTTPClient{}.RequestTraceAttrs(req)...) + return OldHTTPClient{}.RequestTraceAttrs(req, attrs) } - return CurrentHTTPClient{}.RequestTraceAttrs(req) + return attrs } // 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 append(OldHTTPClient{}.ResponseTraceAttrs(resp), CurrentHTTPClient{}.ResponseTraceAttrs(resp)...) + return OldHTTPClient{}.ResponseTraceAttrs(resp, attrs) } - - return CurrentHTTPClient{}.ResponseTraceAttrs(resp) + return attrs } func (c HTTPClient) Status(code int) (codes.Code, string) { @@ -297,9 +303,10 @@ func (s HTTPClient) RecordResponseSize(ctx context.Context, responseData int64, } func (s HTTPClient) TraceAttributes(host string) []attribute.KeyValue { + attrs := CurrentHTTPClient{}.TraceAttributes(host) if s.duplicate { - return append(OldHTTPClient{}.TraceAttributes(host), CurrentHTTPClient{}.TraceAttributes(host)...) + return OldHTTPClient{}.TraceAttributes(host, attrs) } - return CurrentHTTPClient{}.TraceAttributes(host) + return attrs } diff --git a/instrumentation/net/http/otelhttp/internal/semconv/env_test.go b/instrumentation/net/http/otelhttp/internal/semconv/env_test.go index 39b243ba915..afa6db6c20c 100644 --- a/instrumentation/net/http/otelhttp/internal/semconv/env_test.go +++ b/instrumentation/net/http/otelhttp/internal/semconv/env_test.go @@ -151,8 +151,8 @@ func TestHTTPClientTraceAttributes(t *testing.T) { optinVal: "http/dup", wantAttributes: []attribute.KeyValue{ - attribute.String("net.host.name", "example.com"), attribute.String("server.address", "example.com"), + attribute.String("net.host.name", "example.com"), }, }, } { @@ -188,8 +188,8 @@ func TestClientTraceAttributes(t *testing.T) { host: "example.com", wantAttributes: []attribute.KeyValue{ - attribute.String("net.host.name", "example.com"), attribute.String("server.address", "example.com"), + attribute.String("net.host.name", "example.com"), }, }, } { diff --git a/instrumentation/net/http/otelhttp/internal/semconv/v1.20.0.go b/instrumentation/net/http/otelhttp/internal/semconv/v1.20.0.go index 582bb620189..72420422619 100644 --- a/instrumentation/net/http/otelhttp/internal/semconv/v1.20.0.go +++ b/instrumentation/net/http/otelhttp/internal/semconv/v1.20.0.go @@ -37,8 +37,8 @@ type OldHTTPServer 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 (o OldHTTPServer) RequestTraceAttrs(server string, req *http.Request) []attribute.KeyValue { - return semconvutil.HTTPServerRequest(server, req, semconvutil.HTTPServerRequestOptions{}) +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 { @@ -48,9 +48,7 @@ func (o OldHTTPServer) NetworkTransportAttr(network string) attribute.KeyValue { // 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) []attribute.KeyValue { - attributes := []attribute.KeyValue{} - +func (o OldHTTPServer) ResponseTraceAttrs(resp ResponseTelemetry, attributes []attribute.KeyValue) []attribute.KeyValue { if resp.ReadBytes > 0 { attributes = append(attributes, semconv.HTTPRequestContentLength(int(resp.ReadBytes))) } @@ -179,12 +177,12 @@ func (o OldHTTPServer) scheme(https bool) attribute.KeyValue { // nolint:revive type OldHTTPClient struct{} -func (o OldHTTPClient) RequestTraceAttrs(req *http.Request) []attribute.KeyValue { - return semconvutil.HTTPClientRequest(req) +func (o OldHTTPClient) RequestTraceAttrs(req *http.Request, attrs []attribute.KeyValue) []attribute.KeyValue { + return semconvutil.HTTPClientRequest(req, attrs) } -func (o OldHTTPClient) ResponseTraceAttrs(resp *http.Response) []attribute.KeyValue { - return semconvutil.HTTPClientResponse(resp) +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 { @@ -270,8 +268,6 @@ func (o OldHTTPClient) createMeasures(meter metric.Meter) (metric.Int64Counter, } // TraceAttributes returns attributes for httptrace. -func (c OldHTTPClient) TraceAttributes(host string) []attribute.KeyValue { - return []attribute.KeyValue{ - semconv.NetHostName(host), - } +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/httpconv.go b/instrumentation/net/http/otelhttp/internal/semconvutil/httpconv.go index 99f0cc83e72..26e4784c8e8 100644 --- a/instrumentation/net/http/otelhttp/internal/semconvutil/httpconv.go +++ b/instrumentation/net/http/otelhttp/internal/semconvutil/httpconv.go @@ -10,6 +10,7 @@ package semconvutil // import "go.opentelemetry.io/contrib/instrumentation/net/h import ( "fmt" "net/http" + "slices" "strings" "go.opentelemetry.io/otel/attribute" @@ -32,9 +33,9 @@ type HTTPServerRequestOptions struct { // attributes. If a complete set of attributes can be generated using the // request contained in resp. For example: // -// append(HTTPClientResponse(resp), ClientRequest(resp.Request)...) -func HTTPClientResponse(resp *http.Response) []attribute.KeyValue { - return hc.ClientResponse(resp) +// 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. @@ -42,8 +43,8 @@ func HTTPClientResponse(resp *http.Response) []attribute.KeyValue { // "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) []attribute.KeyValue { - return hc.ClientRequest(req) +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. @@ -81,8 +82,8 @@ func HTTPClientStatus(code int) (codes.Code, string) { // "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) []attribute.KeyValue { - return hc.ServerRequest(server, req, opts) +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 @@ -159,8 +160,8 @@ var hc = &httpConv{ // attributes. If a complete set of attributes can be generated using the // request contained in resp. For example: // -// append(ClientResponse(resp), ClientRequest(resp.Request)...) -func (c *httpConv) ClientResponse(resp *http.Response) []attribute.KeyValue { +// 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 @@ -172,8 +173,11 @@ func (c *httpConv) ClientResponse(resp *http.Response) []attribute.KeyValue { if resp.ContentLength > 0 { n++ } + if n == 0 { + return attrs + } - attrs := make([]attribute.KeyValue, 0, n) + attrs = slices.Grow(attrs, n) if resp.StatusCode > 0 { attrs = append(attrs, c.HTTPStatusCodeKey.Int(resp.StatusCode)) } @@ -188,7 +192,7 @@ func (c *httpConv) ClientResponse(resp *http.Response) []attribute.KeyValue { // "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) []attribute.KeyValue { +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 @@ -227,8 +231,7 @@ func (c *httpConv) ClientRequest(req *http.Request) []attribute.KeyValue { n++ } - attrs := make([]attribute.KeyValue, 0, n) - + attrs = slices.Grow(attrs, n) attrs = append(attrs, c.method(req.Method)) var u string @@ -311,7 +314,7 @@ func (c *httpConv) ClientRequestMetrics(req *http.Request) []attribute.KeyValue // 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) []attribute.KeyValue { +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 @@ -394,7 +397,7 @@ func (c *httpConv) ServerRequest(server string, req *http.Request, opts HTTPServ n++ } - attrs := make([]attribute.KeyValue, 0, n) + attrs = slices.Grow(attrs, n) attrs = append(attrs, c.method(req.Method)) attrs = append(attrs, c.scheme(req.TLS != nil)) diff --git a/instrumentation/net/http/otelhttp/internal/semconvutil/httpconv_test.go b/instrumentation/net/http/otelhttp/internal/semconvutil/httpconv_test.go index 9b618b558cd..26e8f65e665 100644 --- a/instrumentation/net/http/otelhttp/internal/semconvutil/httpconv_test.go +++ b/instrumentation/net/http/otelhttp/internal/semconvutil/httpconv_test.go @@ -27,7 +27,7 @@ func TestHTTPClientResponse(t *testing.T) { StatusCode: stat, ContentLength: n, } - got := HTTPClientResponse(resp) + got := HTTPClientResponse(resp, nil) assert.Equal(t, 2, cap(got), "slice capacity") assert.ElementsMatch(t, []attribute.KeyValue{ attribute.Key("http.status_code").Int(stat), @@ -55,7 +55,7 @@ func TestHTTPSClientRequest(t *testing.T) { attribute.String("http.url", "https://127.0.0.1:443/resource"), attribute.String("net.peer.name", "127.0.0.1"), }, - HTTPClientRequest(req), + HTTPClientRequest(req, nil), ) } @@ -115,7 +115,7 @@ func TestHTTPClientRequest(t *testing.T) { attribute.String("user_agent.original", agent), attribute.Int("http.request_content_length", n), }, - HTTPClientRequest(req), + HTTPClientRequest(req, nil), ) } @@ -156,7 +156,7 @@ func TestHTTPClientRequestMetrics(t *testing.T) { func TestHTTPClientRequestRequired(t *testing.T) { req := new(http.Request) var got []attribute.KeyValue - assert.NotPanics(t, func() { got = HTTPClientRequest(req) }) + assert.NotPanics(t, func() { got = HTTPClientRequest(req, nil) }) want := []attribute.KeyValue{ attribute.String("http.method", "GET"), attribute.String("http.url", ""), @@ -248,7 +248,7 @@ func TestHTTPServerRequest(t *testing.T) { attribute.String("net.protocol.version", "1.1"), attribute.String("http.target", "/"), }, - HTTPServerRequest("", got, tt.httpServerRequestOpts)) + HTTPServerRequest("", got, tt.httpServerRequestOpts, nil)) }) } } @@ -301,14 +301,14 @@ func TestHTTPServerName(t *testing.T) { ) portStr := strconv.Itoa(port) server := host + ":" + portStr - assert.NotPanics(t, func() { got = HTTPServerRequest(server, req, HTTPServerRequestOptions{}) }) + 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{}) }) + 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)) } @@ -316,7 +316,7 @@ func TestHTTPServerName(t *testing.T) { func TestHTTPServerRequestFailsGracefully(t *testing.T) { req := new(http.Request) var got []attribute.KeyValue - assert.NotPanics(t, func() { got = HTTPServerRequest("", req, HTTPServerRequestOptions{}) }) + assert.NotPanics(t, func() { got = HTTPServerRequest("", req, HTTPServerRequestOptions{}, nil) }) want := []attribute.KeyValue{ attribute.String("http.method", "GET"), attribute.String("http.scheme", "http"), diff --git a/internal/shared/semconv/env.go.tmpl b/internal/shared/semconv/env.go.tmpl index b0a159c6df8..809c559cd27 100644 --- a/internal/shared/semconv/env.go.tmpl +++ b/internal/shared/semconv/env.go.tmpl @@ -62,15 +62,19 @@ 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 append(OldHTTPServer{}.RequestTraceAttrs(server, req), CurrentHTTPServer{}.RequestTraceAttrs(server, req, opts)...) + return OldHTTPServer{}.RequestTraceAttrs(server, req, attrs) } - return CurrentHTTPServer{}.RequestTraceAttrs(server, req, opts) + return attrs } func (s HTTPServer) NetworkTransportAttr(network string) []attribute.KeyValue { if s.duplicate { - return append([]attribute.KeyValue{OldHTTPServer{}.NetworkTransportAttr(network)}, CurrentHTTPServer{}.NetworkTransportAttr(network)) + return []attribute.KeyValue{ + OldHTTPServer{}.NetworkTransportAttr(network), + CurrentHTTPServer{}.NetworkTransportAttr(network), + } } return []attribute.KeyValue{ CurrentHTTPServer{}.NetworkTransportAttr(network), @@ -81,10 +85,11 @@ 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 append(OldHTTPServer{}.ResponseTraceAttrs(resp), CurrentHTTPServer{}.ResponseTraceAttrs(resp)...) + return OldHTTPServer{}.ResponseTraceAttrs(resp, attrs) } - return CurrentHTTPServer{}.ResponseTraceAttrs(resp) + return attrs } // Route returns the attribute for the route. @@ -208,19 +213,20 @@ func NewHTTPClient(meter metric.Meter) HTTPClient { // 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 append(OldHTTPClient{}.RequestTraceAttrs(req), CurrentHTTPClient{}.RequestTraceAttrs(req)...) + return OldHTTPClient{}.RequestTraceAttrs(req, attrs) } - return CurrentHTTPClient{}.RequestTraceAttrs(req) + return attrs } // 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 append(OldHTTPClient{}.ResponseTraceAttrs(resp), CurrentHTTPClient{}.ResponseTraceAttrs(resp)...) + return OldHTTPClient{}.ResponseTraceAttrs(resp, attrs) } - - return CurrentHTTPClient{}.ResponseTraceAttrs(resp) + return attrs } func (c HTTPClient) Status(code int) (codes.Code, string) { @@ -297,9 +303,10 @@ func (s HTTPClient) RecordResponseSize(ctx context.Context, responseData int64, } func (s HTTPClient) TraceAttributes(host string) []attribute.KeyValue { + attrs := CurrentHTTPClient{}.TraceAttributes(host) if s.duplicate { - return append(OldHTTPClient{}.TraceAttributes(host), CurrentHTTPClient{}.TraceAttributes(host)...) + return OldHTTPClient{}.TraceAttributes(host, attrs) } - return CurrentHTTPClient{}.TraceAttributes(host) + return attrs } diff --git a/internal/shared/semconv/env_test.go.tmpl b/internal/shared/semconv/env_test.go.tmpl index 39b243ba915..afa6db6c20c 100644 --- a/internal/shared/semconv/env_test.go.tmpl +++ b/internal/shared/semconv/env_test.go.tmpl @@ -151,8 +151,8 @@ func TestHTTPClientTraceAttributes(t *testing.T) { optinVal: "http/dup", wantAttributes: []attribute.KeyValue{ - attribute.String("net.host.name", "example.com"), attribute.String("server.address", "example.com"), + attribute.String("net.host.name", "example.com"), }, }, } { @@ -188,8 +188,8 @@ func TestClientTraceAttributes(t *testing.T) { host: "example.com", wantAttributes: []attribute.KeyValue{ - attribute.String("net.host.name", "example.com"), attribute.String("server.address", "example.com"), + attribute.String("net.host.name", "example.com"), }, }, } { diff --git a/internal/shared/semconv/v1.20.0.go.tmpl b/internal/shared/semconv/v1.20.0.go.tmpl index 32c763518b4..fd8bf0f712d 100644 --- a/internal/shared/semconv/v1.20.0.go.tmpl +++ b/internal/shared/semconv/v1.20.0.go.tmpl @@ -37,8 +37,8 @@ type OldHTTPServer 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 (o OldHTTPServer) RequestTraceAttrs(server string, req *http.Request) []attribute.KeyValue { - return semconvutil.HTTPServerRequest(server, req, semconvutil.HTTPServerRequestOptions{}) +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 { @@ -48,9 +48,7 @@ func (o OldHTTPServer) NetworkTransportAttr(network string) attribute.KeyValue { // 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) []attribute.KeyValue { - attributes := []attribute.KeyValue{} - +func (o OldHTTPServer) ResponseTraceAttrs(resp ResponseTelemetry, attributes []attribute.KeyValue) []attribute.KeyValue { if resp.ReadBytes > 0 { attributes = append(attributes, semconv.HTTPRequestContentLength(int(resp.ReadBytes))) } @@ -179,12 +177,12 @@ func (o OldHTTPServer) scheme(https bool) attribute.KeyValue { // nolint:revive type OldHTTPClient struct{} -func (o OldHTTPClient) RequestTraceAttrs(req *http.Request) []attribute.KeyValue { - return semconvutil.HTTPClientRequest(req) +func (o OldHTTPClient) RequestTraceAttrs(req *http.Request, attrs []attribute.KeyValue) []attribute.KeyValue { + return semconvutil.HTTPClientRequest(req, attrs) } -func (o OldHTTPClient) ResponseTraceAttrs(resp *http.Response) []attribute.KeyValue { - return semconvutil.HTTPClientResponse(resp) +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 { @@ -270,8 +268,6 @@ func (o OldHTTPClient) createMeasures(meter metric.Meter) (metric.Int64Counter, } // TraceAttributes returns attributes for httptrace. -func (c OldHTTPClient) TraceAttributes(host string) []attribute.KeyValue { - return []attribute.KeyValue{ - semconv.NetHostName(host), - } +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 index 406f70c503c..c41aa8baebb 100644 --- a/internal/shared/semconvutil/httpconv.go.tmpl +++ b/internal/shared/semconvutil/httpconv.go.tmpl @@ -10,6 +10,7 @@ package semconvutil import ( "fmt" "net/http" + "slices" "strings" "go.opentelemetry.io/otel/attribute" @@ -32,9 +33,9 @@ type HTTPServerRequestOptions struct { // attributes. If a complete set of attributes can be generated using the // request contained in resp. For example: // -// append(HTTPClientResponse(resp), ClientRequest(resp.Request)...) -func HTTPClientResponse(resp *http.Response) []attribute.KeyValue { - return hc.ClientResponse(resp) +// 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. @@ -42,8 +43,8 @@ func HTTPClientResponse(resp *http.Response) []attribute.KeyValue { // "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) []attribute.KeyValue { - return hc.ClientRequest(req) +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. @@ -81,8 +82,8 @@ func HTTPClientStatus(code int) (codes.Code, string) { // "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) []attribute.KeyValue { - return hc.ServerRequest(server, req, opts) +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 @@ -159,8 +160,8 @@ var hc = &httpConv{ // attributes. If a complete set of attributes can be generated using the // request contained in resp. For example: // -// append(ClientResponse(resp), ClientRequest(resp.Request)...) -func (c *httpConv) ClientResponse(resp *http.Response) []attribute.KeyValue { +// 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 @@ -172,8 +173,11 @@ func (c *httpConv) ClientResponse(resp *http.Response) []attribute.KeyValue { if resp.ContentLength > 0 { n++ } + if n == 0 { + return attrs + } - attrs := make([]attribute.KeyValue, 0, n) + attrs = slices.Grow(attrs, n) if resp.StatusCode > 0 { attrs = append(attrs, c.HTTPStatusCodeKey.Int(resp.StatusCode)) } @@ -188,7 +192,7 @@ func (c *httpConv) ClientResponse(resp *http.Response) []attribute.KeyValue { // "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) []attribute.KeyValue { +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 @@ -227,8 +231,7 @@ func (c *httpConv) ClientRequest(req *http.Request) []attribute.KeyValue { n++ } - attrs := make([]attribute.KeyValue, 0, n) - + attrs = slices.Grow(attrs, n) attrs = append(attrs, c.method(req.Method)) var u string @@ -311,7 +314,7 @@ func (c *httpConv) ClientRequestMetrics(req *http.Request) []attribute.KeyValue // 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) []attribute.KeyValue { +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 @@ -394,7 +397,7 @@ func (c *httpConv) ServerRequest(server string, req *http.Request, opts HTTPServ n++ } - attrs := make([]attribute.KeyValue, 0, n) + attrs = slices.Grow(attrs, n) attrs = append(attrs, c.method(req.Method)) attrs = append(attrs, c.scheme(req.TLS != nil)) diff --git a/internal/shared/semconvutil/httpconv_test.go.tmpl b/internal/shared/semconvutil/httpconv_test.go.tmpl index 9b618b558cd..26e8f65e665 100644 --- a/internal/shared/semconvutil/httpconv_test.go.tmpl +++ b/internal/shared/semconvutil/httpconv_test.go.tmpl @@ -27,7 +27,7 @@ func TestHTTPClientResponse(t *testing.T) { StatusCode: stat, ContentLength: n, } - got := HTTPClientResponse(resp) + got := HTTPClientResponse(resp, nil) assert.Equal(t, 2, cap(got), "slice capacity") assert.ElementsMatch(t, []attribute.KeyValue{ attribute.Key("http.status_code").Int(stat), @@ -55,7 +55,7 @@ func TestHTTPSClientRequest(t *testing.T) { attribute.String("http.url", "https://127.0.0.1:443/resource"), attribute.String("net.peer.name", "127.0.0.1"), }, - HTTPClientRequest(req), + HTTPClientRequest(req, nil), ) } @@ -115,7 +115,7 @@ func TestHTTPClientRequest(t *testing.T) { attribute.String("user_agent.original", agent), attribute.Int("http.request_content_length", n), }, - HTTPClientRequest(req), + HTTPClientRequest(req, nil), ) } @@ -156,7 +156,7 @@ func TestHTTPClientRequestMetrics(t *testing.T) { func TestHTTPClientRequestRequired(t *testing.T) { req := new(http.Request) var got []attribute.KeyValue - assert.NotPanics(t, func() { got = HTTPClientRequest(req) }) + assert.NotPanics(t, func() { got = HTTPClientRequest(req, nil) }) want := []attribute.KeyValue{ attribute.String("http.method", "GET"), attribute.String("http.url", ""), @@ -248,7 +248,7 @@ func TestHTTPServerRequest(t *testing.T) { attribute.String("net.protocol.version", "1.1"), attribute.String("http.target", "/"), }, - HTTPServerRequest("", got, tt.httpServerRequestOpts)) + HTTPServerRequest("", got, tt.httpServerRequestOpts, nil)) }) } } @@ -301,14 +301,14 @@ func TestHTTPServerName(t *testing.T) { ) portStr := strconv.Itoa(port) server := host + ":" + portStr - assert.NotPanics(t, func() { got = HTTPServerRequest(server, req, HTTPServerRequestOptions{}) }) + 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{}) }) + 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)) } @@ -316,7 +316,7 @@ func TestHTTPServerName(t *testing.T) { func TestHTTPServerRequestFailsGracefully(t *testing.T) { req := new(http.Request) var got []attribute.KeyValue - assert.NotPanics(t, func() { got = HTTPServerRequest("", req, HTTPServerRequestOptions{}) }) + assert.NotPanics(t, func() { got = HTTPServerRequest("", req, HTTPServerRequestOptions{}, nil) }) want := []attribute.KeyValue{ attribute.String("http.method", "GET"), attribute.String("http.scheme", "http"),