Skip to content

Commit 8846bcf

Browse files
HTTP Semconv migration Part1 Client - v1.24.0 support (#5773)
This change adds the new semantic version (v1.24.0) attribute producer to the semconv of otlehttp. Part of #5332 --------- Co-authored-by: Aaron Clawson <[email protected]> Co-authored-by: Tyler Yahn <[email protected]>
1 parent c80e464 commit 8846bcf

File tree

5 files changed

+173
-5
lines changed

5 files changed

+173
-5
lines changed

instrumentation/net/http/otelhttp/internal/semconv/common_test.go

+88
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"github.com/stretchr/testify/require"
1515

1616
"go.opentelemetry.io/otel/attribute"
17+
"go.opentelemetry.io/otel/codes"
1718
)
1819

1920
type testServerReq struct {
@@ -65,3 +66,90 @@ func testTraceRequest(t *testing.T, serv HTTPServer, want func(testServerReq) []
6566

6667
assert.ElementsMatch(t, want(srvReq), serv.RequestTraceAttrs("", req))
6768
}
69+
70+
func TestHTTPClientStatus(t *testing.T) {
71+
tests := []struct {
72+
code int
73+
stat codes.Code
74+
msg bool
75+
}{
76+
{0, codes.Error, true},
77+
{http.StatusContinue, codes.Unset, false},
78+
{http.StatusSwitchingProtocols, codes.Unset, false},
79+
{http.StatusProcessing, codes.Unset, false},
80+
{http.StatusEarlyHints, codes.Unset, false},
81+
{http.StatusOK, codes.Unset, false},
82+
{http.StatusCreated, codes.Unset, false},
83+
{http.StatusAccepted, codes.Unset, false},
84+
{http.StatusNonAuthoritativeInfo, codes.Unset, false},
85+
{http.StatusNoContent, codes.Unset, false},
86+
{http.StatusResetContent, codes.Unset, false},
87+
{http.StatusPartialContent, codes.Unset, false},
88+
{http.StatusMultiStatus, codes.Unset, false},
89+
{http.StatusAlreadyReported, codes.Unset, false},
90+
{http.StatusIMUsed, codes.Unset, false},
91+
{http.StatusMultipleChoices, codes.Unset, false},
92+
{http.StatusMovedPermanently, codes.Unset, false},
93+
{http.StatusFound, codes.Unset, false},
94+
{http.StatusSeeOther, codes.Unset, false},
95+
{http.StatusNotModified, codes.Unset, false},
96+
{http.StatusUseProxy, codes.Unset, false},
97+
{306, codes.Unset, false},
98+
{http.StatusTemporaryRedirect, codes.Unset, false},
99+
{http.StatusPermanentRedirect, codes.Unset, false},
100+
{http.StatusBadRequest, codes.Error, false},
101+
{http.StatusUnauthorized, codes.Error, false},
102+
{http.StatusPaymentRequired, codes.Error, false},
103+
{http.StatusForbidden, codes.Error, false},
104+
{http.StatusNotFound, codes.Error, false},
105+
{http.StatusMethodNotAllowed, codes.Error, false},
106+
{http.StatusNotAcceptable, codes.Error, false},
107+
{http.StatusProxyAuthRequired, codes.Error, false},
108+
{http.StatusRequestTimeout, codes.Error, false},
109+
{http.StatusConflict, codes.Error, false},
110+
{http.StatusGone, codes.Error, false},
111+
{http.StatusLengthRequired, codes.Error, false},
112+
{http.StatusPreconditionFailed, codes.Error, false},
113+
{http.StatusRequestEntityTooLarge, codes.Error, false},
114+
{http.StatusRequestURITooLong, codes.Error, false},
115+
{http.StatusUnsupportedMediaType, codes.Error, false},
116+
{http.StatusRequestedRangeNotSatisfiable, codes.Error, false},
117+
{http.StatusExpectationFailed, codes.Error, false},
118+
{http.StatusTeapot, codes.Error, false},
119+
{http.StatusMisdirectedRequest, codes.Error, false},
120+
{http.StatusUnprocessableEntity, codes.Error, false},
121+
{http.StatusLocked, codes.Error, false},
122+
{http.StatusFailedDependency, codes.Error, false},
123+
{http.StatusTooEarly, codes.Error, false},
124+
{http.StatusUpgradeRequired, codes.Error, false},
125+
{http.StatusPreconditionRequired, codes.Error, false},
126+
{http.StatusTooManyRequests, codes.Error, false},
127+
{http.StatusRequestHeaderFieldsTooLarge, codes.Error, false},
128+
{http.StatusUnavailableForLegalReasons, codes.Error, false},
129+
{499, codes.Error, false},
130+
{http.StatusInternalServerError, codes.Error, false},
131+
{http.StatusNotImplemented, codes.Error, false},
132+
{http.StatusBadGateway, codes.Error, false},
133+
{http.StatusServiceUnavailable, codes.Error, false},
134+
{http.StatusGatewayTimeout, codes.Error, false},
135+
{http.StatusHTTPVersionNotSupported, codes.Error, false},
136+
{http.StatusVariantAlsoNegotiates, codes.Error, false},
137+
{http.StatusInsufficientStorage, codes.Error, false},
138+
{http.StatusLoopDetected, codes.Error, false},
139+
{http.StatusNotExtended, codes.Error, false},
140+
{http.StatusNetworkAuthenticationRequired, codes.Error, false},
141+
{600, codes.Error, true},
142+
}
143+
144+
for _, test := range tests {
145+
t.Run(strconv.Itoa(test.code), func(t *testing.T) {
146+
c, msg := HTTPClient{}.Status(test.code)
147+
assert.Equal(t, test.stat, c)
148+
if test.msg && msg == "" {
149+
t.Errorf("expected non-empty message for %d", test.code)
150+
} else if !test.msg && msg != "" {
151+
t.Errorf("expected empty message for %d, got: %s", test.code, msg)
152+
}
153+
})
154+
}
155+
}

instrumentation/net/http/otelhttp/internal/semconv/env.go

+31
Original file line numberDiff line numberDiff line change
@@ -80,3 +80,34 @@ func ServerStatus(code int) (codes.Code, string) {
8080
}
8181
return codes.Unset, ""
8282
}
83+
84+
type HTTPClient struct {
85+
// TODO (#5332): Support for new semantic conventions
86+
// duplicate bool
87+
}
88+
89+
func NewHTTPClient() HTTPClient {
90+
// TODO (#5332): Support for new semantic conventions
91+
// env := strings.ToLower(os.Getenv("OTEL_HTTP_CLIENT_COMPATIBILITY_MODE"))
92+
return HTTPClient{}
93+
}
94+
95+
// RequestTraceAttrs returns attributes for an HTTP request made by a client.
96+
func (c HTTPClient) RequestTraceAttrs(req *http.Request) []attribute.KeyValue {
97+
return oldHTTPClient{}.RequestTraceAttrs(req)
98+
}
99+
100+
// ResponseTraceAttrs returns metric attributes for an HTTP request made by a client.
101+
func (c HTTPClient) ResponseTraceAttrs(resp *http.Response) []attribute.KeyValue {
102+
return oldHTTPClient{}.ResponseTraceAttrs(resp)
103+
}
104+
105+
func (c HTTPClient) Status(code int) (codes.Code, string) {
106+
if code < 100 || code >= 600 {
107+
return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code)
108+
}
109+
if code >= 400 {
110+
return codes.Error, ""
111+
}
112+
return codes.Unset, ""
113+
}

