From 0273b8cfd1c6d632826cd1309835e862ce6926cf Mon Sep 17 00:00:00 2001 From: ItalyPaleAle <43508+ItalyPaleAle@users.noreply.github.com> Date: Sun, 8 Sep 2024 14:34:22 -0700 Subject: [PATCH 01/11] Gin exporter: support reading Client IP from custom headers (and make sure proxy is trusted) With Gin, it's possible to configure the server to read the Client IP from custom headers; examples include `X-Real-Ip` or `CF-Connecting-IP`. This PR makes it possible to set as span attribute the same IP that Gin reads. Additionally, it makes sure that headers such as "X-Forwarded-For" are used only if Gin is configured to trust the upstream server PS: Also fixed unit tests, where there were assertions inside handlers, which are executed in separate goroutines --- .../gin-gonic/gin/otelgin/gintrace.go | 15 +++ .../gin-gonic/gin/otelgin/gintrace_test.go | 122 ++++++++++++++++-- .../github.com/gin-gonic/gin/otelgin/go.mod | 2 + .../github.com/gin-gonic/gin/otelgin/go.sum | 4 + 4 files changed, 135 insertions(+), 8 deletions(-) diff --git a/instrumentation/github.com/gin-gonic/gin/otelgin/gintrace.go b/instrumentation/github.com/gin-gonic/gin/otelgin/gintrace.go index 1affd4d6ca5..6702632188b 100644 --- a/instrumentation/github.com/gin-gonic/gin/otelgin/gintrace.go +++ b/instrumentation/github.com/gin-gonic/gin/otelgin/gintrace.go @@ -66,11 +66,26 @@ func Middleware(service string, opts ...Option) gin.HandlerFunc { c.Request = c.Request.WithContext(savedCtx) }() ctx := cfg.Propagators.Extract(savedCtx, propagation.HeaderCarrier(c.Request.Header)) + + // Gin's ClientIP method can detect the client's IP from various headers set by proxies, and it's configurable + // However, semconvutil supports the X-Forwarded-For header only + // So we need to temporarily set the client's IP in the X-Forwarded-For header, before restoring it after the call + originalHeader := c.Request.Header.Get("X-Forwarded-For") + c.Request.Header.Set("X-Forwarded-For", c.ClientIP()) + opts := []oteltrace.SpanStartOption{ oteltrace.WithAttributes(semconvutil.HTTPServerRequest(service, c.Request)...), oteltrace.WithAttributes(semconv.HTTPRoute(c.FullPath())), oteltrace.WithSpanKind(oteltrace.SpanKindServer), } + + // Restore the previous value for X-Forwarded-For + if originalHeader == "" { + c.Request.Header.Del("X-Forwarded-For") + } else { + c.Request.Header.Set("X-Forwarded-For", originalHeader) + } + var spanName string if cfg.SpanNameFormatter == nil { spanName = c.FullPath() diff --git a/instrumentation/github.com/gin-gonic/gin/otelgin/gintrace_test.go b/instrumentation/github.com/gin-gonic/gin/otelgin/gintrace_test.go index ecef39e3aeb..3c15a781ec2 100644 --- a/instrumentation/github.com/gin-gonic/gin/otelgin/gintrace_test.go +++ b/instrumentation/github.com/gin-gonic/gin/otelgin/gintrace_test.go @@ -10,12 +10,17 @@ import ( "net/http" "net/http/httptest" "testing" + "time" "github.com/gin-gonic/gin" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/propagation" + sdktrace "go.opentelemetry.io/otel/sdk/trace" + "go.opentelemetry.io/otel/sdk/trace/tracetest" "go.opentelemetry.io/otel/trace" "go.opentelemetry.io/otel/trace/noop" @@ -32,8 +37,9 @@ func TestGetSpanNotInstrumented(t *testing.T) { // Assert we don't have a span on the context. span := trace.SpanFromContext(c.Request.Context()) ok := !span.SpanContext().IsValid() - assert.True(t, ok) - _, _ = c.Writer.Write([]byte("ok")) + if !ok { + c.Status(http.StatusInternalServerError) + } }) r := httptest.NewRequest("GET", "/ping", nil) w := httptest.NewRecorder() @@ -60,13 +66,20 @@ func TestPropagationWithGlobalPropagators(t *testing.T) { router := gin.New() router.Use(Middleware("foobar", WithTracerProvider(provider))) + resCh := make(chan trace.Span, 1) router.GET("/user/:id", func(c *gin.Context) { - span := trace.SpanFromContext(c.Request.Context()) - assert.Equal(t, sc.TraceID(), span.SpanContext().TraceID()) - assert.Equal(t, sc.SpanID(), span.SpanContext().SpanID()) + resCh <- trace.SpanFromContext(c.Request.Context()) }) router.ServeHTTP(w, r) + + select { + case span := <-resCh: + assert.Equal(t, sc.TraceID(), span.SpanContext().TraceID()) + assert.Equal(t, sc.SpanID(), span.SpanContext().SpanID()) + case <-time.After(5 * time.Second): + t.Fatal("did not receive signal in 5s") + } } func TestPropagationWithCustomPropagators(t *testing.T) { @@ -87,11 +100,104 @@ func TestPropagationWithCustomPropagators(t *testing.T) { router := gin.New() router.Use(Middleware("foobar", WithTracerProvider(provider), WithPropagators(b3))) + resCh := make(chan trace.Span, 1) router.GET("/user/:id", func(c *gin.Context) { - span := trace.SpanFromContext(c.Request.Context()) - assert.Equal(t, sc.TraceID(), span.SpanContext().TraceID()) - assert.Equal(t, sc.SpanID(), span.SpanContext().SpanID()) + resCh <- trace.SpanFromContext(c.Request.Context()) }) router.ServeHTTP(w, r) + + select { + case span := <-resCh: + assert.Equal(t, sc.TraceID(), span.SpanContext().TraceID()) + assert.Equal(t, sc.SpanID(), span.SpanContext().SpanID()) + case <-time.After(5 * time.Second): + t.Fatal("did not receive signal in 5s") + } +} + +func TestClientIP(t *testing.T) { + testFn := func(requestFn func(r *http.Request), ginFn func(router *gin.Engine), expect string) func(t *testing.T) { + return func(t *testing.T) { + r := httptest.NewRequest("GET", "/ping", nil) + r.RemoteAddr = "1.2.3.4:5678" + + if requestFn != nil { + requestFn(r) + } + + sr := tracetest.NewSpanRecorder() + provider := sdktrace.NewTracerProvider() + provider.RegisterSpanProcessor(sr) + + doneCh := make(chan struct{}) + router := gin.New() + + if ginFn != nil { + ginFn(router) + } + + router.Use(Middleware("foobar", WithTracerProvider(provider))) + router.GET("/ping", func(c *gin.Context) { + close(doneCh) + }) + + w := httptest.NewRecorder() + router.ServeHTTP(w, r) + response := w.Result() //nolint:bodyclose // False positive for httptest.ResponseRecorder: https://github.com/timakin/bodyclose/issues/59. + assert.Equal(t, http.StatusOK, response.StatusCode) + + select { + case <-doneCh: + // nop + case <-time.After(5 * time.Second): + t.Fatal("did not receive signal in 5s") + } + + res := sr.Ended() + require.Len(t, res, 1) + + got := make(map[attribute.Key]attribute.Value, len(res[0].Attributes())) + for _, a := range res[0].Attributes() { + got[a.Key] = a.Value + } + + require.NotEmpty(t, got["http.client_ip"]) + assert.Equal(t, expect, got["http.client_ip"].AsString()) + } + } + + t.Run("no header", testFn(nil, nil, "1.2.3.4")) + + t.Run("header is not trusted", testFn( + func(r *http.Request) { + r.Header.Set("X-Forwarded-For", "9.8.7.6") + }, + func(router *gin.Engine) { + router.SetTrustedProxies(nil) + }, + "1.2.3.4", + )) + + t.Run("client IP in X-Forwarded-For header", testFn( + func(r *http.Request) { + r.Header.Set("X-Forwarded-For", "9.8.7.6") + }, + func(router *gin.Engine) { + router.SetTrustedProxies([]string{"0.0.0.0/0"}) + }, + "9.8.7.6", + )) + + t.Run("client IP in X-Custom-IP", testFn( + func(r *http.Request) { + r.Header.Set("X-Forwarded-For", "2.3.2.3") // not used + r.Header.Set("X-Custom-IP", "9.8.7.6") + }, + func(router *gin.Engine) { + router.RemoteIPHeaders = []string{"X-Custom-IP", "X-Forwarded-For"} + router.SetTrustedProxies([]string{"0.0.0.0/0"}) + }, + "9.8.7.6", + )) } diff --git a/instrumentation/github.com/gin-gonic/gin/otelgin/go.mod b/instrumentation/github.com/gin-gonic/gin/otelgin/go.mod index a614073ab0c..f085aa11874 100644 --- a/instrumentation/github.com/gin-gonic/gin/otelgin/go.mod +++ b/instrumentation/github.com/gin-gonic/gin/otelgin/go.mod @@ -9,6 +9,7 @@ require ( github.com/stretchr/testify v1.9.0 go.opentelemetry.io/contrib/propagators/b3 v1.29.0 go.opentelemetry.io/otel v1.29.0 + go.opentelemetry.io/otel/sdk v1.29.0 go.opentelemetry.io/otel/trace v1.29.0 ) @@ -26,6 +27,7 @@ require ( github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.22.0 // indirect github.com/goccy/go-json v0.10.3 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.2.8 // indirect github.com/leodido/go-urn v1.4.0 // indirect diff --git a/instrumentation/github.com/gin-gonic/gin/otelgin/go.sum b/instrumentation/github.com/gin-gonic/gin/otelgin/go.sum index 72833e2ad6f..4edeada175e 100644 --- a/instrumentation/github.com/gin-gonic/gin/otelgin/go.sum +++ b/instrumentation/github.com/gin-gonic/gin/otelgin/go.sum @@ -34,6 +34,8 @@ github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PU github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= @@ -71,6 +73,8 @@ go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= +go.opentelemetry.io/otel/sdk v1.29.0 h1:vkqKjk7gwhS8VaWb0POZKmIEDimRCMsopNYnriHyryo= +go.opentelemetry.io/otel/sdk v1.29.0/go.mod h1:pM8Dx5WKnvxLCb+8lG1PRNIDxu9g9b9g59Qr7hfAAok= go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= golang.org/x/arch v0.10.0 h1:S3huipmSclq3PJMNe76NGwkBR504WFkQ5dhzWzP8ZW8= From 66ab05758ad771a715c429aff9fffa7472ff6c6d Mon Sep 17 00:00:00 2001 From: ItalyPaleAle <43508+ItalyPaleAle@users.noreply.github.com> Date: Mon, 9 Sep 2024 06:57:53 -0700 Subject: [PATCH 02/11] Revert other changes to tests --- .../gin-gonic/gin/otelgin/gintrace_test.go | 26 +++++-------------- 1 file changed, 6 insertions(+), 20 deletions(-) diff --git a/instrumentation/github.com/gin-gonic/gin/otelgin/gintrace_test.go b/instrumentation/github.com/gin-gonic/gin/otelgin/gintrace_test.go index 3c15a781ec2..e0f32bf09f3 100644 --- a/instrumentation/github.com/gin-gonic/gin/otelgin/gintrace_test.go +++ b/instrumentation/github.com/gin-gonic/gin/otelgin/gintrace_test.go @@ -66,20 +66,13 @@ func TestPropagationWithGlobalPropagators(t *testing.T) { router := gin.New() router.Use(Middleware("foobar", WithTracerProvider(provider))) - resCh := make(chan trace.Span, 1) router.GET("/user/:id", func(c *gin.Context) { - resCh <- trace.SpanFromContext(c.Request.Context()) + span := trace.SpanFromContext(c.Request.Context()) + assert.Equal(t, sc.TraceID(), span.SpanContext().TraceID()) + assert.Equal(t, sc.SpanID(), span.SpanContext().SpanID()) }) router.ServeHTTP(w, r) - - select { - case span := <-resCh: - assert.Equal(t, sc.TraceID(), span.SpanContext().TraceID()) - assert.Equal(t, sc.SpanID(), span.SpanContext().SpanID()) - case <-time.After(5 * time.Second): - t.Fatal("did not receive signal in 5s") - } } func TestPropagationWithCustomPropagators(t *testing.T) { @@ -100,20 +93,13 @@ func TestPropagationWithCustomPropagators(t *testing.T) { router := gin.New() router.Use(Middleware("foobar", WithTracerProvider(provider), WithPropagators(b3))) - resCh := make(chan trace.Span, 1) router.GET("/user/:id", func(c *gin.Context) { - resCh <- trace.SpanFromContext(c.Request.Context()) + span := trace.SpanFromContext(c.Request.Context()) + assert.Equal(t, sc.TraceID(), span.SpanContext().TraceID()) + assert.Equal(t, sc.SpanID(), span.SpanContext().SpanID()) }) router.ServeHTTP(w, r) - - select { - case span := <-resCh: - assert.Equal(t, sc.TraceID(), span.SpanContext().TraceID()) - assert.Equal(t, sc.SpanID(), span.SpanContext().SpanID()) - case <-time.After(5 * time.Second): - t.Fatal("did not receive signal in 5s") - } } func TestClientIP(t *testing.T) { From e978ded58dee10edf642a53fdbc5856b52a904a1 Mon Sep 17 00:00:00 2001 From: ItalyPaleAle <43508+ItalyPaleAle@users.noreply.github.com> Date: Mon, 9 Sep 2024 06:59:01 -0700 Subject: [PATCH 03/11] Lint --- .../github.com/gin-gonic/gin/otelgin/gintrace_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/instrumentation/github.com/gin-gonic/gin/otelgin/gintrace_test.go b/instrumentation/github.com/gin-gonic/gin/otelgin/gintrace_test.go index e0f32bf09f3..c1f4e91a765 100644 --- a/instrumentation/github.com/gin-gonic/gin/otelgin/gintrace_test.go +++ b/instrumentation/github.com/gin-gonic/gin/otelgin/gintrace_test.go @@ -160,6 +160,7 @@ func TestClientIP(t *testing.T) { r.Header.Set("X-Forwarded-For", "9.8.7.6") }, func(router *gin.Engine) { + //nolint:errcheck router.SetTrustedProxies(nil) }, "1.2.3.4", @@ -170,6 +171,7 @@ func TestClientIP(t *testing.T) { r.Header.Set("X-Forwarded-For", "9.8.7.6") }, func(router *gin.Engine) { + //nolint:errcheck router.SetTrustedProxies([]string{"0.0.0.0/0"}) }, "9.8.7.6", @@ -182,6 +184,7 @@ func TestClientIP(t *testing.T) { }, func(router *gin.Engine) { router.RemoteIPHeaders = []string{"X-Custom-IP", "X-Forwarded-For"} + //nolint:errcheck router.SetTrustedProxies([]string{"0.0.0.0/0"}) }, "9.8.7.6", From 69fc65d7374d7531c941f52d72a7188ffe9f049e Mon Sep 17 00:00:00 2001 From: ItalyPaleAle <43508+ItalyPaleAle@users.noreply.github.com> Date: Tue, 10 Sep 2024 07:41:31 -0700 Subject: [PATCH 04/11] Refactored to update semconvutil --- .../internal/semconvutil/httpconv.go | 23 +++- .../internal/semconvutil/httpconv_test.go | 117 ++++++++++++------ .../go-restful/otelrestful/restful.go | 2 +- .../gin-gonic/gin/otelgin/gintrace.go | 19 +-- .../otelgin/internal/semconvutil/httpconv.go | 23 +++- .../internal/semconvutil/httpconv_test.go | 117 ++++++++++++------ .../otelmux/internal/semconvutil/httpconv.go | 23 +++- .../internal/semconvutil/httpconv_test.go | 117 ++++++++++++------ .../github.com/gorilla/mux/otelmux/mux.go | 2 +- .../github.com/labstack/echo/otelecho/echo.go | 2 +- .../otelecho/internal/semconvutil/httpconv.go | 23 +++- .../internal/semconvutil/httpconv_test.go | 117 ++++++++++++------ .../internal/semconvutil/httpconv.go | 23 +++- .../internal/semconvutil/httpconv_test.go | 117 ++++++++++++------ .../macaron.v1/otelmacaron/macaron.go | 2 +- .../http/httptrace/otelhttptrace/httptrace.go | 5 +- .../httptrace/otelhttptrace/httptrace_test.go | 1 + .../internal/semconvutil/httpconv.go | 23 +++- .../internal/semconvutil/httpconv_test.go | 117 ++++++++++++------ .../http/otelhttp/internal/semconv/v1.20.0.go | 2 +- .../otelhttp/internal/semconvutil/httpconv.go | 23 +++- .../internal/semconvutil/httpconv_test.go | 117 ++++++++++++------ internal/shared/semconvutil/httpconv.go.tmpl | 23 +++- .../shared/semconvutil/httpconv_test.go.tmpl | 117 ++++++++++++------ 24 files changed, 807 insertions(+), 348 deletions(-) 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 9f2833b190a..2569f0e0c16 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 @@ -54,6 +54,11 @@ func HTTPClientStatus(code int) (codes.Code, string) { return hc.ClientStatus(code) } +type HTTPServerRequestOptions struct { + // If set, this is used as value for the "http.client_ip" attribute. + HTTPClientIP string +} + // HTTPServerRequest returns trace attributes for an HTTP request received by a // server. // @@ -75,8 +80,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) []attribute.KeyValue { - return hc.ServerRequest(server, req) +func HTTPServerRequest(server string, req *http.Request, opts HTTPServerRequestOptions) []attribute.KeyValue { + return hc.ServerRequest(server, req, opts) } // HTTPServerRequestMetrics returns metric attributes for an HTTP request received by a @@ -305,7 +310,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) []attribute.KeyValue { +func (c *httpConv) ServerRequest(server string, req *http.Request, opts HTTPServerRequestOptions) []attribute.KeyValue { /* The following semantic conventions are returned if present: http.method string http.scheme string @@ -358,7 +363,17 @@ func (c *httpConv) ServerRequest(server string, req *http.Request) []attribute.K n++ } - clientIP := serverClientIP(req.Header.Get("X-Forwarded-For")) + // For client IP, use, in order: + // 1. The value passed in the options + // 2. The value in the X-Forwarded-For header + // 3. The peer address + clientIP := opts.HTTPClientIP + if clientIP == "" { + clientIP = serverClientIP(req.Header.Get("X-Forwarded-For")) + if clientIP == "" { + clientIP = peer + } + } if clientIP != "" { n++ } 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 868b09da277..ec538cbe026 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 @@ -12,6 +12,7 @@ import ( "net/url" "strconv" "testing" + "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -165,47 +166,83 @@ func TestHTTPClientRequestRequired(t *testing.T) { } func TestHTTPServerRequest(t *testing.T) { - got := make(chan *http.Request, 1) - handler := func(w http.ResponseWriter, r *http.Request) { - got <- r - w.WriteHeader(http.StatusOK) - } + testFn := func(requestModifierFn func(r *http.Request), opts HTTPServerRequestOptions, expectClientIP string) func(t *testing.T) { + return func(t *testing.T) { + reqCh := make(chan *http.Request, 1) + handler := func(w http.ResponseWriter, r *http.Request) { + r.RemoteAddr = "1.2.3.4:5678" + reqCh <- r + w.WriteHeader(http.StatusOK) + } - srv := httptest.NewServer(http.HandlerFunc(handler)) - defer srv.Close() + srv := httptest.NewServer(http.HandlerFunc(handler)) + defer srv.Close() - srvURL, err := url.Parse(srv.URL) - require.NoError(t, err) - srvPort, err := strconv.ParseInt(srvURL.Port(), 10, 32) - require.NoError(t, err) + srvURL, err := url.Parse(srv.URL) + require.NoError(t, err) + srvPort, err := strconv.ParseInt(srvURL.Port(), 10, 32) + require.NoError(t, err) - resp, err := srv.Client().Get(srv.URL) - require.NoError(t, err) - require.NoError(t, resp.Body.Close()) + req, err := http.NewRequest(http.MethodGet, srv.URL, nil) + require.NoError(t, err) - req := <-got - peer, peerPort := splitHostPort(req.RemoteAddr) + if requestModifierFn != nil { + requestModifierFn(req) + } - const user = "alice" - req.SetBasicAuth(user, "pswrd") + resp, err := srv.Client().Do(req) + require.NoError(t, err) + require.NoError(t, resp.Body.Close()) - const clientIP = "127.0.0.5" - req.Header.Add("X-Forwarded-For", clientIP) + var got *http.Request + select { + case got = <-reqCh: + // All good + case <-time.After(5 * time.Second): + t.Fatal("Did not receive a signal in 5s") + } - assert.ElementsMatch(t, - []attribute.KeyValue{ - attribute.String("http.method", "GET"), - attribute.String("http.scheme", "http"), - attribute.String("net.host.name", srvURL.Hostname()), - attribute.Int("net.host.port", int(srvPort)), - attribute.String("net.sock.peer.addr", peer), - attribute.Int("net.sock.peer.port", peerPort), - attribute.String("user_agent.original", "Go-http-client/1.1"), - attribute.String("http.client_ip", clientIP), - attribute.String("net.protocol.version", "1.1"), - attribute.String("http.target", "/"), + peer, peerPort := splitHostPort(got.RemoteAddr) + + const user = "alice" + got.SetBasicAuth(user, "pswrd") + + assert.ElementsMatch(t, + []attribute.KeyValue{ + attribute.String("http.method", "GET"), + attribute.String("http.scheme", "http"), + attribute.String("net.host.name", srvURL.Hostname()), + attribute.Int("net.host.port", int(srvPort)), + attribute.String("net.sock.peer.addr", peer), + attribute.Int("net.sock.peer.port", peerPort), + attribute.String("user_agent.original", "Go-http-client/1.1"), + attribute.String("http.client_ip", expectClientIP), + attribute.String("net.protocol.version", "1.1"), + attribute.String("http.target", "/"), + }, + HTTPServerRequest("", got, opts)) + } + } + + t.Run("client IP from network", testFn(nil, HTTPServerRequestOptions{}, "1.2.3.4")) + + t.Run("client IP from X-Forwarded-For header", testFn( + func(r *http.Request) { + r.Header.Add("X-Forwarded-For", "5.6.7.8") }, - HTTPServerRequest("", req)) + HTTPServerRequestOptions{}, + "5.6.7.8", + )) + + t.Run("set client IP in options", testFn( + func(r *http.Request) { + r.Header.Add("X-Forwarded-For", "5.6.7.8") + }, + HTTPServerRequestOptions{ + HTTPClientIP: "9.8.7.6", + }, + "9.8.7.6", + )) } func TestHTTPServerRequestMetrics(t *testing.T) { @@ -227,7 +264,13 @@ func TestHTTPServerRequestMetrics(t *testing.T) { require.NoError(t, err) require.NoError(t, resp.Body.Close()) - req := <-got + var req *http.Request + select { + case req = <-got: + // All good + case <-time.After(5 * time.Second): + t.Fatal("did not receive a signal in 5s") + } assert.ElementsMatch(t, []attribute.KeyValue{ @@ -250,14 +293,14 @@ func TestHTTPServerName(t *testing.T) { ) portStr := strconv.Itoa(port) server := host + ":" + portStr - assert.NotPanics(t, func() { got = HTTPServerRequest(server, req) }) + assert.NotPanics(t, func() { got = HTTPServerRequest(server, req, HTTPServerRequestOptions{}) }) 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) }) + assert.NotPanics(t, func() { got = HTTPServerRequest(host, req, HTTPServerRequestOptions{}) }) assert.Contains(t, got, attribute.String("net.host.name", host)) assert.Contains(t, got, attribute.Int("net.host.port", port)) } @@ -265,7 +308,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) }) + assert.NotPanics(t, func() { got = HTTPServerRequest("", req, HTTPServerRequestOptions{}) }) 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 42c7d913ad6..70cab16b144 100644 --- a/instrumentation/github.com/emicklei/go-restful/otelrestful/restful.go +++ b/instrumentation/github.com/emicklei/go-restful/otelrestful/restful.go @@ -43,7 +43,7 @@ func OTelFilter(service string, opts ...Option) restful.FilterFunction { spanName := route opts := []oteltrace.SpanStartOption{ - oteltrace.WithAttributes(semconvutil.HTTPServerRequest(service, r)...), + oteltrace.WithAttributes(semconvutil.HTTPServerRequest(service, r, semconvutil.HTTPServerRequestOptions{})...), oteltrace.WithSpanKind(oteltrace.SpanKindServer), } if route != "" { diff --git a/instrumentation/github.com/gin-gonic/gin/otelgin/gintrace.go b/instrumentation/github.com/gin-gonic/gin/otelgin/gintrace.go index 6702632188b..ed4cf29e0ff 100644 --- a/instrumentation/github.com/gin-gonic/gin/otelgin/gintrace.go +++ b/instrumentation/github.com/gin-gonic/gin/otelgin/gintrace.go @@ -67,25 +67,16 @@ func Middleware(service string, opts ...Option) gin.HandlerFunc { }() ctx := cfg.Propagators.Extract(savedCtx, propagation.HeaderCarrier(c.Request.Header)) - // Gin's ClientIP method can detect the client's IP from various headers set by proxies, and it's configurable - // However, semconvutil supports the X-Forwarded-For header only - // So we need to temporarily set the client's IP in the X-Forwarded-For header, before restoring it after the call - originalHeader := c.Request.Header.Get("X-Forwarded-For") - c.Request.Header.Set("X-Forwarded-For", c.ClientIP()) - + serverRequestOpts := semconvutil.HTTPServerRequestOptions{ + // Gin's ClientIP method can detect the client's IP from various headers set by proxies, and it's configurable + HTTPClientIP: c.ClientIP(), + } opts := []oteltrace.SpanStartOption{ - oteltrace.WithAttributes(semconvutil.HTTPServerRequest(service, c.Request)...), + oteltrace.WithAttributes(semconvutil.HTTPServerRequest(service, c.Request, serverRequestOpts)...), oteltrace.WithAttributes(semconv.HTTPRoute(c.FullPath())), oteltrace.WithSpanKind(oteltrace.SpanKindServer), } - // Restore the previous value for X-Forwarded-For - if originalHeader == "" { - c.Request.Header.Del("X-Forwarded-For") - } else { - c.Request.Header.Set("X-Forwarded-For", originalHeader) - } - var spanName string if cfg.SpanNameFormatter == nil { spanName = c.FullPath() 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 8cfe7e3dc0b..73c75016a6c 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 @@ -54,6 +54,11 @@ func HTTPClientStatus(code int) (codes.Code, string) { return hc.ClientStatus(code) } +type HTTPServerRequestOptions struct { + // If set, this is used as value for the "http.client_ip" attribute. + HTTPClientIP string +} + // HTTPServerRequest returns trace attributes for an HTTP request received by a // server. // @@ -75,8 +80,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) []attribute.KeyValue { - return hc.ServerRequest(server, req) +func HTTPServerRequest(server string, req *http.Request, opts HTTPServerRequestOptions) []attribute.KeyValue { + return hc.ServerRequest(server, req, opts) } // HTTPServerRequestMetrics returns metric attributes for an HTTP request received by a @@ -305,7 +310,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) []attribute.KeyValue { +func (c *httpConv) ServerRequest(server string, req *http.Request, opts HTTPServerRequestOptions) []attribute.KeyValue { /* The following semantic conventions are returned if present: http.method string http.scheme string @@ -358,7 +363,17 @@ func (c *httpConv) ServerRequest(server string, req *http.Request) []attribute.K n++ } - clientIP := serverClientIP(req.Header.Get("X-Forwarded-For")) + // For client IP, use, in order: + // 1. The value passed in the options + // 2. The value in the X-Forwarded-For header + // 3. The peer address + clientIP := opts.HTTPClientIP + if clientIP == "" { + clientIP = serverClientIP(req.Header.Get("X-Forwarded-For")) + if clientIP == "" { + clientIP = peer + } + } if clientIP != "" { n++ } 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 868b09da277..ec538cbe026 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 @@ -12,6 +12,7 @@ import ( "net/url" "strconv" "testing" + "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -165,47 +166,83 @@ func TestHTTPClientRequestRequired(t *testing.T) { } func TestHTTPServerRequest(t *testing.T) { - got := make(chan *http.Request, 1) - handler := func(w http.ResponseWriter, r *http.Request) { - got <- r - w.WriteHeader(http.StatusOK) - } + testFn := func(requestModifierFn func(r *http.Request), opts HTTPServerRequestOptions, expectClientIP string) func(t *testing.T) { + return func(t *testing.T) { + reqCh := make(chan *http.Request, 1) + handler := func(w http.ResponseWriter, r *http.Request) { + r.RemoteAddr = "1.2.3.4:5678" + reqCh <- r + w.WriteHeader(http.StatusOK) + } - srv := httptest.NewServer(http.HandlerFunc(handler)) - defer srv.Close() + srv := httptest.NewServer(http.HandlerFunc(handler)) + defer srv.Close() - srvURL, err := url.Parse(srv.URL) - require.NoError(t, err) - srvPort, err := strconv.ParseInt(srvURL.Port(), 10, 32) - require.NoError(t, err) + srvURL, err := url.Parse(srv.URL) + require.NoError(t, err) + srvPort, err := strconv.ParseInt(srvURL.Port(), 10, 32) + require.NoError(t, err) - resp, err := srv.Client().Get(srv.URL) - require.NoError(t, err) - require.NoError(t, resp.Body.Close()) + req, err := http.NewRequest(http.MethodGet, srv.URL, nil) + require.NoError(t, err) - req := <-got - peer, peerPort := splitHostPort(req.RemoteAddr) + if requestModifierFn != nil { + requestModifierFn(req) + } - const user = "alice" - req.SetBasicAuth(user, "pswrd") + resp, err := srv.Client().Do(req) + require.NoError(t, err) + require.NoError(t, resp.Body.Close()) - const clientIP = "127.0.0.5" - req.Header.Add("X-Forwarded-For", clientIP) + var got *http.Request + select { + case got = <-reqCh: + // All good + case <-time.After(5 * time.Second): + t.Fatal("Did not receive a signal in 5s") + } - assert.ElementsMatch(t, - []attribute.KeyValue{ - attribute.String("http.method", "GET"), - attribute.String("http.scheme", "http"), - attribute.String("net.host.name", srvURL.Hostname()), - attribute.Int("net.host.port", int(srvPort)), - attribute.String("net.sock.peer.addr", peer), - attribute.Int("net.sock.peer.port", peerPort), - attribute.String("user_agent.original", "Go-http-client/1.1"), - attribute.String("http.client_ip", clientIP), - attribute.String("net.protocol.version", "1.1"), - attribute.String("http.target", "/"), + peer, peerPort := splitHostPort(got.RemoteAddr) + + const user = "alice" + got.SetBasicAuth(user, "pswrd") + + assert.ElementsMatch(t, + []attribute.KeyValue{ + attribute.String("http.method", "GET"), + attribute.String("http.scheme", "http"), + attribute.String("net.host.name", srvURL.Hostname()), + attribute.Int("net.host.port", int(srvPort)), + attribute.String("net.sock.peer.addr", peer), + attribute.Int("net.sock.peer.port", peerPort), + attribute.String("user_agent.original", "Go-http-client/1.1"), + attribute.String("http.client_ip", expectClientIP), + attribute.String("net.protocol.version", "1.1"), + attribute.String("http.target", "/"), + }, + HTTPServerRequest("", got, opts)) + } + } + + t.Run("client IP from network", testFn(nil, HTTPServerRequestOptions{}, "1.2.3.4")) + + t.Run("client IP from X-Forwarded-For header", testFn( + func(r *http.Request) { + r.Header.Add("X-Forwarded-For", "5.6.7.8") }, - HTTPServerRequest("", req)) + HTTPServerRequestOptions{}, + "5.6.7.8", + )) + + t.Run("set client IP in options", testFn( + func(r *http.Request) { + r.Header.Add("X-Forwarded-For", "5.6.7.8") + }, + HTTPServerRequestOptions{ + HTTPClientIP: "9.8.7.6", + }, + "9.8.7.6", + )) } func TestHTTPServerRequestMetrics(t *testing.T) { @@ -227,7 +264,13 @@ func TestHTTPServerRequestMetrics(t *testing.T) { require.NoError(t, err) require.NoError(t, resp.Body.Close()) - req := <-got + var req *http.Request + select { + case req = <-got: + // All good + case <-time.After(5 * time.Second): + t.Fatal("did not receive a signal in 5s") + } assert.ElementsMatch(t, []attribute.KeyValue{ @@ -250,14 +293,14 @@ func TestHTTPServerName(t *testing.T) { ) portStr := strconv.Itoa(port) server := host + ":" + portStr - assert.NotPanics(t, func() { got = HTTPServerRequest(server, req) }) + assert.NotPanics(t, func() { got = HTTPServerRequest(server, req, HTTPServerRequestOptions{}) }) 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) }) + assert.NotPanics(t, func() { got = HTTPServerRequest(host, req, HTTPServerRequestOptions{}) }) assert.Contains(t, got, attribute.String("net.host.name", host)) assert.Contains(t, got, attribute.Int("net.host.port", port)) } @@ -265,7 +308,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) }) + assert.NotPanics(t, func() { got = HTTPServerRequest("", req, HTTPServerRequestOptions{}) }) want := []attribute.KeyValue{ attribute.String("http.method", "GET"), attribute.String("http.scheme", "http"), 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 525ee31ffe8..d190e88da41 100644 --- a/instrumentation/github.com/gorilla/mux/otelmux/internal/semconvutil/httpconv.go +++ b/instrumentation/github.com/gorilla/mux/otelmux/internal/semconvutil/httpconv.go @@ -54,6 +54,11 @@ func HTTPClientStatus(code int) (codes.Code, string) { return hc.ClientStatus(code) } +type HTTPServerRequestOptions struct { + // If set, this is used as value for the "http.client_ip" attribute. + HTTPClientIP string +} + // HTTPServerRequest returns trace attributes for an HTTP request received by a // server. // @@ -75,8 +80,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) []attribute.KeyValue { - return hc.ServerRequest(server, req) +func HTTPServerRequest(server string, req *http.Request, opts HTTPServerRequestOptions) []attribute.KeyValue { + return hc.ServerRequest(server, req, opts) } // HTTPServerRequestMetrics returns metric attributes for an HTTP request received by a @@ -305,7 +310,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) []attribute.KeyValue { +func (c *httpConv) ServerRequest(server string, req *http.Request, opts HTTPServerRequestOptions) []attribute.KeyValue { /* The following semantic conventions are returned if present: http.method string http.scheme string @@ -358,7 +363,17 @@ func (c *httpConv) ServerRequest(server string, req *http.Request) []attribute.K n++ } - clientIP := serverClientIP(req.Header.Get("X-Forwarded-For")) + // For client IP, use, in order: + // 1. The value passed in the options + // 2. The value in the X-Forwarded-For header + // 3. The peer address + clientIP := opts.HTTPClientIP + if clientIP == "" { + clientIP = serverClientIP(req.Header.Get("X-Forwarded-For")) + if clientIP == "" { + clientIP = peer + } + } if clientIP != "" { n++ } 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 868b09da277..ec538cbe026 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 @@ -12,6 +12,7 @@ import ( "net/url" "strconv" "testing" + "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -165,47 +166,83 @@ func TestHTTPClientRequestRequired(t *testing.T) { } func TestHTTPServerRequest(t *testing.T) { - got := make(chan *http.Request, 1) - handler := func(w http.ResponseWriter, r *http.Request) { - got <- r - w.WriteHeader(http.StatusOK) - } + testFn := func(requestModifierFn func(r *http.Request), opts HTTPServerRequestOptions, expectClientIP string) func(t *testing.T) { + return func(t *testing.T) { + reqCh := make(chan *http.Request, 1) + handler := func(w http.ResponseWriter, r *http.Request) { + r.RemoteAddr = "1.2.3.4:5678" + reqCh <- r + w.WriteHeader(http.StatusOK) + } - srv := httptest.NewServer(http.HandlerFunc(handler)) - defer srv.Close() + srv := httptest.NewServer(http.HandlerFunc(handler)) + defer srv.Close() - srvURL, err := url.Parse(srv.URL) - require.NoError(t, err) - srvPort, err := strconv.ParseInt(srvURL.Port(), 10, 32) - require.NoError(t, err) + srvURL, err := url.Parse(srv.URL) + require.NoError(t, err) + srvPort, err := strconv.ParseInt(srvURL.Port(), 10, 32) + require.NoError(t, err) - resp, err := srv.Client().Get(srv.URL) - require.NoError(t, err) - require.NoError(t, resp.Body.Close()) + req, err := http.NewRequest(http.MethodGet, srv.URL, nil) + require.NoError(t, err) - req := <-got - peer, peerPort := splitHostPort(req.RemoteAddr) + if requestModifierFn != nil { + requestModifierFn(req) + } - const user = "alice" - req.SetBasicAuth(user, "pswrd") + resp, err := srv.Client().Do(req) + require.NoError(t, err) + require.NoError(t, resp.Body.Close()) - const clientIP = "127.0.0.5" - req.Header.Add("X-Forwarded-For", clientIP) + var got *http.Request + select { + case got = <-reqCh: + // All good + case <-time.After(5 * time.Second): + t.Fatal("Did not receive a signal in 5s") + } - assert.ElementsMatch(t, - []attribute.KeyValue{ - attribute.String("http.method", "GET"), - attribute.String("http.scheme", "http"), - attribute.String("net.host.name", srvURL.Hostname()), - attribute.Int("net.host.port", int(srvPort)), - attribute.String("net.sock.peer.addr", peer), - attribute.Int("net.sock.peer.port", peerPort), - attribute.String("user_agent.original", "Go-http-client/1.1"), - attribute.String("http.client_ip", clientIP), - attribute.String("net.protocol.version", "1.1"), - attribute.String("http.target", "/"), + peer, peerPort := splitHostPort(got.RemoteAddr) + + const user = "alice" + got.SetBasicAuth(user, "pswrd") + + assert.ElementsMatch(t, + []attribute.KeyValue{ + attribute.String("http.method", "GET"), + attribute.String("http.scheme", "http"), + attribute.String("net.host.name", srvURL.Hostname()), + attribute.Int("net.host.port", int(srvPort)), + attribute.String("net.sock.peer.addr", peer), + attribute.Int("net.sock.peer.port", peerPort), + attribute.String("user_agent.original", "Go-http-client/1.1"), + attribute.String("http.client_ip", expectClientIP), + attribute.String("net.protocol.version", "1.1"), + attribute.String("http.target", "/"), + }, + HTTPServerRequest("", got, opts)) + } + } + + t.Run("client IP from network", testFn(nil, HTTPServerRequestOptions{}, "1.2.3.4")) + + t.Run("client IP from X-Forwarded-For header", testFn( + func(r *http.Request) { + r.Header.Add("X-Forwarded-For", "5.6.7.8") }, - HTTPServerRequest("", req)) + HTTPServerRequestOptions{}, + "5.6.7.8", + )) + + t.Run("set client IP in options", testFn( + func(r *http.Request) { + r.Header.Add("X-Forwarded-For", "5.6.7.8") + }, + HTTPServerRequestOptions{ + HTTPClientIP: "9.8.7.6", + }, + "9.8.7.6", + )) } func TestHTTPServerRequestMetrics(t *testing.T) { @@ -227,7 +264,13 @@ func TestHTTPServerRequestMetrics(t *testing.T) { require.NoError(t, err) require.NoError(t, resp.Body.Close()) - req := <-got + var req *http.Request + select { + case req = <-got: + // All good + case <-time.After(5 * time.Second): + t.Fatal("did not receive a signal in 5s") + } assert.ElementsMatch(t, []attribute.KeyValue{ @@ -250,14 +293,14 @@ func TestHTTPServerName(t *testing.T) { ) portStr := strconv.Itoa(port) server := host + ":" + portStr - assert.NotPanics(t, func() { got = HTTPServerRequest(server, req) }) + assert.NotPanics(t, func() { got = HTTPServerRequest(server, req, HTTPServerRequestOptions{}) }) 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) }) + assert.NotPanics(t, func() { got = HTTPServerRequest(host, req, HTTPServerRequestOptions{}) }) assert.Contains(t, got, attribute.String("net.host.name", host)) assert.Contains(t, got, attribute.Int("net.host.port", port)) } @@ -265,7 +308,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) }) + assert.NotPanics(t, func() { got = HTTPServerRequest("", req, HTTPServerRequestOptions{}) }) want := []attribute.KeyValue{ attribute.String("http.method", "GET"), attribute.String("http.scheme", "http"), diff --git a/instrumentation/github.com/gorilla/mux/otelmux/mux.go b/instrumentation/github.com/gorilla/mux/otelmux/mux.go index c7b2355eca8..1dd65c2446d 100644 --- a/instrumentation/github.com/gorilla/mux/otelmux/mux.go +++ b/instrumentation/github.com/gorilla/mux/otelmux/mux.go @@ -142,7 +142,7 @@ func (tw traceware) ServeHTTP(w http.ResponseWriter, r *http.Request) { } opts := []trace.SpanStartOption{ - trace.WithAttributes(semconvutil.HTTPServerRequest(tw.service, r)...), + trace.WithAttributes(semconvutil.HTTPServerRequest(tw.service, r, semconvutil.HTTPServerRequestOptions{})...), trace.WithSpanKind(trace.SpanKindServer), } diff --git a/instrumentation/github.com/labstack/echo/otelecho/echo.go b/instrumentation/github.com/labstack/echo/otelecho/echo.go index 2771ebc5d9e..0dd1091a602 100644 --- a/instrumentation/github.com/labstack/echo/otelecho/echo.go +++ b/instrumentation/github.com/labstack/echo/otelecho/echo.go @@ -60,7 +60,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)...), + oteltrace.WithAttributes(semconvutil.HTTPServerRequest(service, request, semconvutil.HTTPServerRequestOptions{})...), 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 d9384c3e3fb..1c43d26e77d 100644 --- a/instrumentation/github.com/labstack/echo/otelecho/internal/semconvutil/httpconv.go +++ b/instrumentation/github.com/labstack/echo/otelecho/internal/semconvutil/httpconv.go @@ -54,6 +54,11 @@ func HTTPClientStatus(code int) (codes.Code, string) { return hc.ClientStatus(code) } +type HTTPServerRequestOptions struct { + // If set, this is used as value for the "http.client_ip" attribute. + HTTPClientIP string +} + // HTTPServerRequest returns trace attributes for an HTTP request received by a // server. // @@ -75,8 +80,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) []attribute.KeyValue { - return hc.ServerRequest(server, req) +func HTTPServerRequest(server string, req *http.Request, opts HTTPServerRequestOptions) []attribute.KeyValue { + return hc.ServerRequest(server, req, opts) } // HTTPServerRequestMetrics returns metric attributes for an HTTP request received by a @@ -305,7 +310,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) []attribute.KeyValue { +func (c *httpConv) ServerRequest(server string, req *http.Request, opts HTTPServerRequestOptions) []attribute.KeyValue { /* The following semantic conventions are returned if present: http.method string http.scheme string @@ -358,7 +363,17 @@ func (c *httpConv) ServerRequest(server string, req *http.Request) []attribute.K n++ } - clientIP := serverClientIP(req.Header.Get("X-Forwarded-For")) + // For client IP, use, in order: + // 1. The value passed in the options + // 2. The value in the X-Forwarded-For header + // 3. The peer address + clientIP := opts.HTTPClientIP + if clientIP == "" { + clientIP = serverClientIP(req.Header.Get("X-Forwarded-For")) + if clientIP == "" { + clientIP = peer + } + } if clientIP != "" { n++ } 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 868b09da277..ec538cbe026 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 @@ -12,6 +12,7 @@ import ( "net/url" "strconv" "testing" + "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -165,47 +166,83 @@ func TestHTTPClientRequestRequired(t *testing.T) { } func TestHTTPServerRequest(t *testing.T) { - got := make(chan *http.Request, 1) - handler := func(w http.ResponseWriter, r *http.Request) { - got <- r - w.WriteHeader(http.StatusOK) - } + testFn := func(requestModifierFn func(r *http.Request), opts HTTPServerRequestOptions, expectClientIP string) func(t *testing.T) { + return func(t *testing.T) { + reqCh := make(chan *http.Request, 1) + handler := func(w http.ResponseWriter, r *http.Request) { + r.RemoteAddr = "1.2.3.4:5678" + reqCh <- r + w.WriteHeader(http.StatusOK) + } - srv := httptest.NewServer(http.HandlerFunc(handler)) - defer srv.Close() + srv := httptest.NewServer(http.HandlerFunc(handler)) + defer srv.Close() - srvURL, err := url.Parse(srv.URL) - require.NoError(t, err) - srvPort, err := strconv.ParseInt(srvURL.Port(), 10, 32) - require.NoError(t, err) + srvURL, err := url.Parse(srv.URL) + require.NoError(t, err) + srvPort, err := strconv.ParseInt(srvURL.Port(), 10, 32) + require.NoError(t, err) - resp, err := srv.Client().Get(srv.URL) - require.NoError(t, err) - require.NoError(t, resp.Body.Close()) + req, err := http.NewRequest(http.MethodGet, srv.URL, nil) + require.NoError(t, err) - req := <-got - peer, peerPort := splitHostPort(req.RemoteAddr) + if requestModifierFn != nil { + requestModifierFn(req) + } - const user = "alice" - req.SetBasicAuth(user, "pswrd") + resp, err := srv.Client().Do(req) + require.NoError(t, err) + require.NoError(t, resp.Body.Close()) - const clientIP = "127.0.0.5" - req.Header.Add("X-Forwarded-For", clientIP) + var got *http.Request + select { + case got = <-reqCh: + // All good + case <-time.After(5 * time.Second): + t.Fatal("Did not receive a signal in 5s") + } - assert.ElementsMatch(t, - []attribute.KeyValue{ - attribute.String("http.method", "GET"), - attribute.String("http.scheme", "http"), - attribute.String("net.host.name", srvURL.Hostname()), - attribute.Int("net.host.port", int(srvPort)), - attribute.String("net.sock.peer.addr", peer), - attribute.Int("net.sock.peer.port", peerPort), - attribute.String("user_agent.original", "Go-http-client/1.1"), - attribute.String("http.client_ip", clientIP), - attribute.String("net.protocol.version", "1.1"), - attribute.String("http.target", "/"), + peer, peerPort := splitHostPort(got.RemoteAddr) + + const user = "alice" + got.SetBasicAuth(user, "pswrd") + + assert.ElementsMatch(t, + []attribute.KeyValue{ + attribute.String("http.method", "GET"), + attribute.String("http.scheme", "http"), + attribute.String("net.host.name", srvURL.Hostname()), + attribute.Int("net.host.port", int(srvPort)), + attribute.String("net.sock.peer.addr", peer), + attribute.Int("net.sock.peer.port", peerPort), + attribute.String("user_agent.original", "Go-http-client/1.1"), + attribute.String("http.client_ip", expectClientIP), + attribute.String("net.protocol.version", "1.1"), + attribute.String("http.target", "/"), + }, + HTTPServerRequest("", got, opts)) + } + } + + t.Run("client IP from network", testFn(nil, HTTPServerRequestOptions{}, "1.2.3.4")) + + t.Run("client IP from X-Forwarded-For header", testFn( + func(r *http.Request) { + r.Header.Add("X-Forwarded-For", "5.6.7.8") }, - HTTPServerRequest("", req)) + HTTPServerRequestOptions{}, + "5.6.7.8", + )) + + t.Run("set client IP in options", testFn( + func(r *http.Request) { + r.Header.Add("X-Forwarded-For", "5.6.7.8") + }, + HTTPServerRequestOptions{ + HTTPClientIP: "9.8.7.6", + }, + "9.8.7.6", + )) } func TestHTTPServerRequestMetrics(t *testing.T) { @@ -227,7 +264,13 @@ func TestHTTPServerRequestMetrics(t *testing.T) { require.NoError(t, err) require.NoError(t, resp.Body.Close()) - req := <-got + var req *http.Request + select { + case req = <-got: + // All good + case <-time.After(5 * time.Second): + t.Fatal("did not receive a signal in 5s") + } assert.ElementsMatch(t, []attribute.KeyValue{ @@ -250,14 +293,14 @@ func TestHTTPServerName(t *testing.T) { ) portStr := strconv.Itoa(port) server := host + ":" + portStr - assert.NotPanics(t, func() { got = HTTPServerRequest(server, req) }) + assert.NotPanics(t, func() { got = HTTPServerRequest(server, req, HTTPServerRequestOptions{}) }) 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) }) + assert.NotPanics(t, func() { got = HTTPServerRequest(host, req, HTTPServerRequestOptions{}) }) assert.Contains(t, got, attribute.String("net.host.name", host)) assert.Contains(t, got, attribute.Int("net.host.port", port)) } @@ -265,7 +308,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) }) + assert.NotPanics(t, func() { got = HTTPServerRequest("", req, HTTPServerRequestOptions{}) }) want := []attribute.KeyValue{ attribute.String("http.method", "GET"), attribute.String("http.scheme", "http"), diff --git a/instrumentation/gopkg.in/macaron.v1/otelmacaron/internal/semconvutil/httpconv.go b/instrumentation/gopkg.in/macaron.v1/otelmacaron/internal/semconvutil/httpconv.go index ae5563010b6..b882da5adc2 100644 --- a/instrumentation/gopkg.in/macaron.v1/otelmacaron/internal/semconvutil/httpconv.go +++ b/instrumentation/gopkg.in/macaron.v1/otelmacaron/internal/semconvutil/httpconv.go @@ -54,6 +54,11 @@ func HTTPClientStatus(code int) (codes.Code, string) { return hc.ClientStatus(code) } +type HTTPServerRequestOptions struct { + // If set, this is used as value for the "http.client_ip" attribute. + HTTPClientIP string +} + // HTTPServerRequest returns trace attributes for an HTTP request received by a // server. // @@ -75,8 +80,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) []attribute.KeyValue { - return hc.ServerRequest(server, req) +func HTTPServerRequest(server string, req *http.Request, opts HTTPServerRequestOptions) []attribute.KeyValue { + return hc.ServerRequest(server, req, opts) } // HTTPServerRequestMetrics returns metric attributes for an HTTP request received by a @@ -305,7 +310,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) []attribute.KeyValue { +func (c *httpConv) ServerRequest(server string, req *http.Request, opts HTTPServerRequestOptions) []attribute.KeyValue { /* The following semantic conventions are returned if present: http.method string http.scheme string @@ -358,7 +363,17 @@ func (c *httpConv) ServerRequest(server string, req *http.Request) []attribute.K n++ } - clientIP := serverClientIP(req.Header.Get("X-Forwarded-For")) + // For client IP, use, in order: + // 1. The value passed in the options + // 2. The value in the X-Forwarded-For header + // 3. The peer address + clientIP := opts.HTTPClientIP + if clientIP == "" { + clientIP = serverClientIP(req.Header.Get("X-Forwarded-For")) + if clientIP == "" { + clientIP = peer + } + } if clientIP != "" { n++ } diff --git a/instrumentation/gopkg.in/macaron.v1/otelmacaron/internal/semconvutil/httpconv_test.go b/instrumentation/gopkg.in/macaron.v1/otelmacaron/internal/semconvutil/httpconv_test.go index 868b09da277..ec538cbe026 100644 --- a/instrumentation/gopkg.in/macaron.v1/otelmacaron/internal/semconvutil/httpconv_test.go +++ b/instrumentation/gopkg.in/macaron.v1/otelmacaron/internal/semconvutil/httpconv_test.go @@ -12,6 +12,7 @@ import ( "net/url" "strconv" "testing" + "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -165,47 +166,83 @@ func TestHTTPClientRequestRequired(t *testing.T) { } func TestHTTPServerRequest(t *testing.T) { - got := make(chan *http.Request, 1) - handler := func(w http.ResponseWriter, r *http.Request) { - got <- r - w.WriteHeader(http.StatusOK) - } + testFn := func(requestModifierFn func(r *http.Request), opts HTTPServerRequestOptions, expectClientIP string) func(t *testing.T) { + return func(t *testing.T) { + reqCh := make(chan *http.Request, 1) + handler := func(w http.ResponseWriter, r *http.Request) { + r.RemoteAddr = "1.2.3.4:5678" + reqCh <- r + w.WriteHeader(http.StatusOK) + } - srv := httptest.NewServer(http.HandlerFunc(handler)) - defer srv.Close() + srv := httptest.NewServer(http.HandlerFunc(handler)) + defer srv.Close() - srvURL, err := url.Parse(srv.URL) - require.NoError(t, err) - srvPort, err := strconv.ParseInt(srvURL.Port(), 10, 32) - require.NoError(t, err) + srvURL, err := url.Parse(srv.URL) + require.NoError(t, err) + srvPort, err := strconv.ParseInt(srvURL.Port(), 10, 32) + require.NoError(t, err) - resp, err := srv.Client().Get(srv.URL) - require.NoError(t, err) - require.NoError(t, resp.Body.Close()) + req, err := http.NewRequest(http.MethodGet, srv.URL, nil) + require.NoError(t, err) - req := <-got - peer, peerPort := splitHostPort(req.RemoteAddr) + if requestModifierFn != nil { + requestModifierFn(req) + } - const user = "alice" - req.SetBasicAuth(user, "pswrd") + resp, err := srv.Client().Do(req) + require.NoError(t, err) + require.NoError(t, resp.Body.Close()) - const clientIP = "127.0.0.5" - req.Header.Add("X-Forwarded-For", clientIP) + var got *http.Request + select { + case got = <-reqCh: + // All good + case <-time.After(5 * time.Second): + t.Fatal("Did not receive a signal in 5s") + } - assert.ElementsMatch(t, - []attribute.KeyValue{ - attribute.String("http.method", "GET"), - attribute.String("http.scheme", "http"), - attribute.String("net.host.name", srvURL.Hostname()), - attribute.Int("net.host.port", int(srvPort)), - attribute.String("net.sock.peer.addr", peer), - attribute.Int("net.sock.peer.port", peerPort), - attribute.String("user_agent.original", "Go-http-client/1.1"), - attribute.String("http.client_ip", clientIP), - attribute.String("net.protocol.version", "1.1"), - attribute.String("http.target", "/"), + peer, peerPort := splitHostPort(got.RemoteAddr) + + const user = "alice" + got.SetBasicAuth(user, "pswrd") + + assert.ElementsMatch(t, + []attribute.KeyValue{ + attribute.String("http.method", "GET"), + attribute.String("http.scheme", "http"), + attribute.String("net.host.name", srvURL.Hostname()), + attribute.Int("net.host.port", int(srvPort)), + attribute.String("net.sock.peer.addr", peer), + attribute.Int("net.sock.peer.port", peerPort), + attribute.String("user_agent.original", "Go-http-client/1.1"), + attribute.String("http.client_ip", expectClientIP), + attribute.String("net.protocol.version", "1.1"), + attribute.String("http.target", "/"), + }, + HTTPServerRequest("", got, opts)) + } + } + + t.Run("client IP from network", testFn(nil, HTTPServerRequestOptions{}, "1.2.3.4")) + + t.Run("client IP from X-Forwarded-For header", testFn( + func(r *http.Request) { + r.Header.Add("X-Forwarded-For", "5.6.7.8") }, - HTTPServerRequest("", req)) + HTTPServerRequestOptions{}, + "5.6.7.8", + )) + + t.Run("set client IP in options", testFn( + func(r *http.Request) { + r.Header.Add("X-Forwarded-For", "5.6.7.8") + }, + HTTPServerRequestOptions{ + HTTPClientIP: "9.8.7.6", + }, + "9.8.7.6", + )) } func TestHTTPServerRequestMetrics(t *testing.T) { @@ -227,7 +264,13 @@ func TestHTTPServerRequestMetrics(t *testing.T) { require.NoError(t, err) require.NoError(t, resp.Body.Close()) - req := <-got + var req *http.Request + select { + case req = <-got: + // All good + case <-time.After(5 * time.Second): + t.Fatal("did not receive a signal in 5s") + } assert.ElementsMatch(t, []attribute.KeyValue{ @@ -250,14 +293,14 @@ func TestHTTPServerName(t *testing.T) { ) portStr := strconv.Itoa(port) server := host + ":" + portStr - assert.NotPanics(t, func() { got = HTTPServerRequest(server, req) }) + assert.NotPanics(t, func() { got = HTTPServerRequest(server, req, HTTPServerRequestOptions{}) }) 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) }) + assert.NotPanics(t, func() { got = HTTPServerRequest(host, req, HTTPServerRequestOptions{}) }) assert.Contains(t, got, attribute.String("net.host.name", host)) assert.Contains(t, got, attribute.Int("net.host.port", port)) } @@ -265,7 +308,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) }) + assert.NotPanics(t, func() { got = HTTPServerRequest("", req, HTTPServerRequestOptions{}) }) want := []attribute.KeyValue{ attribute.String("http.method", "GET"), attribute.String("http.scheme", "http"), diff --git a/instrumentation/gopkg.in/macaron.v1/otelmacaron/macaron.go b/instrumentation/gopkg.in/macaron.v1/otelmacaron/macaron.go index bda8143fe93..17e86da2757 100644 --- a/instrumentation/gopkg.in/macaron.v1/otelmacaron/macaron.go +++ b/instrumentation/gopkg.in/macaron.v1/otelmacaron/macaron.go @@ -33,7 +33,7 @@ func Middleware(service string, opts ...Option) macaron.Handler { ctx := cfg.Propagators.Extract(savedCtx, propagation.HeaderCarrier(c.Req.Header)) opts := []oteltrace.SpanStartOption{ - oteltrace.WithAttributes(semconvutil.HTTPServerRequest(service, c.Req.Request)...), + oteltrace.WithAttributes(semconvutil.HTTPServerRequest(service, c.Req.Request, semconvutil.HTTPServerRequestOptions{})...), oteltrace.WithSpanKind(oteltrace.SpanKindServer), } // TODO: span name should be router template not the actual request path, eg /user/:id vs /user/123 diff --git a/instrumentation/net/http/httptrace/otelhttptrace/httptrace.go b/instrumentation/net/http/httptrace/otelhttptrace/httptrace.go index a1230c36abc..b3150331000 100644 --- a/instrumentation/net/http/httptrace/otelhttptrace/httptrace.go +++ b/instrumentation/net/http/httptrace/otelhttptrace/httptrace.go @@ -54,7 +54,10 @@ func Extract(ctx context.Context, req *http.Request, opts ...Option) ([]attribut c := newConfig(opts) ctx = c.propagators.Extract(ctx, propagation.HeaderCarrier(req.Header)) - attrs := append(semconvutil.HTTPServerRequest("", req), semconvutil.NetTransport("tcp")) + attrs := append( + semconvutil.HTTPServerRequest("", req, semconvutil.HTTPServerRequestOptions{}), + semconvutil.NetTransport("tcp"), + ) if req.ContentLength > 0 { a := semconv.HTTPRequestContentLength(int(req.ContentLength)) attrs = append(attrs, a) diff --git a/instrumentation/net/http/httptrace/otelhttptrace/httptrace_test.go b/instrumentation/net/http/httptrace/otelhttptrace/httptrace_test.go index faf81b85640..e9a5a73e71f 100644 --- a/instrumentation/net/http/httptrace/otelhttptrace/httptrace_test.go +++ b/instrumentation/net/http/httptrace/otelhttptrace/httptrace_test.go @@ -78,6 +78,7 @@ func TestRoundtrip(t *testing.T) { semconv.HTTPSchemeKey: "http", semconv.HTTPTargetKey: "/", semconv.HTTPRequestContentLengthKey: "3", + semconv.HTTPClientIPKey: hp[0], semconv.NetSockPeerAddrKey: hp[0], semconv.NetTransportKey: "ip_tcp", semconv.UserAgentOriginalKey: "Go-http-client/1.1", diff --git a/instrumentation/net/http/httptrace/otelhttptrace/internal/semconvutil/httpconv.go b/instrumentation/net/http/httptrace/otelhttptrace/internal/semconvutil/httpconv.go index 811d4f3fe21..f19d5629012 100644 --- a/instrumentation/net/http/httptrace/otelhttptrace/internal/semconvutil/httpconv.go +++ b/instrumentation/net/http/httptrace/otelhttptrace/internal/semconvutil/httpconv.go @@ -54,6 +54,11 @@ func HTTPClientStatus(code int) (codes.Code, string) { return hc.ClientStatus(code) } +type HTTPServerRequestOptions struct { + // If set, this is used as value for the "http.client_ip" attribute. + HTTPClientIP string +} + // HTTPServerRequest returns trace attributes for an HTTP request received by a // server. // @@ -75,8 +80,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) []attribute.KeyValue { - return hc.ServerRequest(server, req) +func HTTPServerRequest(server string, req *http.Request, opts HTTPServerRequestOptions) []attribute.KeyValue { + return hc.ServerRequest(server, req, opts) } // HTTPServerRequestMetrics returns metric attributes for an HTTP request received by a @@ -305,7 +310,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) []attribute.KeyValue { +func (c *httpConv) ServerRequest(server string, req *http.Request, opts HTTPServerRequestOptions) []attribute.KeyValue { /* The following semantic conventions are returned if present: http.method string http.scheme string @@ -358,7 +363,17 @@ func (c *httpConv) ServerRequest(server string, req *http.Request) []attribute.K n++ } - clientIP := serverClientIP(req.Header.Get("X-Forwarded-For")) + // For client IP, use, in order: + // 1. The value passed in the options + // 2. The value in the X-Forwarded-For header + // 3. The peer address + clientIP := opts.HTTPClientIP + if clientIP == "" { + clientIP = serverClientIP(req.Header.Get("X-Forwarded-For")) + if clientIP == "" { + clientIP = peer + } + } if clientIP != "" { n++ } 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 868b09da277..ec538cbe026 100644 --- a/instrumentation/net/http/httptrace/otelhttptrace/internal/semconvutil/httpconv_test.go +++ b/instrumentation/net/http/httptrace/otelhttptrace/internal/semconvutil/httpconv_test.go @@ -12,6 +12,7 @@ import ( "net/url" "strconv" "testing" + "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -165,47 +166,83 @@ func TestHTTPClientRequestRequired(t *testing.T) { } func TestHTTPServerRequest(t *testing.T) { - got := make(chan *http.Request, 1) - handler := func(w http.ResponseWriter, r *http.Request) { - got <- r - w.WriteHeader(http.StatusOK) - } + testFn := func(requestModifierFn func(r *http.Request), opts HTTPServerRequestOptions, expectClientIP string) func(t *testing.T) { + return func(t *testing.T) { + reqCh := make(chan *http.Request, 1) + handler := func(w http.ResponseWriter, r *http.Request) { + r.RemoteAddr = "1.2.3.4:5678" + reqCh <- r + w.WriteHeader(http.StatusOK) + } - srv := httptest.NewServer(http.HandlerFunc(handler)) - defer srv.Close() + srv := httptest.NewServer(http.HandlerFunc(handler)) + defer srv.Close() - srvURL, err := url.Parse(srv.URL) - require.NoError(t, err) - srvPort, err := strconv.ParseInt(srvURL.Port(), 10, 32) - require.NoError(t, err) + srvURL, err := url.Parse(srv.URL) + require.NoError(t, err) + srvPort, err := strconv.ParseInt(srvURL.Port(), 10, 32) + require.NoError(t, err) - resp, err := srv.Client().Get(srv.URL) - require.NoError(t, err) - require.NoError(t, resp.Body.Close()) + req, err := http.NewRequest(http.MethodGet, srv.URL, nil) + require.NoError(t, err) - req := <-got - peer, peerPort := splitHostPort(req.RemoteAddr) + if requestModifierFn != nil { + requestModifierFn(req) + } - const user = "alice" - req.SetBasicAuth(user, "pswrd") + resp, err := srv.Client().Do(req) + require.NoError(t, err) + require.NoError(t, resp.Body.Close()) - const clientIP = "127.0.0.5" - req.Header.Add("X-Forwarded-For", clientIP) + var got *http.Request + select { + case got = <-reqCh: + // All good + case <-time.After(5 * time.Second): + t.Fatal("Did not receive a signal in 5s") + } - assert.ElementsMatch(t, - []attribute.KeyValue{ - attribute.String("http.method", "GET"), - attribute.String("http.scheme", "http"), - attribute.String("net.host.name", srvURL.Hostname()), - attribute.Int("net.host.port", int(srvPort)), - attribute.String("net.sock.peer.addr", peer), - attribute.Int("net.sock.peer.port", peerPort), - attribute.String("user_agent.original", "Go-http-client/1.1"), - attribute.String("http.client_ip", clientIP), - attribute.String("net.protocol.version", "1.1"), - attribute.String("http.target", "/"), + peer, peerPort := splitHostPort(got.RemoteAddr) + + const user = "alice" + got.SetBasicAuth(user, "pswrd") + + assert.ElementsMatch(t, + []attribute.KeyValue{ + attribute.String("http.method", "GET"), + attribute.String("http.scheme", "http"), + attribute.String("net.host.name", srvURL.Hostname()), + attribute.Int("net.host.port", int(srvPort)), + attribute.String("net.sock.peer.addr", peer), + attribute.Int("net.sock.peer.port", peerPort), + attribute.String("user_agent.original", "Go-http-client/1.1"), + attribute.String("http.client_ip", expectClientIP), + attribute.String("net.protocol.version", "1.1"), + attribute.String("http.target", "/"), + }, + HTTPServerRequest("", got, opts)) + } + } + + t.Run("client IP from network", testFn(nil, HTTPServerRequestOptions{}, "1.2.3.4")) + + t.Run("client IP from X-Forwarded-For header", testFn( + func(r *http.Request) { + r.Header.Add("X-Forwarded-For", "5.6.7.8") }, - HTTPServerRequest("", req)) + HTTPServerRequestOptions{}, + "5.6.7.8", + )) + + t.Run("set client IP in options", testFn( + func(r *http.Request) { + r.Header.Add("X-Forwarded-For", "5.6.7.8") + }, + HTTPServerRequestOptions{ + HTTPClientIP: "9.8.7.6", + }, + "9.8.7.6", + )) } func TestHTTPServerRequestMetrics(t *testing.T) { @@ -227,7 +264,13 @@ func TestHTTPServerRequestMetrics(t *testing.T) { require.NoError(t, err) require.NoError(t, resp.Body.Close()) - req := <-got + var req *http.Request + select { + case req = <-got: + // All good + case <-time.After(5 * time.Second): + t.Fatal("did not receive a signal in 5s") + } assert.ElementsMatch(t, []attribute.KeyValue{ @@ -250,14 +293,14 @@ func TestHTTPServerName(t *testing.T) { ) portStr := strconv.Itoa(port) server := host + ":" + portStr - assert.NotPanics(t, func() { got = HTTPServerRequest(server, req) }) + assert.NotPanics(t, func() { got = HTTPServerRequest(server, req, HTTPServerRequestOptions{}) }) 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) }) + assert.NotPanics(t, func() { got = HTTPServerRequest(host, req, HTTPServerRequestOptions{}) }) assert.Contains(t, got, attribute.String("net.host.name", host)) assert.Contains(t, got, attribute.Int("net.host.port", port)) } @@ -265,7 +308,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) }) + assert.NotPanics(t, func() { got = HTTPServerRequest("", req, HTTPServerRequestOptions{}) }) want := []attribute.KeyValue{ attribute.String("http.method", "GET"), attribute.String("http.scheme", "http"), 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 c999b05e675..09563c95bd1 100644 --- a/instrumentation/net/http/otelhttp/internal/semconv/v1.20.0.go +++ b/instrumentation/net/http/otelhttp/internal/semconv/v1.20.0.go @@ -36,7 +36,7 @@ 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) + return semconvutil.HTTPServerRequest(server, req, semconvutil.HTTPServerRequestOptions{}) } // ResponseTraceAttrs returns trace attributes for telemetry from an HTTP response. diff --git a/instrumentation/net/http/otelhttp/internal/semconvutil/httpconv.go b/instrumentation/net/http/otelhttp/internal/semconvutil/httpconv.go index a73bb06e90e..fe1e53ef23b 100644 --- a/instrumentation/net/http/otelhttp/internal/semconvutil/httpconv.go +++ b/instrumentation/net/http/otelhttp/internal/semconvutil/httpconv.go @@ -54,6 +54,11 @@ func HTTPClientStatus(code int) (codes.Code, string) { return hc.ClientStatus(code) } +type HTTPServerRequestOptions struct { + // If set, this is used as value for the "http.client_ip" attribute. + HTTPClientIP string +} + // HTTPServerRequest returns trace attributes for an HTTP request received by a // server. // @@ -75,8 +80,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) []attribute.KeyValue { - return hc.ServerRequest(server, req) +func HTTPServerRequest(server string, req *http.Request, opts HTTPServerRequestOptions) []attribute.KeyValue { + return hc.ServerRequest(server, req, opts) } // HTTPServerRequestMetrics returns metric attributes for an HTTP request received by a @@ -305,7 +310,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) []attribute.KeyValue { +func (c *httpConv) ServerRequest(server string, req *http.Request, opts HTTPServerRequestOptions) []attribute.KeyValue { /* The following semantic conventions are returned if present: http.method string http.scheme string @@ -358,7 +363,17 @@ func (c *httpConv) ServerRequest(server string, req *http.Request) []attribute.K n++ } - clientIP := serverClientIP(req.Header.Get("X-Forwarded-For")) + // For client IP, use, in order: + // 1. The value passed in the options + // 2. The value in the X-Forwarded-For header + // 3. The peer address + clientIP := opts.HTTPClientIP + if clientIP == "" { + clientIP = serverClientIP(req.Header.Get("X-Forwarded-For")) + if clientIP == "" { + clientIP = peer + } + } if clientIP != "" { n++ } diff --git a/instrumentation/net/http/otelhttp/internal/semconvutil/httpconv_test.go b/instrumentation/net/http/otelhttp/internal/semconvutil/httpconv_test.go index 868b09da277..ec538cbe026 100644 --- a/instrumentation/net/http/otelhttp/internal/semconvutil/httpconv_test.go +++ b/instrumentation/net/http/otelhttp/internal/semconvutil/httpconv_test.go @@ -12,6 +12,7 @@ import ( "net/url" "strconv" "testing" + "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -165,47 +166,83 @@ func TestHTTPClientRequestRequired(t *testing.T) { } func TestHTTPServerRequest(t *testing.T) { - got := make(chan *http.Request, 1) - handler := func(w http.ResponseWriter, r *http.Request) { - got <- r - w.WriteHeader(http.StatusOK) - } + testFn := func(requestModifierFn func(r *http.Request), opts HTTPServerRequestOptions, expectClientIP string) func(t *testing.T) { + return func(t *testing.T) { + reqCh := make(chan *http.Request, 1) + handler := func(w http.ResponseWriter, r *http.Request) { + r.RemoteAddr = "1.2.3.4:5678" + reqCh <- r + w.WriteHeader(http.StatusOK) + } - srv := httptest.NewServer(http.HandlerFunc(handler)) - defer srv.Close() + srv := httptest.NewServer(http.HandlerFunc(handler)) + defer srv.Close() - srvURL, err := url.Parse(srv.URL) - require.NoError(t, err) - srvPort, err := strconv.ParseInt(srvURL.Port(), 10, 32) - require.NoError(t, err) + srvURL, err := url.Parse(srv.URL) + require.NoError(t, err) + srvPort, err := strconv.ParseInt(srvURL.Port(), 10, 32) + require.NoError(t, err) - resp, err := srv.Client().Get(srv.URL) - require.NoError(t, err) - require.NoError(t, resp.Body.Close()) + req, err := http.NewRequest(http.MethodGet, srv.URL, nil) + require.NoError(t, err) - req := <-got - peer, peerPort := splitHostPort(req.RemoteAddr) + if requestModifierFn != nil { + requestModifierFn(req) + } - const user = "alice" - req.SetBasicAuth(user, "pswrd") + resp, err := srv.Client().Do(req) + require.NoError(t, err) + require.NoError(t, resp.Body.Close()) - const clientIP = "127.0.0.5" - req.Header.Add("X-Forwarded-For", clientIP) + var got *http.Request + select { + case got = <-reqCh: + // All good + case <-time.After(5 * time.Second): + t.Fatal("Did not receive a signal in 5s") + } - assert.ElementsMatch(t, - []attribute.KeyValue{ - attribute.String("http.method", "GET"), - attribute.String("http.scheme", "http"), - attribute.String("net.host.name", srvURL.Hostname()), - attribute.Int("net.host.port", int(srvPort)), - attribute.String("net.sock.peer.addr", peer), - attribute.Int("net.sock.peer.port", peerPort), - attribute.String("user_agent.original", "Go-http-client/1.1"), - attribute.String("http.client_ip", clientIP), - attribute.String("net.protocol.version", "1.1"), - attribute.String("http.target", "/"), + peer, peerPort := splitHostPort(got.RemoteAddr) + + const user = "alice" + got.SetBasicAuth(user, "pswrd") + + assert.ElementsMatch(t, + []attribute.KeyValue{ + attribute.String("http.method", "GET"), + attribute.String("http.scheme", "http"), + attribute.String("net.host.name", srvURL.Hostname()), + attribute.Int("net.host.port", int(srvPort)), + attribute.String("net.sock.peer.addr", peer), + attribute.Int("net.sock.peer.port", peerPort), + attribute.String("user_agent.original", "Go-http-client/1.1"), + attribute.String("http.client_ip", expectClientIP), + attribute.String("net.protocol.version", "1.1"), + attribute.String("http.target", "/"), + }, + HTTPServerRequest("", got, opts)) + } + } + + t.Run("client IP from network", testFn(nil, HTTPServerRequestOptions{}, "1.2.3.4")) + + t.Run("client IP from X-Forwarded-For header", testFn( + func(r *http.Request) { + r.Header.Add("X-Forwarded-For", "5.6.7.8") }, - HTTPServerRequest("", req)) + HTTPServerRequestOptions{}, + "5.6.7.8", + )) + + t.Run("set client IP in options", testFn( + func(r *http.Request) { + r.Header.Add("X-Forwarded-For", "5.6.7.8") + }, + HTTPServerRequestOptions{ + HTTPClientIP: "9.8.7.6", + }, + "9.8.7.6", + )) } func TestHTTPServerRequestMetrics(t *testing.T) { @@ -227,7 +264,13 @@ func TestHTTPServerRequestMetrics(t *testing.T) { require.NoError(t, err) require.NoError(t, resp.Body.Close()) - req := <-got + var req *http.Request + select { + case req = <-got: + // All good + case <-time.After(5 * time.Second): + t.Fatal("did not receive a signal in 5s") + } assert.ElementsMatch(t, []attribute.KeyValue{ @@ -250,14 +293,14 @@ func TestHTTPServerName(t *testing.T) { ) portStr := strconv.Itoa(port) server := host + ":" + portStr - assert.NotPanics(t, func() { got = HTTPServerRequest(server, req) }) + assert.NotPanics(t, func() { got = HTTPServerRequest(server, req, HTTPServerRequestOptions{}) }) 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) }) + assert.NotPanics(t, func() { got = HTTPServerRequest(host, req, HTTPServerRequestOptions{}) }) assert.Contains(t, got, attribute.String("net.host.name", host)) assert.Contains(t, got, attribute.Int("net.host.port", port)) } @@ -265,7 +308,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) }) + assert.NotPanics(t, func() { got = HTTPServerRequest("", req, HTTPServerRequestOptions{}) }) want := []attribute.KeyValue{ attribute.String("http.method", "GET"), attribute.String("http.scheme", "http"), diff --git a/internal/shared/semconvutil/httpconv.go.tmpl b/internal/shared/semconvutil/httpconv.go.tmpl index 3a675411290..0e299edb67f 100644 --- a/internal/shared/semconvutil/httpconv.go.tmpl +++ b/internal/shared/semconvutil/httpconv.go.tmpl @@ -54,6 +54,11 @@ func HTTPClientStatus(code int) (codes.Code, string) { return hc.ClientStatus(code) } +type HTTPServerRequestOptions struct { + // If set, this is used as value for the "http.client_ip" attribute. + HTTPClientIP string +} + // HTTPServerRequest returns trace attributes for an HTTP request received by a // server. // @@ -75,8 +80,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) []attribute.KeyValue { - return hc.ServerRequest(server, req) +func HTTPServerRequest(server string, req *http.Request, opts HTTPServerRequestOptions) []attribute.KeyValue { + return hc.ServerRequest(server, req, opts) } // HTTPServerRequestMetrics returns metric attributes for an HTTP request received by a @@ -305,7 +310,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) []attribute.KeyValue { +func (c *httpConv) ServerRequest(server string, req *http.Request, opts HTTPServerRequestOptions) []attribute.KeyValue { /* The following semantic conventions are returned if present: http.method string http.scheme string @@ -358,7 +363,17 @@ func (c *httpConv) ServerRequest(server string, req *http.Request) []attribute.K n++ } - clientIP := serverClientIP(req.Header.Get("X-Forwarded-For")) + // For client IP, use, in order: + // 1. The value passed in the options + // 2. The value in the X-Forwarded-For header + // 3. The peer address + clientIP := opts.HTTPClientIP + if clientIP == "" { + clientIP = serverClientIP(req.Header.Get("X-Forwarded-For")) + if clientIP == "" { + clientIP = peer + } + } if clientIP != "" { n++ } diff --git a/internal/shared/semconvutil/httpconv_test.go.tmpl b/internal/shared/semconvutil/httpconv_test.go.tmpl index 868b09da277..ec538cbe026 100644 --- a/internal/shared/semconvutil/httpconv_test.go.tmpl +++ b/internal/shared/semconvutil/httpconv_test.go.tmpl @@ -12,6 +12,7 @@ import ( "net/url" "strconv" "testing" + "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -165,47 +166,83 @@ func TestHTTPClientRequestRequired(t *testing.T) { } func TestHTTPServerRequest(t *testing.T) { - got := make(chan *http.Request, 1) - handler := func(w http.ResponseWriter, r *http.Request) { - got <- r - w.WriteHeader(http.StatusOK) - } + testFn := func(requestModifierFn func(r *http.Request), opts HTTPServerRequestOptions, expectClientIP string) func(t *testing.T) { + return func(t *testing.T) { + reqCh := make(chan *http.Request, 1) + handler := func(w http.ResponseWriter, r *http.Request) { + r.RemoteAddr = "1.2.3.4:5678" + reqCh <- r + w.WriteHeader(http.StatusOK) + } - srv := httptest.NewServer(http.HandlerFunc(handler)) - defer srv.Close() + srv := httptest.NewServer(http.HandlerFunc(handler)) + defer srv.Close() - srvURL, err := url.Parse(srv.URL) - require.NoError(t, err) - srvPort, err := strconv.ParseInt(srvURL.Port(), 10, 32) - require.NoError(t, err) + srvURL, err := url.Parse(srv.URL) + require.NoError(t, err) + srvPort, err := strconv.ParseInt(srvURL.Port(), 10, 32) + require.NoError(t, err) - resp, err := srv.Client().Get(srv.URL) - require.NoError(t, err) - require.NoError(t, resp.Body.Close()) + req, err := http.NewRequest(http.MethodGet, srv.URL, nil) + require.NoError(t, err) - req := <-got - peer, peerPort := splitHostPort(req.RemoteAddr) + if requestModifierFn != nil { + requestModifierFn(req) + } - const user = "alice" - req.SetBasicAuth(user, "pswrd") + resp, err := srv.Client().Do(req) + require.NoError(t, err) + require.NoError(t, resp.Body.Close()) - const clientIP = "127.0.0.5" - req.Header.Add("X-Forwarded-For", clientIP) + var got *http.Request + select { + case got = <-reqCh: + // All good + case <-time.After(5 * time.Second): + t.Fatal("Did not receive a signal in 5s") + } - assert.ElementsMatch(t, - []attribute.KeyValue{ - attribute.String("http.method", "GET"), - attribute.String("http.scheme", "http"), - attribute.String("net.host.name", srvURL.Hostname()), - attribute.Int("net.host.port", int(srvPort)), - attribute.String("net.sock.peer.addr", peer), - attribute.Int("net.sock.peer.port", peerPort), - attribute.String("user_agent.original", "Go-http-client/1.1"), - attribute.String("http.client_ip", clientIP), - attribute.String("net.protocol.version", "1.1"), - attribute.String("http.target", "/"), + peer, peerPort := splitHostPort(got.RemoteAddr) + + const user = "alice" + got.SetBasicAuth(user, "pswrd") + + assert.ElementsMatch(t, + []attribute.KeyValue{ + attribute.String("http.method", "GET"), + attribute.String("http.scheme", "http"), + attribute.String("net.host.name", srvURL.Hostname()), + attribute.Int("net.host.port", int(srvPort)), + attribute.String("net.sock.peer.addr", peer), + attribute.Int("net.sock.peer.port", peerPort), + attribute.String("user_agent.original", "Go-http-client/1.1"), + attribute.String("http.client_ip", expectClientIP), + attribute.String("net.protocol.version", "1.1"), + attribute.String("http.target", "/"), + }, + HTTPServerRequest("", got, opts)) + } + } + + t.Run("client IP from network", testFn(nil, HTTPServerRequestOptions{}, "1.2.3.4")) + + t.Run("client IP from X-Forwarded-For header", testFn( + func(r *http.Request) { + r.Header.Add("X-Forwarded-For", "5.6.7.8") }, - HTTPServerRequest("", req)) + HTTPServerRequestOptions{}, + "5.6.7.8", + )) + + t.Run("set client IP in options", testFn( + func(r *http.Request) { + r.Header.Add("X-Forwarded-For", "5.6.7.8") + }, + HTTPServerRequestOptions{ + HTTPClientIP: "9.8.7.6", + }, + "9.8.7.6", + )) } func TestHTTPServerRequestMetrics(t *testing.T) { @@ -227,7 +264,13 @@ func TestHTTPServerRequestMetrics(t *testing.T) { require.NoError(t, err) require.NoError(t, resp.Body.Close()) - req := <-got + var req *http.Request + select { + case req = <-got: + // All good + case <-time.After(5 * time.Second): + t.Fatal("did not receive a signal in 5s") + } assert.ElementsMatch(t, []attribute.KeyValue{ @@ -250,14 +293,14 @@ func TestHTTPServerName(t *testing.T) { ) portStr := strconv.Itoa(port) server := host + ":" + portStr - assert.NotPanics(t, func() { got = HTTPServerRequest(server, req) }) + assert.NotPanics(t, func() { got = HTTPServerRequest(server, req, HTTPServerRequestOptions{}) }) 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) }) + assert.NotPanics(t, func() { got = HTTPServerRequest(host, req, HTTPServerRequestOptions{}) }) assert.Contains(t, got, attribute.String("net.host.name", host)) assert.Contains(t, got, attribute.Int("net.host.port", port)) } @@ -265,7 +308,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) }) + assert.NotPanics(t, func() { got = HTTPServerRequest("", req, HTTPServerRequestOptions{}) }) want := []attribute.KeyValue{ attribute.String("http.method", "GET"), attribute.String("http.scheme", "http"), From 5ea8dec28365497f5fe8426a34d3416c343d8849 Mon Sep 17 00:00:00 2001 From: ItalyPaleAle <43508+ItalyPaleAle@users.noreply.github.com> Date: Wed, 11 Sep 2024 06:51:27 -0700 Subject: [PATCH 05/11] Updated per review --- .../internal/semconvutil/httpconv_test.go | 39 +++++++++++-------- .../internal/semconvutil/httpconv_test.go | 39 +++++++++++-------- .../internal/semconvutil/httpconv_test.go | 39 +++++++++++-------- .../internal/semconvutil/httpconv_test.go | 39 +++++++++++-------- .../internal/semconvutil/httpconv_test.go | 39 +++++++++++-------- .../internal/semconvutil/httpconv_test.go | 39 +++++++++++-------- .../internal/semconvutil/httpconv_test.go | 39 +++++++++++-------- .../shared/semconvutil/httpconv_test.go.tmpl | 39 +++++++++++-------- 8 files changed, 184 insertions(+), 128 deletions(-) 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 ec538cbe026..00b396195b9 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 @@ -166,7 +166,13 @@ func TestHTTPClientRequestRequired(t *testing.T) { } func TestHTTPServerRequest(t *testing.T) { - testFn := func(requestModifierFn func(r *http.Request), opts HTTPServerRequestOptions, expectClientIP string) func(t *testing.T) { + type testParams struct { + requestModifierFn func(r *http.Request) + httpServerRequestOpts HTTPServerRequestOptions + wantClientIP string + } + + testFn := func(tt testParams) func(t *testing.T) { return func(t *testing.T) { reqCh := make(chan *http.Request, 1) handler := func(w http.ResponseWriter, r *http.Request) { @@ -186,8 +192,8 @@ func TestHTTPServerRequest(t *testing.T) { req, err := http.NewRequest(http.MethodGet, srv.URL, nil) require.NoError(t, err) - if requestModifierFn != nil { - requestModifierFn(req) + if tt.requestModifierFn != nil { + tt.requestModifierFn(req) } resp, err := srv.Client().Do(req) @@ -216,33 +222,34 @@ func TestHTTPServerRequest(t *testing.T) { attribute.String("net.sock.peer.addr", peer), attribute.Int("net.sock.peer.port", peerPort), attribute.String("user_agent.original", "Go-http-client/1.1"), - attribute.String("http.client_ip", expectClientIP), + attribute.String("http.client_ip", tt.wantClientIP), attribute.String("net.protocol.version", "1.1"), attribute.String("http.target", "/"), }, - HTTPServerRequest("", got, opts)) + HTTPServerRequest("", got, tt.httpServerRequestOpts)) } } - t.Run("client IP from network", testFn(nil, HTTPServerRequestOptions{}, "1.2.3.4")) + t.Run("client IP from network", testFn(testParams{ + wantClientIP: "1.2.3.4", + })) - t.Run("client IP from X-Forwarded-For header", testFn( - func(r *http.Request) { + t.Run("client IP from X-Forwarded-For header", testFn(testParams{ + requestModifierFn: func(r *http.Request) { r.Header.Add("X-Forwarded-For", "5.6.7.8") }, - HTTPServerRequestOptions{}, - "5.6.7.8", - )) + wantClientIP: "5.6.7.8", + })) - t.Run("set client IP in options", testFn( - func(r *http.Request) { + t.Run("set client IP in options", testFn(testParams{ + requestModifierFn: func(r *http.Request) { r.Header.Add("X-Forwarded-For", "5.6.7.8") }, - HTTPServerRequestOptions{ + httpServerRequestOpts: HTTPServerRequestOptions{ HTTPClientIP: "9.8.7.6", }, - "9.8.7.6", - )) + wantClientIP: "9.8.7.6", + })) } func TestHTTPServerRequestMetrics(t *testing.T) { 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 ec538cbe026..00b396195b9 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 @@ -166,7 +166,13 @@ func TestHTTPClientRequestRequired(t *testing.T) { } func TestHTTPServerRequest(t *testing.T) { - testFn := func(requestModifierFn func(r *http.Request), opts HTTPServerRequestOptions, expectClientIP string) func(t *testing.T) { + type testParams struct { + requestModifierFn func(r *http.Request) + httpServerRequestOpts HTTPServerRequestOptions + wantClientIP string + } + + testFn := func(tt testParams) func(t *testing.T) { return func(t *testing.T) { reqCh := make(chan *http.Request, 1) handler := func(w http.ResponseWriter, r *http.Request) { @@ -186,8 +192,8 @@ func TestHTTPServerRequest(t *testing.T) { req, err := http.NewRequest(http.MethodGet, srv.URL, nil) require.NoError(t, err) - if requestModifierFn != nil { - requestModifierFn(req) + if tt.requestModifierFn != nil { + tt.requestModifierFn(req) } resp, err := srv.Client().Do(req) @@ -216,33 +222,34 @@ func TestHTTPServerRequest(t *testing.T) { attribute.String("net.sock.peer.addr", peer), attribute.Int("net.sock.peer.port", peerPort), attribute.String("user_agent.original", "Go-http-client/1.1"), - attribute.String("http.client_ip", expectClientIP), + attribute.String("http.client_ip", tt.wantClientIP), attribute.String("net.protocol.version", "1.1"), attribute.String("http.target", "/"), }, - HTTPServerRequest("", got, opts)) + HTTPServerRequest("", got, tt.httpServerRequestOpts)) } } - t.Run("client IP from network", testFn(nil, HTTPServerRequestOptions{}, "1.2.3.4")) + t.Run("client IP from network", testFn(testParams{ + wantClientIP: "1.2.3.4", + })) - t.Run("client IP from X-Forwarded-For header", testFn( - func(r *http.Request) { + t.Run("client IP from X-Forwarded-For header", testFn(testParams{ + requestModifierFn: func(r *http.Request) { r.Header.Add("X-Forwarded-For", "5.6.7.8") }, - HTTPServerRequestOptions{}, - "5.6.7.8", - )) + wantClientIP: "5.6.7.8", + })) - t.Run("set client IP in options", testFn( - func(r *http.Request) { + t.Run("set client IP in options", testFn(testParams{ + requestModifierFn: func(r *http.Request) { r.Header.Add("X-Forwarded-For", "5.6.7.8") }, - HTTPServerRequestOptions{ + httpServerRequestOpts: HTTPServerRequestOptions{ HTTPClientIP: "9.8.7.6", }, - "9.8.7.6", - )) + wantClientIP: "9.8.7.6", + })) } func TestHTTPServerRequestMetrics(t *testing.T) { 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 ec538cbe026..00b396195b9 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 @@ -166,7 +166,13 @@ func TestHTTPClientRequestRequired(t *testing.T) { } func TestHTTPServerRequest(t *testing.T) { - testFn := func(requestModifierFn func(r *http.Request), opts HTTPServerRequestOptions, expectClientIP string) func(t *testing.T) { + type testParams struct { + requestModifierFn func(r *http.Request) + httpServerRequestOpts HTTPServerRequestOptions + wantClientIP string + } + + testFn := func(tt testParams) func(t *testing.T) { return func(t *testing.T) { reqCh := make(chan *http.Request, 1) handler := func(w http.ResponseWriter, r *http.Request) { @@ -186,8 +192,8 @@ func TestHTTPServerRequest(t *testing.T) { req, err := http.NewRequest(http.MethodGet, srv.URL, nil) require.NoError(t, err) - if requestModifierFn != nil { - requestModifierFn(req) + if tt.requestModifierFn != nil { + tt.requestModifierFn(req) } resp, err := srv.Client().Do(req) @@ -216,33 +222,34 @@ func TestHTTPServerRequest(t *testing.T) { attribute.String("net.sock.peer.addr", peer), attribute.Int("net.sock.peer.port", peerPort), attribute.String("user_agent.original", "Go-http-client/1.1"), - attribute.String("http.client_ip", expectClientIP), + attribute.String("http.client_ip", tt.wantClientIP), attribute.String("net.protocol.version", "1.1"), attribute.String("http.target", "/"), }, - HTTPServerRequest("", got, opts)) + HTTPServerRequest("", got, tt.httpServerRequestOpts)) } } - t.Run("client IP from network", testFn(nil, HTTPServerRequestOptions{}, "1.2.3.4")) + t.Run("client IP from network", testFn(testParams{ + wantClientIP: "1.2.3.4", + })) - t.Run("client IP from X-Forwarded-For header", testFn( - func(r *http.Request) { + t.Run("client IP from X-Forwarded-For header", testFn(testParams{ + requestModifierFn: func(r *http.Request) { r.Header.Add("X-Forwarded-For", "5.6.7.8") }, - HTTPServerRequestOptions{}, - "5.6.7.8", - )) + wantClientIP: "5.6.7.8", + })) - t.Run("set client IP in options", testFn( - func(r *http.Request) { + t.Run("set client IP in options", testFn(testParams{ + requestModifierFn: func(r *http.Request) { r.Header.Add("X-Forwarded-For", "5.6.7.8") }, - HTTPServerRequestOptions{ + httpServerRequestOpts: HTTPServerRequestOptions{ HTTPClientIP: "9.8.7.6", }, - "9.8.7.6", - )) + wantClientIP: "9.8.7.6", + })) } func TestHTTPServerRequestMetrics(t *testing.T) { 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 ec538cbe026..00b396195b9 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 @@ -166,7 +166,13 @@ func TestHTTPClientRequestRequired(t *testing.T) { } func TestHTTPServerRequest(t *testing.T) { - testFn := func(requestModifierFn func(r *http.Request), opts HTTPServerRequestOptions, expectClientIP string) func(t *testing.T) { + type testParams struct { + requestModifierFn func(r *http.Request) + httpServerRequestOpts HTTPServerRequestOptions + wantClientIP string + } + + testFn := func(tt testParams) func(t *testing.T) { return func(t *testing.T) { reqCh := make(chan *http.Request, 1) handler := func(w http.ResponseWriter, r *http.Request) { @@ -186,8 +192,8 @@ func TestHTTPServerRequest(t *testing.T) { req, err := http.NewRequest(http.MethodGet, srv.URL, nil) require.NoError(t, err) - if requestModifierFn != nil { - requestModifierFn(req) + if tt.requestModifierFn != nil { + tt.requestModifierFn(req) } resp, err := srv.Client().Do(req) @@ -216,33 +222,34 @@ func TestHTTPServerRequest(t *testing.T) { attribute.String("net.sock.peer.addr", peer), attribute.Int("net.sock.peer.port", peerPort), attribute.String("user_agent.original", "Go-http-client/1.1"), - attribute.String("http.client_ip", expectClientIP), + attribute.String("http.client_ip", tt.wantClientIP), attribute.String("net.protocol.version", "1.1"), attribute.String("http.target", "/"), }, - HTTPServerRequest("", got, opts)) + HTTPServerRequest("", got, tt.httpServerRequestOpts)) } } - t.Run("client IP from network", testFn(nil, HTTPServerRequestOptions{}, "1.2.3.4")) + t.Run("client IP from network", testFn(testParams{ + wantClientIP: "1.2.3.4", + })) - t.Run("client IP from X-Forwarded-For header", testFn( - func(r *http.Request) { + t.Run("client IP from X-Forwarded-For header", testFn(testParams{ + requestModifierFn: func(r *http.Request) { r.Header.Add("X-Forwarded-For", "5.6.7.8") }, - HTTPServerRequestOptions{}, - "5.6.7.8", - )) + wantClientIP: "5.6.7.8", + })) - t.Run("set client IP in options", testFn( - func(r *http.Request) { + t.Run("set client IP in options", testFn(testParams{ + requestModifierFn: func(r *http.Request) { r.Header.Add("X-Forwarded-For", "5.6.7.8") }, - HTTPServerRequestOptions{ + httpServerRequestOpts: HTTPServerRequestOptions{ HTTPClientIP: "9.8.7.6", }, - "9.8.7.6", - )) + wantClientIP: "9.8.7.6", + })) } func TestHTTPServerRequestMetrics(t *testing.T) { diff --git a/instrumentation/gopkg.in/macaron.v1/otelmacaron/internal/semconvutil/httpconv_test.go b/instrumentation/gopkg.in/macaron.v1/otelmacaron/internal/semconvutil/httpconv_test.go index ec538cbe026..00b396195b9 100644 --- a/instrumentation/gopkg.in/macaron.v1/otelmacaron/internal/semconvutil/httpconv_test.go +++ b/instrumentation/gopkg.in/macaron.v1/otelmacaron/internal/semconvutil/httpconv_test.go @@ -166,7 +166,13 @@ func TestHTTPClientRequestRequired(t *testing.T) { } func TestHTTPServerRequest(t *testing.T) { - testFn := func(requestModifierFn func(r *http.Request), opts HTTPServerRequestOptions, expectClientIP string) func(t *testing.T) { + type testParams struct { + requestModifierFn func(r *http.Request) + httpServerRequestOpts HTTPServerRequestOptions + wantClientIP string + } + + testFn := func(tt testParams) func(t *testing.T) { return func(t *testing.T) { reqCh := make(chan *http.Request, 1) handler := func(w http.ResponseWriter, r *http.Request) { @@ -186,8 +192,8 @@ func TestHTTPServerRequest(t *testing.T) { req, err := http.NewRequest(http.MethodGet, srv.URL, nil) require.NoError(t, err) - if requestModifierFn != nil { - requestModifierFn(req) + if tt.requestModifierFn != nil { + tt.requestModifierFn(req) } resp, err := srv.Client().Do(req) @@ -216,33 +222,34 @@ func TestHTTPServerRequest(t *testing.T) { attribute.String("net.sock.peer.addr", peer), attribute.Int("net.sock.peer.port", peerPort), attribute.String("user_agent.original", "Go-http-client/1.1"), - attribute.String("http.client_ip", expectClientIP), + attribute.String("http.client_ip", tt.wantClientIP), attribute.String("net.protocol.version", "1.1"), attribute.String("http.target", "/"), }, - HTTPServerRequest("", got, opts)) + HTTPServerRequest("", got, tt.httpServerRequestOpts)) } } - t.Run("client IP from network", testFn(nil, HTTPServerRequestOptions{}, "1.2.3.4")) + t.Run("client IP from network", testFn(testParams{ + wantClientIP: "1.2.3.4", + })) - t.Run("client IP from X-Forwarded-For header", testFn( - func(r *http.Request) { + t.Run("client IP from X-Forwarded-For header", testFn(testParams{ + requestModifierFn: func(r *http.Request) { r.Header.Add("X-Forwarded-For", "5.6.7.8") }, - HTTPServerRequestOptions{}, - "5.6.7.8", - )) + wantClientIP: "5.6.7.8", + })) - t.Run("set client IP in options", testFn( - func(r *http.Request) { + t.Run("set client IP in options", testFn(testParams{ + requestModifierFn: func(r *http.Request) { r.Header.Add("X-Forwarded-For", "5.6.7.8") }, - HTTPServerRequestOptions{ + httpServerRequestOpts: HTTPServerRequestOptions{ HTTPClientIP: "9.8.7.6", }, - "9.8.7.6", - )) + wantClientIP: "9.8.7.6", + })) } func TestHTTPServerRequestMetrics(t *testing.T) { 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 ec538cbe026..00b396195b9 100644 --- a/instrumentation/net/http/httptrace/otelhttptrace/internal/semconvutil/httpconv_test.go +++ b/instrumentation/net/http/httptrace/otelhttptrace/internal/semconvutil/httpconv_test.go @@ -166,7 +166,13 @@ func TestHTTPClientRequestRequired(t *testing.T) { } func TestHTTPServerRequest(t *testing.T) { - testFn := func(requestModifierFn func(r *http.Request), opts HTTPServerRequestOptions, expectClientIP string) func(t *testing.T) { + type testParams struct { + requestModifierFn func(r *http.Request) + httpServerRequestOpts HTTPServerRequestOptions + wantClientIP string + } + + testFn := func(tt testParams) func(t *testing.T) { return func(t *testing.T) { reqCh := make(chan *http.Request, 1) handler := func(w http.ResponseWriter, r *http.Request) { @@ -186,8 +192,8 @@ func TestHTTPServerRequest(t *testing.T) { req, err := http.NewRequest(http.MethodGet, srv.URL, nil) require.NoError(t, err) - if requestModifierFn != nil { - requestModifierFn(req) + if tt.requestModifierFn != nil { + tt.requestModifierFn(req) } resp, err := srv.Client().Do(req) @@ -216,33 +222,34 @@ func TestHTTPServerRequest(t *testing.T) { attribute.String("net.sock.peer.addr", peer), attribute.Int("net.sock.peer.port", peerPort), attribute.String("user_agent.original", "Go-http-client/1.1"), - attribute.String("http.client_ip", expectClientIP), + attribute.String("http.client_ip", tt.wantClientIP), attribute.String("net.protocol.version", "1.1"), attribute.String("http.target", "/"), }, - HTTPServerRequest("", got, opts)) + HTTPServerRequest("", got, tt.httpServerRequestOpts)) } } - t.Run("client IP from network", testFn(nil, HTTPServerRequestOptions{}, "1.2.3.4")) + t.Run("client IP from network", testFn(testParams{ + wantClientIP: "1.2.3.4", + })) - t.Run("client IP from X-Forwarded-For header", testFn( - func(r *http.Request) { + t.Run("client IP from X-Forwarded-For header", testFn(testParams{ + requestModifierFn: func(r *http.Request) { r.Header.Add("X-Forwarded-For", "5.6.7.8") }, - HTTPServerRequestOptions{}, - "5.6.7.8", - )) + wantClientIP: "5.6.7.8", + })) - t.Run("set client IP in options", testFn( - func(r *http.Request) { + t.Run("set client IP in options", testFn(testParams{ + requestModifierFn: func(r *http.Request) { r.Header.Add("X-Forwarded-For", "5.6.7.8") }, - HTTPServerRequestOptions{ + httpServerRequestOpts: HTTPServerRequestOptions{ HTTPClientIP: "9.8.7.6", }, - "9.8.7.6", - )) + wantClientIP: "9.8.7.6", + })) } func TestHTTPServerRequestMetrics(t *testing.T) { diff --git a/instrumentation/net/http/otelhttp/internal/semconvutil/httpconv_test.go b/instrumentation/net/http/otelhttp/internal/semconvutil/httpconv_test.go index ec538cbe026..00b396195b9 100644 --- a/instrumentation/net/http/otelhttp/internal/semconvutil/httpconv_test.go +++ b/instrumentation/net/http/otelhttp/internal/semconvutil/httpconv_test.go @@ -166,7 +166,13 @@ func TestHTTPClientRequestRequired(t *testing.T) { } func TestHTTPServerRequest(t *testing.T) { - testFn := func(requestModifierFn func(r *http.Request), opts HTTPServerRequestOptions, expectClientIP string) func(t *testing.T) { + type testParams struct { + requestModifierFn func(r *http.Request) + httpServerRequestOpts HTTPServerRequestOptions + wantClientIP string + } + + testFn := func(tt testParams) func(t *testing.T) { return func(t *testing.T) { reqCh := make(chan *http.Request, 1) handler := func(w http.ResponseWriter, r *http.Request) { @@ -186,8 +192,8 @@ func TestHTTPServerRequest(t *testing.T) { req, err := http.NewRequest(http.MethodGet, srv.URL, nil) require.NoError(t, err) - if requestModifierFn != nil { - requestModifierFn(req) + if tt.requestModifierFn != nil { + tt.requestModifierFn(req) } resp, err := srv.Client().Do(req) @@ -216,33 +222,34 @@ func TestHTTPServerRequest(t *testing.T) { attribute.String("net.sock.peer.addr", peer), attribute.Int("net.sock.peer.port", peerPort), attribute.String("user_agent.original", "Go-http-client/1.1"), - attribute.String("http.client_ip", expectClientIP), + attribute.String("http.client_ip", tt.wantClientIP), attribute.String("net.protocol.version", "1.1"), attribute.String("http.target", "/"), }, - HTTPServerRequest("", got, opts)) + HTTPServerRequest("", got, tt.httpServerRequestOpts)) } } - t.Run("client IP from network", testFn(nil, HTTPServerRequestOptions{}, "1.2.3.4")) + t.Run("client IP from network", testFn(testParams{ + wantClientIP: "1.2.3.4", + })) - t.Run("client IP from X-Forwarded-For header", testFn( - func(r *http.Request) { + t.Run("client IP from X-Forwarded-For header", testFn(testParams{ + requestModifierFn: func(r *http.Request) { r.Header.Add("X-Forwarded-For", "5.6.7.8") }, - HTTPServerRequestOptions{}, - "5.6.7.8", - )) + wantClientIP: "5.6.7.8", + })) - t.Run("set client IP in options", testFn( - func(r *http.Request) { + t.Run("set client IP in options", testFn(testParams{ + requestModifierFn: func(r *http.Request) { r.Header.Add("X-Forwarded-For", "5.6.7.8") }, - HTTPServerRequestOptions{ + httpServerRequestOpts: HTTPServerRequestOptions{ HTTPClientIP: "9.8.7.6", }, - "9.8.7.6", - )) + wantClientIP: "9.8.7.6", + })) } func TestHTTPServerRequestMetrics(t *testing.T) { diff --git a/internal/shared/semconvutil/httpconv_test.go.tmpl b/internal/shared/semconvutil/httpconv_test.go.tmpl index ec538cbe026..00b396195b9 100644 --- a/internal/shared/semconvutil/httpconv_test.go.tmpl +++ b/internal/shared/semconvutil/httpconv_test.go.tmpl @@ -166,7 +166,13 @@ func TestHTTPClientRequestRequired(t *testing.T) { } func TestHTTPServerRequest(t *testing.T) { - testFn := func(requestModifierFn func(r *http.Request), opts HTTPServerRequestOptions, expectClientIP string) func(t *testing.T) { + type testParams struct { + requestModifierFn func(r *http.Request) + httpServerRequestOpts HTTPServerRequestOptions + wantClientIP string + } + + testFn := func(tt testParams) func(t *testing.T) { return func(t *testing.T) { reqCh := make(chan *http.Request, 1) handler := func(w http.ResponseWriter, r *http.Request) { @@ -186,8 +192,8 @@ func TestHTTPServerRequest(t *testing.T) { req, err := http.NewRequest(http.MethodGet, srv.URL, nil) require.NoError(t, err) - if requestModifierFn != nil { - requestModifierFn(req) + if tt.requestModifierFn != nil { + tt.requestModifierFn(req) } resp, err := srv.Client().Do(req) @@ -216,33 +222,34 @@ func TestHTTPServerRequest(t *testing.T) { attribute.String("net.sock.peer.addr", peer), attribute.Int("net.sock.peer.port", peerPort), attribute.String("user_agent.original", "Go-http-client/1.1"), - attribute.String("http.client_ip", expectClientIP), + attribute.String("http.client_ip", tt.wantClientIP), attribute.String("net.protocol.version", "1.1"), attribute.String("http.target", "/"), }, - HTTPServerRequest("", got, opts)) + HTTPServerRequest("", got, tt.httpServerRequestOpts)) } } - t.Run("client IP from network", testFn(nil, HTTPServerRequestOptions{}, "1.2.3.4")) + t.Run("client IP from network", testFn(testParams{ + wantClientIP: "1.2.3.4", + })) - t.Run("client IP from X-Forwarded-For header", testFn( - func(r *http.Request) { + t.Run("client IP from X-Forwarded-For header", testFn(testParams{ + requestModifierFn: func(r *http.Request) { r.Header.Add("X-Forwarded-For", "5.6.7.8") }, - HTTPServerRequestOptions{}, - "5.6.7.8", - )) + wantClientIP: "5.6.7.8", + })) - t.Run("set client IP in options", testFn( - func(r *http.Request) { + t.Run("set client IP in options", testFn(testParams{ + requestModifierFn: func(r *http.Request) { r.Header.Add("X-Forwarded-For", "5.6.7.8") }, - HTTPServerRequestOptions{ + httpServerRequestOpts: HTTPServerRequestOptions{ HTTPClientIP: "9.8.7.6", }, - "9.8.7.6", - )) + wantClientIP: "9.8.7.6", + })) } func TestHTTPServerRequestMetrics(t *testing.T) { From 2046f0bfec361a1028d89190976a1895ab820bb8 Mon Sep 17 00:00:00 2001 From: ItalyPaleAle <43508+ItalyPaleAle@users.noreply.github.com> Date: Wed, 11 Sep 2024 06:54:12 -0700 Subject: [PATCH 06/11] Add changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 16e09533c0b..2dd5c15e217 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - Superfluous call to `WriteHeader` when flushing after setting a status code in `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp`. (#6074) - Superfluous call to `WriteHeader` when writing the response body after setting a status code in `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp`. (#6055) +- Use Gin's own `ClientIP` method to detect the client's IP, which supports custom proxy headers, in `go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin` From 5cc00254daa0aa95482597c5311abc802dd6bb1d Mon Sep 17 00:00:00 2001 From: "Alessandro (Ale) Segala" <43508+ItalyPaleAle@users.noreply.github.com> Date: Tue, 17 Sep 2024 08:04:26 -0700 Subject: [PATCH 07/11] Update CHANGELOG.md Co-authored-by: Damien Mathieu <42@dmathieu.com> --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2dd5c15e217..572b622fc1b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,7 +21,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - Superfluous call to `WriteHeader` when flushing after setting a status code in `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp`. (#6074) - Superfluous call to `WriteHeader` when writing the response body after setting a status code in `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp`. (#6055) -- Use Gin's own `ClientIP` method to detect the client's IP, which supports custom proxy headers, in `go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin` +- Use Gin's own `ClientIP` method to detect the client's IP, which supports custom proxy headers in `go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin`. (#6095) From 66533feb3b99d8d5ed86b03c3734dcf19792893d Mon Sep 17 00:00:00 2001 From: ItalyPaleAle <43508+ItalyPaleAle@users.noreply.github.com> Date: Tue, 17 Sep 2024 08:06:23 -0700 Subject: [PATCH 08/11] updated per review feedback --- .../internal/semconvutil/httpconv_test.go | 55 ++++++++++--------- .../internal/semconvutil/httpconv_test.go | 55 ++++++++++--------- .../internal/semconvutil/httpconv_test.go | 55 ++++++++++--------- .../internal/semconvutil/httpconv_test.go | 55 ++++++++++--------- .../internal/semconvutil/httpconv_test.go | 55 ++++++++++--------- .../internal/semconvutil/httpconv_test.go | 55 ++++++++++--------- .../internal/semconvutil/httpconv_test.go | 55 ++++++++++--------- .../shared/semconvutil/httpconv_test.go.tmpl | 55 ++++++++++--------- 8 files changed, 224 insertions(+), 216 deletions(-) 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 00b396195b9..b58f40d0c56 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 @@ -166,14 +166,36 @@ func TestHTTPClientRequestRequired(t *testing.T) { } func TestHTTPServerRequest(t *testing.T) { - type testParams struct { + for _, tt := range []struct { + name string requestModifierFn func(r *http.Request) httpServerRequestOpts HTTPServerRequestOptions - wantClientIP string - } - testFn := func(tt testParams) func(t *testing.T) { - return func(t *testing.T) { + wantClientIP string + }{ + { + name: "with a client IP from the network", + wantClientIP: "1.2.3.4", + }, + { + name: "with a client IP from x-forwarded-for header", + requestModifierFn: func(r *http.Request) { + r.Header.Add("X-Forwarded-For", "5.6.7.8") + }, + wantClientIP: "5.6.7.8", + }, + { + name: "with a cl;ient IP in options", + requestModifierFn: func(r *http.Request) { + r.Header.Add("X-Forwarded-For", "5.6.7.8") + }, + httpServerRequestOpts: HTTPServerRequestOptions{ + HTTPClientIP: "9.8.7.6", + }, + wantClientIP: "9.8.7.6", + }, + } { + t.Run(tt.name, func(t *testing.T) { reqCh := make(chan *http.Request, 1) handler := func(w http.ResponseWriter, r *http.Request) { r.RemoteAddr = "1.2.3.4:5678" @@ -227,29 +249,8 @@ func TestHTTPServerRequest(t *testing.T) { attribute.String("http.target", "/"), }, HTTPServerRequest("", got, tt.httpServerRequestOpts)) - } + }) } - - t.Run("client IP from network", testFn(testParams{ - wantClientIP: "1.2.3.4", - })) - - t.Run("client IP from X-Forwarded-For header", testFn(testParams{ - requestModifierFn: func(r *http.Request) { - r.Header.Add("X-Forwarded-For", "5.6.7.8") - }, - wantClientIP: "5.6.7.8", - })) - - t.Run("set client IP in options", testFn(testParams{ - requestModifierFn: func(r *http.Request) { - r.Header.Add("X-Forwarded-For", "5.6.7.8") - }, - httpServerRequestOpts: HTTPServerRequestOptions{ - HTTPClientIP: "9.8.7.6", - }, - wantClientIP: "9.8.7.6", - })) } func TestHTTPServerRequestMetrics(t *testing.T) { 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 00b396195b9..b58f40d0c56 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 @@ -166,14 +166,36 @@ func TestHTTPClientRequestRequired(t *testing.T) { } func TestHTTPServerRequest(t *testing.T) { - type testParams struct { + for _, tt := range []struct { + name string requestModifierFn func(r *http.Request) httpServerRequestOpts HTTPServerRequestOptions - wantClientIP string - } - testFn := func(tt testParams) func(t *testing.T) { - return func(t *testing.T) { + wantClientIP string + }{ + { + name: "with a client IP from the network", + wantClientIP: "1.2.3.4", + }, + { + name: "with a client IP from x-forwarded-for header", + requestModifierFn: func(r *http.Request) { + r.Header.Add("X-Forwarded-For", "5.6.7.8") + }, + wantClientIP: "5.6.7.8", + }, + { + name: "with a cl;ient IP in options", + requestModifierFn: func(r *http.Request) { + r.Header.Add("X-Forwarded-For", "5.6.7.8") + }, + httpServerRequestOpts: HTTPServerRequestOptions{ + HTTPClientIP: "9.8.7.6", + }, + wantClientIP: "9.8.7.6", + }, + } { + t.Run(tt.name, func(t *testing.T) { reqCh := make(chan *http.Request, 1) handler := func(w http.ResponseWriter, r *http.Request) { r.RemoteAddr = "1.2.3.4:5678" @@ -227,29 +249,8 @@ func TestHTTPServerRequest(t *testing.T) { attribute.String("http.target", "/"), }, HTTPServerRequest("", got, tt.httpServerRequestOpts)) - } + }) } - - t.Run("client IP from network", testFn(testParams{ - wantClientIP: "1.2.3.4", - })) - - t.Run("client IP from X-Forwarded-For header", testFn(testParams{ - requestModifierFn: func(r *http.Request) { - r.Header.Add("X-Forwarded-For", "5.6.7.8") - }, - wantClientIP: "5.6.7.8", - })) - - t.Run("set client IP in options", testFn(testParams{ - requestModifierFn: func(r *http.Request) { - r.Header.Add("X-Forwarded-For", "5.6.7.8") - }, - httpServerRequestOpts: HTTPServerRequestOptions{ - HTTPClientIP: "9.8.7.6", - }, - wantClientIP: "9.8.7.6", - })) } func TestHTTPServerRequestMetrics(t *testing.T) { 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 00b396195b9..b58f40d0c56 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 @@ -166,14 +166,36 @@ func TestHTTPClientRequestRequired(t *testing.T) { } func TestHTTPServerRequest(t *testing.T) { - type testParams struct { + for _, tt := range []struct { + name string requestModifierFn func(r *http.Request) httpServerRequestOpts HTTPServerRequestOptions - wantClientIP string - } - testFn := func(tt testParams) func(t *testing.T) { - return func(t *testing.T) { + wantClientIP string + }{ + { + name: "with a client IP from the network", + wantClientIP: "1.2.3.4", + }, + { + name: "with a client IP from x-forwarded-for header", + requestModifierFn: func(r *http.Request) { + r.Header.Add("X-Forwarded-For", "5.6.7.8") + }, + wantClientIP: "5.6.7.8", + }, + { + name: "with a cl;ient IP in options", + requestModifierFn: func(r *http.Request) { + r.Header.Add("X-Forwarded-For", "5.6.7.8") + }, + httpServerRequestOpts: HTTPServerRequestOptions{ + HTTPClientIP: "9.8.7.6", + }, + wantClientIP: "9.8.7.6", + }, + } { + t.Run(tt.name, func(t *testing.T) { reqCh := make(chan *http.Request, 1) handler := func(w http.ResponseWriter, r *http.Request) { r.RemoteAddr = "1.2.3.4:5678" @@ -227,29 +249,8 @@ func TestHTTPServerRequest(t *testing.T) { attribute.String("http.target", "/"), }, HTTPServerRequest("", got, tt.httpServerRequestOpts)) - } + }) } - - t.Run("client IP from network", testFn(testParams{ - wantClientIP: "1.2.3.4", - })) - - t.Run("client IP from X-Forwarded-For header", testFn(testParams{ - requestModifierFn: func(r *http.Request) { - r.Header.Add("X-Forwarded-For", "5.6.7.8") - }, - wantClientIP: "5.6.7.8", - })) - - t.Run("set client IP in options", testFn(testParams{ - requestModifierFn: func(r *http.Request) { - r.Header.Add("X-Forwarded-For", "5.6.7.8") - }, - httpServerRequestOpts: HTTPServerRequestOptions{ - HTTPClientIP: "9.8.7.6", - }, - wantClientIP: "9.8.7.6", - })) } func TestHTTPServerRequestMetrics(t *testing.T) { 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 00b396195b9..b58f40d0c56 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 @@ -166,14 +166,36 @@ func TestHTTPClientRequestRequired(t *testing.T) { } func TestHTTPServerRequest(t *testing.T) { - type testParams struct { + for _, tt := range []struct { + name string requestModifierFn func(r *http.Request) httpServerRequestOpts HTTPServerRequestOptions - wantClientIP string - } - testFn := func(tt testParams) func(t *testing.T) { - return func(t *testing.T) { + wantClientIP string + }{ + { + name: "with a client IP from the network", + wantClientIP: "1.2.3.4", + }, + { + name: "with a client IP from x-forwarded-for header", + requestModifierFn: func(r *http.Request) { + r.Header.Add("X-Forwarded-For", "5.6.7.8") + }, + wantClientIP: "5.6.7.8", + }, + { + name: "with a cl;ient IP in options", + requestModifierFn: func(r *http.Request) { + r.Header.Add("X-Forwarded-For", "5.6.7.8") + }, + httpServerRequestOpts: HTTPServerRequestOptions{ + HTTPClientIP: "9.8.7.6", + }, + wantClientIP: "9.8.7.6", + }, + } { + t.Run(tt.name, func(t *testing.T) { reqCh := make(chan *http.Request, 1) handler := func(w http.ResponseWriter, r *http.Request) { r.RemoteAddr = "1.2.3.4:5678" @@ -227,29 +249,8 @@ func TestHTTPServerRequest(t *testing.T) { attribute.String("http.target", "/"), }, HTTPServerRequest("", got, tt.httpServerRequestOpts)) - } + }) } - - t.Run("client IP from network", testFn(testParams{ - wantClientIP: "1.2.3.4", - })) - - t.Run("client IP from X-Forwarded-For header", testFn(testParams{ - requestModifierFn: func(r *http.Request) { - r.Header.Add("X-Forwarded-For", "5.6.7.8") - }, - wantClientIP: "5.6.7.8", - })) - - t.Run("set client IP in options", testFn(testParams{ - requestModifierFn: func(r *http.Request) { - r.Header.Add("X-Forwarded-For", "5.6.7.8") - }, - httpServerRequestOpts: HTTPServerRequestOptions{ - HTTPClientIP: "9.8.7.6", - }, - wantClientIP: "9.8.7.6", - })) } func TestHTTPServerRequestMetrics(t *testing.T) { diff --git a/instrumentation/gopkg.in/macaron.v1/otelmacaron/internal/semconvutil/httpconv_test.go b/instrumentation/gopkg.in/macaron.v1/otelmacaron/internal/semconvutil/httpconv_test.go index 00b396195b9..b58f40d0c56 100644 --- a/instrumentation/gopkg.in/macaron.v1/otelmacaron/internal/semconvutil/httpconv_test.go +++ b/instrumentation/gopkg.in/macaron.v1/otelmacaron/internal/semconvutil/httpconv_test.go @@ -166,14 +166,36 @@ func TestHTTPClientRequestRequired(t *testing.T) { } func TestHTTPServerRequest(t *testing.T) { - type testParams struct { + for _, tt := range []struct { + name string requestModifierFn func(r *http.Request) httpServerRequestOpts HTTPServerRequestOptions - wantClientIP string - } - testFn := func(tt testParams) func(t *testing.T) { - return func(t *testing.T) { + wantClientIP string + }{ + { + name: "with a client IP from the network", + wantClientIP: "1.2.3.4", + }, + { + name: "with a client IP from x-forwarded-for header", + requestModifierFn: func(r *http.Request) { + r.Header.Add("X-Forwarded-For", "5.6.7.8") + }, + wantClientIP: "5.6.7.8", + }, + { + name: "with a cl;ient IP in options", + requestModifierFn: func(r *http.Request) { + r.Header.Add("X-Forwarded-For", "5.6.7.8") + }, + httpServerRequestOpts: HTTPServerRequestOptions{ + HTTPClientIP: "9.8.7.6", + }, + wantClientIP: "9.8.7.6", + }, + } { + t.Run(tt.name, func(t *testing.T) { reqCh := make(chan *http.Request, 1) handler := func(w http.ResponseWriter, r *http.Request) { r.RemoteAddr = "1.2.3.4:5678" @@ -227,29 +249,8 @@ func TestHTTPServerRequest(t *testing.T) { attribute.String("http.target", "/"), }, HTTPServerRequest("", got, tt.httpServerRequestOpts)) - } + }) } - - t.Run("client IP from network", testFn(testParams{ - wantClientIP: "1.2.3.4", - })) - - t.Run("client IP from X-Forwarded-For header", testFn(testParams{ - requestModifierFn: func(r *http.Request) { - r.Header.Add("X-Forwarded-For", "5.6.7.8") - }, - wantClientIP: "5.6.7.8", - })) - - t.Run("set client IP in options", testFn(testParams{ - requestModifierFn: func(r *http.Request) { - r.Header.Add("X-Forwarded-For", "5.6.7.8") - }, - httpServerRequestOpts: HTTPServerRequestOptions{ - HTTPClientIP: "9.8.7.6", - }, - wantClientIP: "9.8.7.6", - })) } func TestHTTPServerRequestMetrics(t *testing.T) { 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 00b396195b9..b58f40d0c56 100644 --- a/instrumentation/net/http/httptrace/otelhttptrace/internal/semconvutil/httpconv_test.go +++ b/instrumentation/net/http/httptrace/otelhttptrace/internal/semconvutil/httpconv_test.go @@ -166,14 +166,36 @@ func TestHTTPClientRequestRequired(t *testing.T) { } func TestHTTPServerRequest(t *testing.T) { - type testParams struct { + for _, tt := range []struct { + name string requestModifierFn func(r *http.Request) httpServerRequestOpts HTTPServerRequestOptions - wantClientIP string - } - testFn := func(tt testParams) func(t *testing.T) { - return func(t *testing.T) { + wantClientIP string + }{ + { + name: "with a client IP from the network", + wantClientIP: "1.2.3.4", + }, + { + name: "with a client IP from x-forwarded-for header", + requestModifierFn: func(r *http.Request) { + r.Header.Add("X-Forwarded-For", "5.6.7.8") + }, + wantClientIP: "5.6.7.8", + }, + { + name: "with a cl;ient IP in options", + requestModifierFn: func(r *http.Request) { + r.Header.Add("X-Forwarded-For", "5.6.7.8") + }, + httpServerRequestOpts: HTTPServerRequestOptions{ + HTTPClientIP: "9.8.7.6", + }, + wantClientIP: "9.8.7.6", + }, + } { + t.Run(tt.name, func(t *testing.T) { reqCh := make(chan *http.Request, 1) handler := func(w http.ResponseWriter, r *http.Request) { r.RemoteAddr = "1.2.3.4:5678" @@ -227,29 +249,8 @@ func TestHTTPServerRequest(t *testing.T) { attribute.String("http.target", "/"), }, HTTPServerRequest("", got, tt.httpServerRequestOpts)) - } + }) } - - t.Run("client IP from network", testFn(testParams{ - wantClientIP: "1.2.3.4", - })) - - t.Run("client IP from X-Forwarded-For header", testFn(testParams{ - requestModifierFn: func(r *http.Request) { - r.Header.Add("X-Forwarded-For", "5.6.7.8") - }, - wantClientIP: "5.6.7.8", - })) - - t.Run("set client IP in options", testFn(testParams{ - requestModifierFn: func(r *http.Request) { - r.Header.Add("X-Forwarded-For", "5.6.7.8") - }, - httpServerRequestOpts: HTTPServerRequestOptions{ - HTTPClientIP: "9.8.7.6", - }, - wantClientIP: "9.8.7.6", - })) } func TestHTTPServerRequestMetrics(t *testing.T) { diff --git a/instrumentation/net/http/otelhttp/internal/semconvutil/httpconv_test.go b/instrumentation/net/http/otelhttp/internal/semconvutil/httpconv_test.go index 00b396195b9..b58f40d0c56 100644 --- a/instrumentation/net/http/otelhttp/internal/semconvutil/httpconv_test.go +++ b/instrumentation/net/http/otelhttp/internal/semconvutil/httpconv_test.go @@ -166,14 +166,36 @@ func TestHTTPClientRequestRequired(t *testing.T) { } func TestHTTPServerRequest(t *testing.T) { - type testParams struct { + for _, tt := range []struct { + name string requestModifierFn func(r *http.Request) httpServerRequestOpts HTTPServerRequestOptions - wantClientIP string - } - testFn := func(tt testParams) func(t *testing.T) { - return func(t *testing.T) { + wantClientIP string + }{ + { + name: "with a client IP from the network", + wantClientIP: "1.2.3.4", + }, + { + name: "with a client IP from x-forwarded-for header", + requestModifierFn: func(r *http.Request) { + r.Header.Add("X-Forwarded-For", "5.6.7.8") + }, + wantClientIP: "5.6.7.8", + }, + { + name: "with a cl;ient IP in options", + requestModifierFn: func(r *http.Request) { + r.Header.Add("X-Forwarded-For", "5.6.7.8") + }, + httpServerRequestOpts: HTTPServerRequestOptions{ + HTTPClientIP: "9.8.7.6", + }, + wantClientIP: "9.8.7.6", + }, + } { + t.Run(tt.name, func(t *testing.T) { reqCh := make(chan *http.Request, 1) handler := func(w http.ResponseWriter, r *http.Request) { r.RemoteAddr = "1.2.3.4:5678" @@ -227,29 +249,8 @@ func TestHTTPServerRequest(t *testing.T) { attribute.String("http.target", "/"), }, HTTPServerRequest("", got, tt.httpServerRequestOpts)) - } + }) } - - t.Run("client IP from network", testFn(testParams{ - wantClientIP: "1.2.3.4", - })) - - t.Run("client IP from X-Forwarded-For header", testFn(testParams{ - requestModifierFn: func(r *http.Request) { - r.Header.Add("X-Forwarded-For", "5.6.7.8") - }, - wantClientIP: "5.6.7.8", - })) - - t.Run("set client IP in options", testFn(testParams{ - requestModifierFn: func(r *http.Request) { - r.Header.Add("X-Forwarded-For", "5.6.7.8") - }, - httpServerRequestOpts: HTTPServerRequestOptions{ - HTTPClientIP: "9.8.7.6", - }, - wantClientIP: "9.8.7.6", - })) } func TestHTTPServerRequestMetrics(t *testing.T) { diff --git a/internal/shared/semconvutil/httpconv_test.go.tmpl b/internal/shared/semconvutil/httpconv_test.go.tmpl index 00b396195b9..b58f40d0c56 100644 --- a/internal/shared/semconvutil/httpconv_test.go.tmpl +++ b/internal/shared/semconvutil/httpconv_test.go.tmpl @@ -166,14 +166,36 @@ func TestHTTPClientRequestRequired(t *testing.T) { } func TestHTTPServerRequest(t *testing.T) { - type testParams struct { + for _, tt := range []struct { + name string requestModifierFn func(r *http.Request) httpServerRequestOpts HTTPServerRequestOptions - wantClientIP string - } - testFn := func(tt testParams) func(t *testing.T) { - return func(t *testing.T) { + wantClientIP string + }{ + { + name: "with a client IP from the network", + wantClientIP: "1.2.3.4", + }, + { + name: "with a client IP from x-forwarded-for header", + requestModifierFn: func(r *http.Request) { + r.Header.Add("X-Forwarded-For", "5.6.7.8") + }, + wantClientIP: "5.6.7.8", + }, + { + name: "with a cl;ient IP in options", + requestModifierFn: func(r *http.Request) { + r.Header.Add("X-Forwarded-For", "5.6.7.8") + }, + httpServerRequestOpts: HTTPServerRequestOptions{ + HTTPClientIP: "9.8.7.6", + }, + wantClientIP: "9.8.7.6", + }, + } { + t.Run(tt.name, func(t *testing.T) { reqCh := make(chan *http.Request, 1) handler := func(w http.ResponseWriter, r *http.Request) { r.RemoteAddr = "1.2.3.4:5678" @@ -227,29 +249,8 @@ func TestHTTPServerRequest(t *testing.T) { attribute.String("http.target", "/"), }, HTTPServerRequest("", got, tt.httpServerRequestOpts)) - } + }) } - - t.Run("client IP from network", testFn(testParams{ - wantClientIP: "1.2.3.4", - })) - - t.Run("client IP from X-Forwarded-For header", testFn(testParams{ - requestModifierFn: func(r *http.Request) { - r.Header.Add("X-Forwarded-For", "5.6.7.8") - }, - wantClientIP: "5.6.7.8", - })) - - t.Run("set client IP in options", testFn(testParams{ - requestModifierFn: func(r *http.Request) { - r.Header.Add("X-Forwarded-For", "5.6.7.8") - }, - httpServerRequestOpts: HTTPServerRequestOptions{ - HTTPClientIP: "9.8.7.6", - }, - wantClientIP: "9.8.7.6", - })) } func TestHTTPServerRequestMetrics(t *testing.T) { From 4b7a39a6fb89f788528ae3de01a577e1d591f287 Mon Sep 17 00:00:00 2001 From: ItalyPaleAle <43508+ItalyPaleAle@users.noreply.github.com> Date: Wed, 18 Sep 2024 08:44:28 -0700 Subject: [PATCH 09/11] Move test to test folder --- .../gin-gonic/gin/otelgin/gintrace_test.go | 94 ------------------- .../github.com/gin-gonic/gin/otelgin/go.mod | 2 - .../github.com/gin-gonic/gin/otelgin/go.sum | 4 - .../gin/otelgin/test/gintrace_test.go | 90 ++++++++++++++++++ 4 files changed, 90 insertions(+), 100 deletions(-) diff --git a/instrumentation/github.com/gin-gonic/gin/otelgin/gintrace_test.go b/instrumentation/github.com/gin-gonic/gin/otelgin/gintrace_test.go index c1f4e91a765..ac690ede1a5 100644 --- a/instrumentation/github.com/gin-gonic/gin/otelgin/gintrace_test.go +++ b/instrumentation/github.com/gin-gonic/gin/otelgin/gintrace_test.go @@ -10,17 +10,12 @@ import ( "net/http" "net/http/httptest" "testing" - "time" "github.com/gin-gonic/gin" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" "go.opentelemetry.io/otel" - "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/propagation" - sdktrace "go.opentelemetry.io/otel/sdk/trace" - "go.opentelemetry.io/otel/sdk/trace/tracetest" "go.opentelemetry.io/otel/trace" "go.opentelemetry.io/otel/trace/noop" @@ -101,92 +96,3 @@ func TestPropagationWithCustomPropagators(t *testing.T) { router.ServeHTTP(w, r) } - -func TestClientIP(t *testing.T) { - testFn := func(requestFn func(r *http.Request), ginFn func(router *gin.Engine), expect string) func(t *testing.T) { - return func(t *testing.T) { - r := httptest.NewRequest("GET", "/ping", nil) - r.RemoteAddr = "1.2.3.4:5678" - - if requestFn != nil { - requestFn(r) - } - - sr := tracetest.NewSpanRecorder() - provider := sdktrace.NewTracerProvider() - provider.RegisterSpanProcessor(sr) - - doneCh := make(chan struct{}) - router := gin.New() - - if ginFn != nil { - ginFn(router) - } - - router.Use(Middleware("foobar", WithTracerProvider(provider))) - router.GET("/ping", func(c *gin.Context) { - close(doneCh) - }) - - w := httptest.NewRecorder() - router.ServeHTTP(w, r) - response := w.Result() //nolint:bodyclose // False positive for httptest.ResponseRecorder: https://github.com/timakin/bodyclose/issues/59. - assert.Equal(t, http.StatusOK, response.StatusCode) - - select { - case <-doneCh: - // nop - case <-time.After(5 * time.Second): - t.Fatal("did not receive signal in 5s") - } - - res := sr.Ended() - require.Len(t, res, 1) - - got := make(map[attribute.Key]attribute.Value, len(res[0].Attributes())) - for _, a := range res[0].Attributes() { - got[a.Key] = a.Value - } - - require.NotEmpty(t, got["http.client_ip"]) - assert.Equal(t, expect, got["http.client_ip"].AsString()) - } - } - - t.Run("no header", testFn(nil, nil, "1.2.3.4")) - - t.Run("header is not trusted", testFn( - func(r *http.Request) { - r.Header.Set("X-Forwarded-For", "9.8.7.6") - }, - func(router *gin.Engine) { - //nolint:errcheck - router.SetTrustedProxies(nil) - }, - "1.2.3.4", - )) - - t.Run("client IP in X-Forwarded-For header", testFn( - func(r *http.Request) { - r.Header.Set("X-Forwarded-For", "9.8.7.6") - }, - func(router *gin.Engine) { - //nolint:errcheck - router.SetTrustedProxies([]string{"0.0.0.0/0"}) - }, - "9.8.7.6", - )) - - t.Run("client IP in X-Custom-IP", testFn( - func(r *http.Request) { - r.Header.Set("X-Forwarded-For", "2.3.2.3") // not used - r.Header.Set("X-Custom-IP", "9.8.7.6") - }, - func(router *gin.Engine) { - router.RemoteIPHeaders = []string{"X-Custom-IP", "X-Forwarded-For"} - //nolint:errcheck - router.SetTrustedProxies([]string{"0.0.0.0/0"}) - }, - "9.8.7.6", - )) -} diff --git a/instrumentation/github.com/gin-gonic/gin/otelgin/go.mod b/instrumentation/github.com/gin-gonic/gin/otelgin/go.mod index 43da44b0bfc..cef83af4d61 100644 --- a/instrumentation/github.com/gin-gonic/gin/otelgin/go.mod +++ b/instrumentation/github.com/gin-gonic/gin/otelgin/go.mod @@ -9,7 +9,6 @@ require ( github.com/stretchr/testify v1.9.0 go.opentelemetry.io/contrib/propagators/b3 v1.29.0 go.opentelemetry.io/otel v1.29.0 - go.opentelemetry.io/otel/sdk v1.29.0 go.opentelemetry.io/otel/trace v1.29.0 ) @@ -27,7 +26,6 @@ require ( github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.22.1 // indirect github.com/goccy/go-json v0.10.3 // indirect - github.com/google/uuid v1.6.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.2.8 // indirect github.com/leodido/go-urn v1.4.0 // indirect diff --git a/instrumentation/github.com/gin-gonic/gin/otelgin/go.sum b/instrumentation/github.com/gin-gonic/gin/otelgin/go.sum index 92e831264a2..2255f812669 100644 --- a/instrumentation/github.com/gin-gonic/gin/otelgin/go.sum +++ b/instrumentation/github.com/gin-gonic/gin/otelgin/go.sum @@ -34,8 +34,6 @@ github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PU github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= @@ -73,8 +71,6 @@ go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= -go.opentelemetry.io/otel/sdk v1.29.0 h1:vkqKjk7gwhS8VaWb0POZKmIEDimRCMsopNYnriHyryo= -go.opentelemetry.io/otel/sdk v1.29.0/go.mod h1:pM8Dx5WKnvxLCb+8lG1PRNIDxu9g9b9g59Qr7hfAAok= go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= golang.org/x/arch v0.10.0 h1:S3huipmSclq3PJMNe76NGwkBR504WFkQ5dhzWzP8ZW8= diff --git a/instrumentation/github.com/gin-gonic/gin/otelgin/test/gintrace_test.go b/instrumentation/github.com/gin-gonic/gin/otelgin/test/gintrace_test.go index d90ac7ec9c0..a3b5d00f07b 100644 --- a/instrumentation/github.com/gin-gonic/gin/otelgin/test/gintrace_test.go +++ b/instrumentation/github.com/gin-gonic/gin/otelgin/test/gintrace_test.go @@ -12,6 +12,7 @@ import ( "net/http/httptest" "strconv" "testing" + "time" "github.com/gin-gonic/gin" "github.com/stretchr/testify/assert" @@ -321,3 +322,92 @@ func TestWithGinFilter(t *testing.T) { assert.Len(t, sr.Ended(), 1) }) } + +func TestClientIP(t *testing.T) { + testFn := func(requestFn func(r *http.Request), ginFn func(router *gin.Engine), expect string) func(t *testing.T) { + return func(t *testing.T) { + r := httptest.NewRequest("GET", "/ping", nil) + r.RemoteAddr = "1.2.3.4:5678" + + if requestFn != nil { + requestFn(r) + } + + sr := tracetest.NewSpanRecorder() + provider := sdktrace.NewTracerProvider() + provider.RegisterSpanProcessor(sr) + + doneCh := make(chan struct{}) + router := gin.New() + + if ginFn != nil { + ginFn(router) + } + + router.Use(otelgin.Middleware("foobar", otelgin.WithTracerProvider(provider))) + router.GET("/ping", func(c *gin.Context) { + close(doneCh) + }) + + w := httptest.NewRecorder() + router.ServeHTTP(w, r) + response := w.Result() //nolint:bodyclose // False positive for httptest.ResponseRecorder: https://github.com/timakin/bodyclose/issues/59. + assert.Equal(t, http.StatusOK, response.StatusCode) + + select { + case <-doneCh: + // nop + case <-time.After(5 * time.Second): + t.Fatal("did not receive signal in 5s") + } + + res := sr.Ended() + require.Len(t, res, 1) + + got := make(map[attribute.Key]attribute.Value, len(res[0].Attributes())) + for _, a := range res[0].Attributes() { + got[a.Key] = a.Value + } + + require.NotEmpty(t, got["http.client_ip"]) + assert.Equal(t, expect, got["http.client_ip"].AsString()) + } + } + + t.Run("no header", testFn(nil, nil, "1.2.3.4")) + + t.Run("header is not trusted", testFn( + func(r *http.Request) { + r.Header.Set("X-Forwarded-For", "9.8.7.6") + }, + func(router *gin.Engine) { + //nolint:errcheck + router.SetTrustedProxies(nil) + }, + "1.2.3.4", + )) + + t.Run("client IP in X-Forwarded-For header", testFn( + func(r *http.Request) { + r.Header.Set("X-Forwarded-For", "9.8.7.6") + }, + func(router *gin.Engine) { + //nolint:errcheck + router.SetTrustedProxies([]string{"0.0.0.0/0"}) + }, + "9.8.7.6", + )) + + t.Run("client IP in X-Custom-IP", testFn( + func(r *http.Request) { + r.Header.Set("X-Forwarded-For", "2.3.2.3") // not used + r.Header.Set("X-Custom-IP", "9.8.7.6") + }, + func(router *gin.Engine) { + router.RemoteIPHeaders = []string{"X-Custom-IP", "X-Forwarded-For"} + //nolint:errcheck + router.SetTrustedProxies([]string{"0.0.0.0/0"}) + }, + "9.8.7.6", + )) +} From 3a07c4025e4474b0fbf338d4216fe2958906f19b Mon Sep 17 00:00:00 2001 From: ItalyPaleAle <43508+ItalyPaleAle@users.noreply.github.com> Date: Fri, 20 Sep 2024 07:50:55 -0700 Subject: [PATCH 10/11] Fixed changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d3af0d0dc0..db0f816c7c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Fixed - Possible nil dereference panic in `go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace`. (#5965) +- Use Gin's own `ClientIP` method to detect the client's IP, which supports custom proxy headers in `go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin`. (#6095) @@ -30,7 +31,6 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - Superfluous call to `WriteHeader` when flushing after setting a status code in `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp`. (#6074) - Superfluous call to `WriteHeader` when writing the response body after setting a status code in `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp`. (#6055) -- Use Gin's own `ClientIP` method to detect the client's IP, which supports custom proxy headers in `go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin`. (#6095) ## [1.29.0/0.54.0/0.23.0/0.9.0/0.4.0/0.2.0/0.1.0] - 2024-08-23 From 666002ab59986efe90e0f2fbbf745d97fbd8c272 Mon Sep 17 00:00:00 2001 From: "Alessandro (Ale) Segala" <43508+ItalyPaleAle@users.noreply.github.com> Date: Sun, 6 Oct 2024 19:34:45 -0700 Subject: [PATCH 11/11] Update CHANGELOG.md --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 80f81586fa6..5fb721d22f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,7 +24,6 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - Possible nil dereference panic in `go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace`. (#5965) - Use Gin's own `ClientIP` method to detect the client's IP, which supports custom proxy headers in `go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin`. (#6095) -- Non-200 HTTP status codes when retrieving sampling rules in `go.opentelemetry.io/contrib/samplers/aws/xray` now return an error. (#5718) ### Removed