instrumentation/net/http/otelhttp/internal/semconv/v1.20.0.go

+10
Original file line numberDiff line numberDiff line change
@@ -72,3 +72,13 @@ func (o oldHTTPServer) Route(route string) attribute.KeyValue {
7272
func HTTPStatusCode(status int) attribute.KeyValue {
7373
return semconv.HTTPStatusCode(status)
7474
}
75+
76+
type oldHTTPClient struct{}
77+
78+
func (o oldHTTPClient) RequestTraceAttrs(req *http.Request) []attribute.KeyValue {
79+
return semconvutil.HTTPClientRequest(req)
80+
}
81+
82+
func (o oldHTTPClient) ResponseTraceAttrs(resp *http.Response) []attribute.KeyValue {
83+
return semconvutil.HTTPClientResponse(resp)
84+
}

instrumentation/net/http/otelhttp/internal/semconv/v1.20.0_test.go

+36
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ package semconv
55

66
import (
77
"fmt"
8+
"net/http"
9+
"strings"
810
"testing"
911

1012
"github.com/stretchr/testify/assert"
@@ -83,3 +85,37 @@ func TestV120TraceResponse(t *testing.T) {
8385
})
8486
}
8587
}
88+
89+
func TestV120ClientRequest(t *testing.T) {
90+
body := strings.NewReader("Hello, world!")
91+
url := "https://example.com:8888/foo/bar?stuff=morestuff"
92+
req, err := http.NewRequest("POST", url, body)
93+
assert.NoError(t, err)
94+
req.Header.Set("User-Agent", "go-test-agent")
95+
96+
want := []attribute.KeyValue{
97+
attribute.String("http.method", "POST"),
98+
attribute.String("http.url", url),
99+
attribute.String("net.peer.name", "example.com"),
100+
attribute.Int("net.peer.port", 8888),
101+
attribute.Int("http.request_content_length", body.Len()),
102+
attribute.String("user_agent.original", "go-test-agent"),
103+
}
104+
got := oldHTTPClient{}.RequestTraceAttrs(req)
105+
assert.ElementsMatch(t, want, got)
106+
}
107+
108+
func TestV120ClientResponse(t *testing.T) {
109+
resp := http.Response{
110+
StatusCode: 200,
111+
ContentLength: 123,
112+
}
113+
114+
want := []attribute.KeyValue{
115+
attribute.Int("http.response_content_length", 123),
116+
attribute.Int("http.status_code", 200),
117+
}
118+
119+
got := oldHTTPClient{}.ResponseTraceAttrs(&resp)
120+
assert.ElementsMatch(t, want, got)
121+
}

instrumentation/net/http/otelhttp/transport.go

+8-5
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,14 @@ import (
1111
"sync/atomic"
1212
"time"
1313

14+
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconv"
1415
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconvutil"
1516
"go.opentelemetry.io/otel"
1617
"go.opentelemetry.io/otel/attribute"
1718
"go.opentelemetry.io/otel/codes"
1819
"go.opentelemetry.io/otel/metric"
1920
"go.opentelemetry.io/otel/propagation"
20-
semconv "go.opentelemetry.io/otel/semconv/v1.20.0"
21+
2122
"go.opentelemetry.io/otel/trace"
2223
)
2324

@@ -34,6 +35,7 @@ type Transport struct {
3435
spanNameFormatter func(string, *http.Request) string
3536
clientTrace func(context.Context) *httptrace.ClientTrace
3637

38+
semconv semconv.HTTPClient
3739
requestBytesCounter metric.Int64Counter
3840
responseBytesCounter metric.Int64Counter
3941
latencyMeasure metric.Float64Histogram
@@ -53,7 +55,8 @@ func NewTransport(base http.RoundTripper, opts ...Option) *Transport {
5355
}
5456

5557
t := Transport{
56-
rt: base,
58+
rt: base,
59+
semconv: semconv.NewHTTPClient(),
5760
}
5861

5962
defaultOpts := []Option{
@@ -155,7 +158,7 @@ func (t *Transport) RoundTrip(r *http.Request) (*http.Response, error) {
155158
r.Body = &bw
156159
}
157160

158-
span.SetAttributes(semconvutil.HTTPClientRequest(r)...)
161+
span.SetAttributes(t.semconv.RequestTraceAttrs(r)...)
159162
t.propagators.Inject(ctx, propagation.HeaderCarrier(r.Header))
160163

161164
res, err := t.rt.RoundTrip(r)
@@ -180,8 +183,8 @@ func (t *Transport) RoundTrip(r *http.Request) (*http.Response, error) {
180183
}
181184

182185
// traces
183-
span.SetAttributes(semconvutil.HTTPClientResponse(res)...)
184-
span.SetStatus(semconvutil.HTTPClientStatus(res.StatusCode))
186+
span.SetAttributes(t.semconv.ResponseTraceAttrs(res)...)
187+
span.SetStatus(t.semconv.Status(res.StatusCode))
185188

186189
res.Body = newWrappedBody(span, readRecordFunc, res.Body)
187190

0 commit comments

Comments
 (0)