From 99b417a43df64244def189695b69ab54f5c44092 Mon Sep 17 00:00:00 2001 From: Anthony Mirabella Date: Mon, 29 Jun 2020 16:53:51 -0400 Subject: [PATCH] Use trace convention helpers from main otel-go repo (#112) * prepare for release_v0.7.0 * Upgrade metric exporter code to otel-v0.7.0 API * Remove internal trace standard helpers in favor of api/standard from otel-go * fixup go.mod Co-authored-by: Evan Torrie Co-authored-by: Tyler Yahn --- go.mod | 1 - instrumentation/gin-gonic/gin/gintrace.go | 12 +- .../gopkg.in/macaron.v1/macaron.go | 12 +- instrumentation/gorilla/mux/mux.go | 12 +- instrumentation/labstack/echo/echo.go | 12 +- internal/trace/http.go | 276 ------- internal/trace/http_test.go | 777 ------------------ 7 files changed, 24 insertions(+), 1078 deletions(-) delete mode 100644 internal/trace/http.go delete mode 100644 internal/trace/http_test.go diff --git a/go.mod b/go.mod index 97e071b6ae9..a65d57c231b 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,6 @@ module go.opentelemetry.io/contrib go 1.14 require ( - github.com/stretchr/testify v1.6.1 go.opentelemetry.io/otel v0.7.0 google.golang.org/grpc v1.30.0 ) diff --git a/instrumentation/gin-gonic/gin/gintrace.go b/instrumentation/gin-gonic/gin/gintrace.go index ea6ade60dfb..0c3eac25e6a 100644 --- a/instrumentation/gin-gonic/gin/gintrace.go +++ b/instrumentation/gin-gonic/gin/gintrace.go @@ -22,10 +22,10 @@ import ( "github.com/gin-gonic/gin" "google.golang.org/grpc/codes" - "go.opentelemetry.io/contrib/internal/trace" otelglobal "go.opentelemetry.io/otel/api/global" "go.opentelemetry.io/otel/api/kv" otelpropagation "go.opentelemetry.io/otel/api/propagation" + "go.opentelemetry.io/otel/api/standard" oteltrace "go.opentelemetry.io/otel/api/trace" ) @@ -56,9 +56,9 @@ func Middleware(service string, opts ...Option) gin.HandlerFunc { }() ctx := otelpropagation.ExtractHTTP(savedCtx, cfg.Propagators, c.Request.Header) opts := []oteltrace.StartOption{ - oteltrace.WithAttributes(trace.NetAttributesFromHTTPRequest("tcp", c.Request)...), - oteltrace.WithAttributes(trace.EndUserAttributesFromHTTPRequest(c.Request)...), - oteltrace.WithAttributes(trace.HTTPServerAttributesFromHTTPRequest(service, c.FullPath(), c.Request)...), + oteltrace.WithAttributes(standard.NetAttributesFromHTTPRequest("tcp", c.Request)...), + oteltrace.WithAttributes(standard.EndUserAttributesFromHTTPRequest(c.Request)...), + oteltrace.WithAttributes(standard.HTTPServerAttributesFromHTTPRequest(service, c.FullPath(), c.Request)...), oteltrace.WithSpanKind(oteltrace.SpanKindServer), } spanName := c.FullPath() @@ -75,8 +75,8 @@ func Middleware(service string, opts ...Option) gin.HandlerFunc { c.Next() status := c.Writer.Status() - attrs := trace.HTTPAttributesFromHTTPStatusCode(status) - spanStatus, spanMessage := trace.SpanStatusFromHTTPStatusCode(status) + attrs := standard.HTTPAttributesFromHTTPStatusCode(status) + spanStatus, spanMessage := standard.SpanStatusFromHTTPStatusCode(status) span.SetAttributes(attrs...) span.SetStatus(spanStatus, spanMessage) if len(c.Errors) > 0 { diff --git a/instrumentation/gopkg.in/macaron.v1/macaron.go b/instrumentation/gopkg.in/macaron.v1/macaron.go index abb2d172eb9..dab777f6a7d 100644 --- a/instrumentation/gopkg.in/macaron.v1/macaron.go +++ b/instrumentation/gopkg.in/macaron.v1/macaron.go @@ -20,9 +20,9 @@ import ( "gopkg.in/macaron.v1" - "go.opentelemetry.io/contrib/internal/trace" otelglobal "go.opentelemetry.io/otel/api/global" otelpropagation "go.opentelemetry.io/otel/api/propagation" + "go.opentelemetry.io/otel/api/standard" oteltrace "go.opentelemetry.io/otel/api/trace" ) @@ -50,9 +50,9 @@ func Middleware(service string, opts ...Option) macaron.Handler { ctx := otelpropagation.ExtractHTTP(savedCtx, cfg.Propagators, c.Req.Header) opts := []oteltrace.StartOption{ - oteltrace.WithAttributes(trace.NetAttributesFromHTTPRequest("tcp", c.Req.Request)...), - oteltrace.WithAttributes(trace.EndUserAttributesFromHTTPRequest(c.Req.Request)...), - oteltrace.WithAttributes(trace.HTTPServerAttributesFromHTTPRequest(service, "", c.Req.Request)...), + oteltrace.WithAttributes(standard.NetAttributesFromHTTPRequest("tcp", c.Req.Request)...), + oteltrace.WithAttributes(standard.EndUserAttributesFromHTTPRequest(c.Req.Request)...), + oteltrace.WithAttributes(standard.HTTPServerAttributesFromHTTPRequest(service, "", c.Req.Request)...), oteltrace.WithSpanKind(oteltrace.SpanKindServer), } // TODO: span name should be router template not the actual request path, eg /user/:id vs /user/123 @@ -70,8 +70,8 @@ func Middleware(service string, opts ...Option) macaron.Handler { c.Next() status := c.Resp.Status() - attrs := trace.HTTPAttributesFromHTTPStatusCode(status) - spanStatus, spanMessage := trace.SpanStatusFromHTTPStatusCode(status) + attrs := standard.HTTPAttributesFromHTTPStatusCode(status) + spanStatus, spanMessage := standard.SpanStatusFromHTTPStatusCode(status) span.SetAttributes(attrs...) span.SetStatus(spanStatus, spanMessage) } diff --git a/instrumentation/gorilla/mux/mux.go b/instrumentation/gorilla/mux/mux.go index 83f5223e690..faafb0dc0b4 100644 --- a/instrumentation/gorilla/mux/mux.go +++ b/instrumentation/gorilla/mux/mux.go @@ -21,9 +21,9 @@ import ( "github.com/gorilla/mux" - "go.opentelemetry.io/contrib/internal/trace" otelglobal "go.opentelemetry.io/otel/api/global" otelpropagation "go.opentelemetry.io/otel/api/propagation" + "go.opentelemetry.io/otel/api/standard" oteltrace "go.opentelemetry.io/otel/api/trace" ) @@ -129,9 +129,9 @@ func (tw traceware) ServeHTTP(w http.ResponseWriter, r *http.Request) { spanName = fmt.Sprintf("HTTP %s route not found", r.Method) } opts := []oteltrace.StartOption{ - oteltrace.WithAttributes(trace.NetAttributesFromHTTPRequest("tcp", r)...), - oteltrace.WithAttributes(trace.EndUserAttributesFromHTTPRequest(r)...), - oteltrace.WithAttributes(trace.HTTPServerAttributesFromHTTPRequest(tw.service, routeStr, r)...), + oteltrace.WithAttributes(standard.NetAttributesFromHTTPRequest("tcp", r)...), + oteltrace.WithAttributes(standard.EndUserAttributesFromHTTPRequest(r)...), + oteltrace.WithAttributes(standard.HTTPServerAttributesFromHTTPRequest(tw.service, routeStr, r)...), oteltrace.WithSpanKind(oteltrace.SpanKindServer), } ctx, span := tw.tracer.Start(ctx, spanName, opts...) @@ -140,8 +140,8 @@ func (tw traceware) ServeHTTP(w http.ResponseWriter, r *http.Request) { rrw := getRRW(w) defer putRRW(rrw) tw.handler.ServeHTTP(rrw, r2) - attrs := trace.HTTPAttributesFromHTTPStatusCode(rrw.status) - spanStatus, spanMessage := trace.SpanStatusFromHTTPStatusCode(rrw.status) + attrs := standard.HTTPAttributesFromHTTPStatusCode(rrw.status) + spanStatus, spanMessage := standard.SpanStatusFromHTTPStatusCode(rrw.status) span.SetAttributes(attrs...) span.SetStatus(spanStatus, spanMessage) } diff --git a/instrumentation/labstack/echo/echo.go b/instrumentation/labstack/echo/echo.go index a8a1f6288fb..c3dbd4989c9 100644 --- a/instrumentation/labstack/echo/echo.go +++ b/instrumentation/labstack/echo/echo.go @@ -19,10 +19,10 @@ import ( "github.com/labstack/echo/v4" - "go.opentelemetry.io/contrib/internal/trace" otelglobal "go.opentelemetry.io/otel/api/global" "go.opentelemetry.io/otel/api/kv" otelpropagation "go.opentelemetry.io/otel/api/propagation" + "go.opentelemetry.io/otel/api/standard" oteltrace "go.opentelemetry.io/otel/api/trace" ) @@ -54,9 +54,9 @@ func Middleware(service string, opts ...Option) echo.MiddlewareFunc { }() ctx := otelpropagation.ExtractHTTP(savedCtx, cfg.Propagators, request.Header) opts := []oteltrace.StartOption{ - oteltrace.WithAttributes(trace.NetAttributesFromHTTPRequest("tcp", request)...), - oteltrace.WithAttributes(trace.EndUserAttributesFromHTTPRequest(request)...), - oteltrace.WithAttributes(trace.HTTPServerAttributesFromHTTPRequest(service, c.Path(), request)...), + oteltrace.WithAttributes(standard.NetAttributesFromHTTPRequest("tcp", request)...), + oteltrace.WithAttributes(standard.EndUserAttributesFromHTTPRequest(request)...), + oteltrace.WithAttributes(standard.HTTPServerAttributesFromHTTPRequest(service, c.Path(), request)...), oteltrace.WithSpanKind(oteltrace.SpanKindServer), } spanName := c.Path() @@ -78,8 +78,8 @@ func Middleware(service string, opts ...Option) echo.MiddlewareFunc { c.Error(err) } - attrs := trace.HTTPAttributesFromHTTPStatusCode(c.Response().Status) - spanStatus, spanMessage := trace.SpanStatusFromHTTPStatusCode(c.Response().Status) + attrs := standard.HTTPAttributesFromHTTPStatusCode(c.Response().Status) + spanStatus, spanMessage := standard.SpanStatusFromHTTPStatusCode(c.Response().Status) span.SetAttributes(attrs...) span.SetStatus(spanStatus, spanMessage) diff --git a/internal/trace/http.go b/internal/trace/http.go deleted file mode 100644 index bbf327821e5..00000000000 --- a/internal/trace/http.go +++ /dev/null @@ -1,276 +0,0 @@ -// Copyright The OpenTelemetry Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package trace - -import ( - "fmt" - "net" - "net/http" - "strconv" - "strings" - - "google.golang.org/grpc/codes" - - otelkv "go.opentelemetry.io/otel/api/kv" -) - -// NetAttributesFromHTTPRequest generates attributes of the net -// namespace as specified by the OpenTelemetry specification for a -// span. The network parameter is a string that net.Dial function -// from standard library can understand. -func NetAttributesFromHTTPRequest(network string, request *http.Request) []otelkv.KeyValue { - transport := "" - switch network { - case "tcp", "tcp4", "tcp6": - transport = "IP.TCP" - case "udp", "udp4", "udp6": - transport = "IP.UDP" - case "ip", "ip4", "ip6": - transport = "IP" - case "unix", "unixgram", "unixpacket": - transport = "Unix" - default: - transport = "other" - } - attrs := []otelkv.KeyValue{ - otelkv.String("net.transport", transport), - } - - peerName, peerIP, peerPort := "", "", 0 - { - hostPart := request.RemoteAddr - portPart := "" - if idx := strings.LastIndex(hostPart, ":"); idx >= 0 { - hostPart = request.RemoteAddr[:idx] - portPart = request.RemoteAddr[idx+1:] - } - if hostPart != "" { - if ip := net.ParseIP(hostPart); ip != nil { - peerIP = ip.String() - } else { - peerName = hostPart - } - - if portPart != "" { - numPort, err := strconv.ParseUint(portPart, 10, 16) - if err == nil { - peerPort = (int)(numPort) - } else { - peerName, peerIP = "", "" - } - } - } - } - if peerName != "" { - attrs = append(attrs, otelkv.String("net.peer.name", peerName)) - } - if peerIP != "" { - attrs = append(attrs, otelkv.String("net.peer.ip", peerIP)) - } - if peerPort != 0 { - attrs = append(attrs, otelkv.Int("net.peer.port", peerPort)) - } - hostIP, hostName, hostPort := "", "", 0 - for _, someHost := range []string{request.Host, request.Header.Get("Host"), request.URL.Host} { - hostPart := "" - if idx := strings.LastIndex(someHost, ":"); idx >= 0 { - strPort := someHost[idx+1:] - numPort, err := strconv.ParseUint(strPort, 10, 16) - if err == nil { - hostPort = (int)(numPort) - } - hostPart = someHost[:idx] - } else { - hostPart = someHost - } - if hostPart != "" { - ip := net.ParseIP(hostPart) - if ip != nil { - hostIP = ip.String() - } else { - hostName = hostPart - } - break - } else { - hostPort = 0 - } - } - if hostIP != "" { - attrs = append(attrs, otelkv.String("net.host.ip", hostIP)) - } - if hostName != "" { - attrs = append(attrs, otelkv.String("net.host.name", hostName)) - } - if hostPort != 0 { - attrs = append(attrs, otelkv.Int("net.host.port", hostPort)) - } - return attrs -} - -// EndUserAttributesFromHTTPRequest generates attributes of the -// enduser namespace as specified by the OpenTelemetry specification -// for a span. -func EndUserAttributesFromHTTPRequest(request *http.Request) []otelkv.KeyValue { - if username, _, ok := request.BasicAuth(); ok { - return []otelkv.KeyValue{otelkv.String("enduser.id", username)} - } - return nil -} - -// HTTPServerAttributesFromHTTPRequest generates attributes of the -// http namespace as specified by the OpenTelemetry specification for -// a span on the server side. Currently, only basic authentication is -// supported. -func HTTPServerAttributesFromHTTPRequest(serverName, route string, request *http.Request) []otelkv.KeyValue { - attrs := []otelkv.KeyValue{ - otelkv.String("http.method", request.Method), - otelkv.String("http.target", request.RequestURI), - } - if serverName != "" { - attrs = append(attrs, otelkv.String("http.server_name", serverName)) - } - scheme := "" - if request.TLS != nil { - scheme = "https" - } else { - scheme = "http" - } - attrs = append(attrs, otelkv.String("http.scheme", scheme)) - if route != "" { - attrs = append(attrs, otelkv.String("http.route", route)) - } - if request.Host != "" { - attrs = append(attrs, otelkv.String("http.host", request.Host)) - } - if ua := request.UserAgent(); ua != "" { - attrs = append(attrs, otelkv.String("http.user_agent", ua)) - } - if values, ok := request.Header["X-Forwarded-For"]; ok && len(values) > 0 { - attrs = append(attrs, otelkv.String("http.client_ip", values[0])) - } - flavor := "" - if request.ProtoMajor == 1 { - flavor = fmt.Sprintf("1.%d", request.ProtoMinor) - } else if request.ProtoMajor == 2 { - flavor = "2" - } - if flavor != "" { - attrs = append(attrs, otelkv.String("http.flavor", flavor)) - } - return attrs -} - -// HTTPAttributesFromHTTPStatusCode generates attributes of the http -// namespace as specified by the OpenTelemetry specification for a -// span. -func HTTPAttributesFromHTTPStatusCode(code int) []otelkv.KeyValue { - attrs := []otelkv.KeyValue{ - otelkv.Int("http.status_code", code), - } - text := http.StatusText(code) - if text != "" { - attrs = append(attrs, otelkv.String("http.status_text", text)) - } - return attrs -} - -type codeRange struct { - fromInclusive int - toInclusive int -} - -func (r codeRange) contains(code int) bool { - return r.fromInclusive <= code && code <= r.toInclusive -} - -var validRangesPerCategory = map[int][]codeRange{ - 1: { - {http.StatusContinue, http.StatusEarlyHints}, - }, - 2: { - {http.StatusOK, http.StatusAlreadyReported}, - {http.StatusIMUsed, http.StatusIMUsed}, - }, - 3: { - {http.StatusMultipleChoices, http.StatusUseProxy}, - {http.StatusTemporaryRedirect, http.StatusPermanentRedirect}, - }, - 4: { - {http.StatusBadRequest, http.StatusTeapot}, // yes, teapot is so useful… - {http.StatusMisdirectedRequest, http.StatusUpgradeRequired}, - {http.StatusPreconditionRequired, http.StatusTooManyRequests}, - {http.StatusRequestHeaderFieldsTooLarge, http.StatusRequestHeaderFieldsTooLarge}, - {http.StatusUnavailableForLegalReasons, http.StatusUnavailableForLegalReasons}, - }, - 5: { - {http.StatusInternalServerError, http.StatusLoopDetected}, - {http.StatusNotExtended, http.StatusNetworkAuthenticationRequired}, - }, -} - -// SpanStatusFromHTTPStatusCode generates a status code and a message -// as specified by the OpenTelemetry specification for a span. -func SpanStatusFromHTTPStatusCode(code int) (codes.Code, string) { - spanCode := func() codes.Code { - category := code / 100 - ranges, ok := validRangesPerCategory[category] - if !ok { - return codes.Unknown - } - ok = false - for _, crange := range ranges { - ok = crange.contains(code) - if ok { - break - } - } - if !ok { - return codes.Unknown - } - switch code { - case http.StatusUnauthorized: - return codes.Unauthenticated - case http.StatusForbidden: - return codes.PermissionDenied - case http.StatusNotFound: - return codes.NotFound - case http.StatusTooManyRequests: - return codes.ResourceExhausted - case http.StatusNotImplemented: - return codes.Unimplemented - case http.StatusServiceUnavailable: - return codes.Unavailable - case http.StatusGatewayTimeout: - return codes.DeadlineExceeded - } - if category > 0 && category < 4 { - return codes.OK - } - if category == 4 { - return codes.InvalidArgument - } - if category == 5 { - return codes.Internal - } - // this really should not happen, if we get there then - // it means that the code got out of sync with - // validRangesPerCategory map - return codes.Unknown - }() - if spanCode == codes.Unknown { - return spanCode, fmt.Sprintf("Invalid HTTP status code %d", code) - } - return spanCode, fmt.Sprintf("HTTP status code: %d", code) -} diff --git a/internal/trace/http_test.go b/internal/trace/http_test.go deleted file mode 100644 index c10d38b39c3..00000000000 --- a/internal/trace/http_test.go +++ /dev/null @@ -1,777 +0,0 @@ -// Copyright The OpenTelemetry Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package trace - -import ( - "crypto/tls" - "net/http" - "net/url" - "strings" - "testing" - - "github.com/stretchr/testify/assert" - "google.golang.org/grpc/codes" - - otelkv "go.opentelemetry.io/otel/api/kv" -) - -type tlsOption int - -const ( - noTLS tlsOption = iota - withTLS -) - -func TestNetAttributesFromHTTPRequest(t *testing.T) { - type testcase struct { - name string - - network string - - method string - requestURI string - proto string - remoteAddr string - host string - url *url.URL - header http.Header - - expected []otelkv.KeyValue - } - testcases := []testcase{ - { - name: "stripped, tcp", - network: "tcp", - method: "GET", - requestURI: "/user/123", - proto: "HTTP/1.0", - remoteAddr: "", - host: "", - url: &url.URL{ - Path: "/user/123", - }, - header: nil, - expected: []otelkv.KeyValue{ - otelkv.String("net.transport", "IP.TCP"), - }, - }, - { - name: "stripped, udp", - network: "udp", - method: "GET", - requestURI: "/user/123", - proto: "HTTP/1.0", - remoteAddr: "", - host: "", - url: &url.URL{ - Path: "/user/123", - }, - header: nil, - expected: []otelkv.KeyValue{ - otelkv.String("net.transport", "IP.UDP"), - }, - }, - { - name: "stripped, ip", - network: "ip", - method: "GET", - requestURI: "/user/123", - proto: "HTTP/1.0", - remoteAddr: "", - host: "", - url: &url.URL{ - Path: "/user/123", - }, - header: nil, - expected: []otelkv.KeyValue{ - otelkv.String("net.transport", "IP"), - }, - }, - { - name: "stripped, unix", - network: "unix", - method: "GET", - requestURI: "/user/123", - proto: "HTTP/1.0", - remoteAddr: "", - host: "", - url: &url.URL{ - Path: "/user/123", - }, - header: nil, - expected: []otelkv.KeyValue{ - otelkv.String("net.transport", "Unix"), - }, - }, - { - name: "stripped, other", - network: "nih", - method: "GET", - requestURI: "/user/123", - proto: "HTTP/1.0", - remoteAddr: "", - host: "", - url: &url.URL{ - Path: "/user/123", - }, - header: nil, - expected: []otelkv.KeyValue{ - otelkv.String("net.transport", "other"), - }, - }, - { - name: "with remote ip and port", - network: "tcp", - method: "GET", - requestURI: "/user/123", - proto: "HTTP/1.0", - remoteAddr: "1.2.3.4:56", - host: "", - url: &url.URL{ - Path: "/user/123", - }, - header: nil, - expected: []otelkv.KeyValue{ - otelkv.String("net.transport", "IP.TCP"), - otelkv.String("net.peer.ip", "1.2.3.4"), - otelkv.Int("net.peer.port", 56), - }, - }, - { - name: "with remote name and port", - network: "tcp", - method: "GET", - requestURI: "/user/123", - proto: "HTTP/1.0", - remoteAddr: "example.com:56", - host: "", - url: &url.URL{ - Path: "/user/123", - }, - header: nil, - expected: []otelkv.KeyValue{ - otelkv.String("net.transport", "IP.TCP"), - otelkv.String("net.peer.name", "example.com"), - otelkv.Int("net.peer.port", 56), - }, - }, - { - name: "with remote ip only", - network: "tcp", - method: "GET", - requestURI: "/user/123", - proto: "HTTP/1.0", - remoteAddr: "1.2.3.4", - host: "", - url: &url.URL{ - Path: "/user/123", - }, - header: nil, - expected: []otelkv.KeyValue{ - otelkv.String("net.transport", "IP.TCP"), - otelkv.String("net.peer.ip", "1.2.3.4"), - }, - }, - { - name: "with remote name only", - network: "tcp", - method: "GET", - requestURI: "/user/123", - proto: "HTTP/1.0", - remoteAddr: "example.com", - host: "", - url: &url.URL{ - Path: "/user/123", - }, - header: nil, - expected: []otelkv.KeyValue{ - otelkv.String("net.transport", "IP.TCP"), - otelkv.String("net.peer.name", "example.com"), - }, - }, - { - name: "with remote port only", - network: "tcp", - method: "GET", - requestURI: "/user/123", - proto: "HTTP/1.0", - remoteAddr: ":56", - host: "", - url: &url.URL{ - Path: "/user/123", - }, - header: nil, - expected: []otelkv.KeyValue{ - otelkv.String("net.transport", "IP.TCP"), - }, - }, - { - name: "with host name only", - network: "tcp", - method: "GET", - requestURI: "/user/123", - proto: "HTTP/1.0", - remoteAddr: "1.2.3.4:56", - host: "example.com", - url: &url.URL{ - Path: "/user/123", - }, - header: nil, - expected: []otelkv.KeyValue{ - otelkv.String("net.transport", "IP.TCP"), - otelkv.String("net.peer.ip", "1.2.3.4"), - otelkv.Int("net.peer.port", 56), - otelkv.String("net.host.name", "example.com"), - }, - }, - { - name: "with host ip only", - network: "tcp", - method: "GET", - requestURI: "/user/123", - proto: "HTTP/1.0", - remoteAddr: "1.2.3.4:56", - host: "4.3.2.1", - url: &url.URL{ - Path: "/user/123", - }, - header: nil, - expected: []otelkv.KeyValue{ - otelkv.String("net.transport", "IP.TCP"), - otelkv.String("net.peer.ip", "1.2.3.4"), - otelkv.Int("net.peer.port", 56), - otelkv.String("net.host.ip", "4.3.2.1"), - }, - }, - { - name: "with host name and port", - network: "tcp", - method: "GET", - requestURI: "/user/123", - proto: "HTTP/1.0", - remoteAddr: "1.2.3.4:56", - host: "example.com:78", - url: &url.URL{ - Path: "/user/123", - }, - header: nil, - expected: []otelkv.KeyValue{ - otelkv.String("net.transport", "IP.TCP"), - otelkv.String("net.peer.ip", "1.2.3.4"), - otelkv.Int("net.peer.port", 56), - otelkv.String("net.host.name", "example.com"), - otelkv.Int("net.host.port", 78), - }, - }, - { - name: "with host ip and port", - network: "tcp", - method: "GET", - requestURI: "/user/123", - proto: "HTTP/1.0", - remoteAddr: "1.2.3.4:56", - host: "4.3.2.1:78", - url: &url.URL{ - Path: "/user/123", - }, - header: nil, - expected: []otelkv.KeyValue{ - otelkv.String("net.transport", "IP.TCP"), - otelkv.String("net.peer.ip", "1.2.3.4"), - otelkv.Int("net.peer.port", 56), - otelkv.String("net.host.ip", "4.3.2.1"), - otelkv.Int("net.host.port", 78), - }, - }, - { - name: "with host name and bogus port", - network: "tcp", - method: "GET", - requestURI: "/user/123", - proto: "HTTP/1.0", - remoteAddr: "1.2.3.4:56", - host: "example.com:qwerty", - url: &url.URL{ - Path: "/user/123", - }, - header: nil, - expected: []otelkv.KeyValue{ - otelkv.String("net.transport", "IP.TCP"), - otelkv.String("net.peer.ip", "1.2.3.4"), - otelkv.Int("net.peer.port", 56), - otelkv.String("net.host.name", "example.com"), - }, - }, - { - name: "with host ip and bogus port", - network: "tcp", - method: "GET", - requestURI: "/user/123", - proto: "HTTP/1.0", - remoteAddr: "1.2.3.4:56", - host: "4.3.2.1:qwerty", - url: &url.URL{ - Path: "/user/123", - }, - header: nil, - expected: []otelkv.KeyValue{ - otelkv.String("net.transport", "IP.TCP"), - otelkv.String("net.peer.ip", "1.2.3.4"), - otelkv.Int("net.peer.port", 56), - otelkv.String("net.host.ip", "4.3.2.1"), - }, - }, - { - name: "with empty host and port", - network: "tcp", - method: "GET", - requestURI: "/user/123", - proto: "HTTP/1.0", - remoteAddr: "1.2.3.4:56", - host: ":80", - url: &url.URL{ - Path: "/user/123", - }, - header: nil, - expected: []otelkv.KeyValue{ - otelkv.String("net.transport", "IP.TCP"), - otelkv.String("net.peer.ip", "1.2.3.4"), - otelkv.Int("net.peer.port", 56), - }, - }, - { - name: "with host ip and port in headers", - network: "tcp", - method: "GET", - requestURI: "/user/123", - proto: "HTTP/1.0", - remoteAddr: "1.2.3.4:56", - host: "", - url: &url.URL{ - Path: "/user/123", - }, - header: http.Header{ - "Host": []string{"4.3.2.1:78"}, - }, - expected: []otelkv.KeyValue{ - otelkv.String("net.transport", "IP.TCP"), - otelkv.String("net.peer.ip", "1.2.3.4"), - otelkv.Int("net.peer.port", 56), - otelkv.String("net.host.ip", "4.3.2.1"), - otelkv.Int("net.host.port", 78), - }, - }, - { - name: "with host ip and port in url", - network: "tcp", - method: "GET", - requestURI: "http://4.3.2.1:78/user/123", - proto: "HTTP/1.0", - remoteAddr: "1.2.3.4:56", - host: "", - url: &url.URL{ - Host: "4.3.2.1:78", - Path: "/user/123", - }, - header: nil, - expected: []otelkv.KeyValue{ - otelkv.String("net.transport", "IP.TCP"), - otelkv.String("net.peer.ip", "1.2.3.4"), - otelkv.Int("net.peer.port", 56), - otelkv.String("net.host.ip", "4.3.2.1"), - otelkv.Int("net.host.port", 78), - }, - }, - } - for idx, tc := range testcases { - r := testRequest(tc.method, tc.requestURI, tc.proto, tc.remoteAddr, tc.host, tc.url, tc.header, noTLS) - got := NetAttributesFromHTTPRequest(tc.network, r) - assertElementsMatch(t, tc.expected, got, "testcase %d - %s", idx, tc.name) - } -} - -func TestEndUserAttributesFromHTTPRequest(t *testing.T) { - r := testRequest("GET", "/user/123", "HTTP/1.1", "", "", nil, http.Header{}, withTLS) - var expected []otelkv.KeyValue - got := EndUserAttributesFromHTTPRequest(r) - assert.ElementsMatch(t, expected, got) - r.SetBasicAuth("admin", "password") - expected = []otelkv.KeyValue{otelkv.String("enduser.id", "admin")} - got = EndUserAttributesFromHTTPRequest(r) - assert.ElementsMatch(t, expected, got) -} - -func TestHTTPServerAttributesFromHTTPRequest(t *testing.T) { - type testcase struct { - name string - - serverName string - route string - - method string - requestURI string - proto string - remoteAddr string - host string - url *url.URL - header http.Header - tls tlsOption - - expected []otelkv.KeyValue - } - testcases := []testcase{ - { - name: "stripped", - serverName: "", - route: "", - method: "GET", - requestURI: "/user/123", - proto: "HTTP/1.0", - remoteAddr: "", - host: "", - url: &url.URL{ - Path: "/user/123", - }, - header: nil, - tls: noTLS, - expected: []otelkv.KeyValue{ - otelkv.String("http.method", "GET"), - otelkv.String("http.target", "/user/123"), - otelkv.String("http.scheme", "http"), - otelkv.String("http.flavor", "1.0"), - }, - }, - { - name: "with server name", - serverName: "my-server-name", - route: "", - method: "GET", - requestURI: "/user/123", - proto: "HTTP/1.0", - remoteAddr: "", - host: "", - url: &url.URL{ - Path: "/user/123", - }, - header: nil, - tls: noTLS, - expected: []otelkv.KeyValue{ - otelkv.String("http.method", "GET"), - otelkv.String("http.target", "/user/123"), - otelkv.String("http.scheme", "http"), - otelkv.String("http.flavor", "1.0"), - otelkv.String("http.server_name", "my-server-name"), - }, - }, - { - name: "with tls", - serverName: "my-server-name", - route: "", - method: "GET", - requestURI: "/user/123", - proto: "HTTP/1.0", - remoteAddr: "", - host: "", - url: &url.URL{ - Path: "/user/123", - }, - header: nil, - tls: withTLS, - expected: []otelkv.KeyValue{ - otelkv.String("http.method", "GET"), - otelkv.String("http.target", "/user/123"), - otelkv.String("http.scheme", "https"), - otelkv.String("http.flavor", "1.0"), - otelkv.String("http.server_name", "my-server-name"), - }, - }, - { - name: "with route", - serverName: "my-server-name", - route: "/user/:id", - method: "GET", - requestURI: "/user/123", - proto: "HTTP/1.0", - remoteAddr: "", - host: "", - url: &url.URL{ - Path: "/user/123", - }, - header: nil, - tls: withTLS, - expected: []otelkv.KeyValue{ - otelkv.String("http.method", "GET"), - otelkv.String("http.target", "/user/123"), - otelkv.String("http.scheme", "https"), - otelkv.String("http.flavor", "1.0"), - otelkv.String("http.server_name", "my-server-name"), - otelkv.String("http.route", "/user/:id"), - }, - }, - { - name: "with host", - serverName: "my-server-name", - route: "/user/:id", - method: "GET", - requestURI: "/user/123", - proto: "HTTP/1.0", - remoteAddr: "", - host: "example.com", - url: &url.URL{ - Path: "/user/123", - }, - header: nil, - tls: withTLS, - expected: []otelkv.KeyValue{ - otelkv.String("http.method", "GET"), - otelkv.String("http.target", "/user/123"), - otelkv.String("http.scheme", "https"), - otelkv.String("http.flavor", "1.0"), - otelkv.String("http.server_name", "my-server-name"), - otelkv.String("http.route", "/user/:id"), - otelkv.String("http.host", "example.com"), - }, - }, - { - name: "with user agent", - serverName: "my-server-name", - route: "/user/:id", - method: "GET", - requestURI: "/user/123", - proto: "HTTP/1.0", - remoteAddr: "", - host: "example.com", - url: &url.URL{ - Path: "/user/123", - }, - header: http.Header{ - "User-Agent": []string{"foodownloader"}, - }, - tls: withTLS, - expected: []otelkv.KeyValue{ - otelkv.String("http.method", "GET"), - otelkv.String("http.target", "/user/123"), - otelkv.String("http.scheme", "https"), - otelkv.String("http.flavor", "1.0"), - otelkv.String("http.server_name", "my-server-name"), - otelkv.String("http.route", "/user/:id"), - otelkv.String("http.host", "example.com"), - otelkv.String("http.user_agent", "foodownloader"), - }, - }, - { - name: "with proxy info", - serverName: "my-server-name", - route: "/user/:id", - method: "GET", - requestURI: "/user/123", - proto: "HTTP/1.0", - remoteAddr: "", - host: "example.com", - url: &url.URL{ - Path: "/user/123", - }, - header: http.Header{ - "User-Agent": []string{"foodownloader"}, - "X-Forwarded-For": []string{"1.2.3.4"}, - }, - tls: withTLS, - expected: []otelkv.KeyValue{ - otelkv.String("http.method", "GET"), - otelkv.String("http.target", "/user/123"), - otelkv.String("http.scheme", "https"), - otelkv.String("http.flavor", "1.0"), - otelkv.String("http.server_name", "my-server-name"), - otelkv.String("http.route", "/user/:id"), - otelkv.String("http.host", "example.com"), - otelkv.String("http.user_agent", "foodownloader"), - otelkv.String("http.client_ip", "1.2.3.4"), - }, - }, - { - name: "with http 1.1", - serverName: "my-server-name", - route: "/user/:id", - method: "GET", - requestURI: "/user/123", - proto: "HTTP/1.1", - remoteAddr: "", - host: "example.com", - url: &url.URL{ - Path: "/user/123", - }, - header: http.Header{ - "User-Agent": []string{"foodownloader"}, - "X-Forwarded-For": []string{"1.2.3.4"}, - }, - tls: withTLS, - expected: []otelkv.KeyValue{ - otelkv.String("http.method", "GET"), - otelkv.String("http.target", "/user/123"), - otelkv.String("http.scheme", "https"), - otelkv.String("http.flavor", "1.1"), - otelkv.String("http.server_name", "my-server-name"), - otelkv.String("http.route", "/user/:id"), - otelkv.String("http.host", "example.com"), - otelkv.String("http.user_agent", "foodownloader"), - otelkv.String("http.client_ip", "1.2.3.4"), - }, - }, - { - name: "with http 2", - serverName: "my-server-name", - route: "/user/:id", - method: "GET", - requestURI: "/user/123", - proto: "HTTP/2.0", - remoteAddr: "", - host: "example.com", - url: &url.URL{ - Path: "/user/123", - }, - header: http.Header{ - "User-Agent": []string{"foodownloader"}, - "X-Forwarded-For": []string{"1.2.3.4"}, - }, - tls: withTLS, - expected: []otelkv.KeyValue{ - otelkv.String("http.method", "GET"), - otelkv.String("http.target", "/user/123"), - otelkv.String("http.scheme", "https"), - otelkv.String("http.flavor", "2"), - otelkv.String("http.server_name", "my-server-name"), - otelkv.String("http.route", "/user/:id"), - otelkv.String("http.host", "example.com"), - otelkv.String("http.user_agent", "foodownloader"), - otelkv.String("http.client_ip", "1.2.3.4"), - }, - }, - } - for idx, tc := range testcases { - r := testRequest(tc.method, tc.requestURI, tc.proto, tc.remoteAddr, tc.host, tc.url, tc.header, tc.tls) - got := HTTPServerAttributesFromHTTPRequest(tc.serverName, tc.route, r) - assertElementsMatch(t, tc.expected, got, "testcase %d - %s", idx, tc.name) - } -} - -func TestHTTPAttributesFromHTTPStatusCode(t *testing.T) { - expected := []otelkv.KeyValue{ - otelkv.Int("http.status_code", 404), - otelkv.String("http.status_text", "Not Found"), - } - got := HTTPAttributesFromHTTPStatusCode(http.StatusNotFound) - assertElementsMatch(t, expected, got, "with valid HTTP status code") - assert.ElementsMatch(t, expected, got) - expected = []otelkv.KeyValue{ - otelkv.Int("http.status_code", 499), - } - got = HTTPAttributesFromHTTPStatusCode(499) - assertElementsMatch(t, expected, got, "with invalid HTTP status code") -} - -func TestSpanStatusFromHTTPStatusCode(t *testing.T) { - for code := 0; code < 1000; code++ { - expected := getExpectedGRPCCodeForHTTPCode(code) - got, _ := SpanStatusFromHTTPStatusCode(code) - assert.Equalf(t, expected, got, "%s vs %s", expected, got) - } -} - -func getExpectedGRPCCodeForHTTPCode(code int) codes.Code { - if http.StatusText(code) == "" { - return codes.Unknown - } - switch code { - case http.StatusUnauthorized: - return codes.Unauthenticated - case http.StatusForbidden: - return codes.PermissionDenied - case http.StatusNotFound: - return codes.NotFound - case http.StatusTooManyRequests: - return codes.ResourceExhausted - case http.StatusNotImplemented: - return codes.Unimplemented - case http.StatusServiceUnavailable: - return codes.Unavailable - case http.StatusGatewayTimeout: - return codes.DeadlineExceeded - } - category := code / 100 - if category < 4 { - return codes.OK - } - if category < 5 { - return codes.InvalidArgument - } - return codes.Internal -} - -func assertElementsMatch(t *testing.T, expected, got []otelkv.KeyValue, format string, args ...interface{}) { - if !assert.ElementsMatchf(t, expected, got, format, args...) { - t.Log("expected:", kvStr(expected)) - t.Log("got:", kvStr(got)) - } -} - -func testRequest(method, requestURI, proto, remoteAddr, host string, u *url.URL, header http.Header, tlsopt tlsOption) *http.Request { - major, minor := protoToInts(proto) - var tlsConn *tls.ConnectionState - switch tlsopt { - case noTLS: - case withTLS: - tlsConn = &tls.ConnectionState{} - } - return &http.Request{ - Method: method, - URL: u, - Proto: proto, - ProtoMajor: major, - ProtoMinor: minor, - Header: header, - Host: host, - RemoteAddr: remoteAddr, - RequestURI: requestURI, - TLS: tlsConn, - } -} - -func protoToInts(proto string) (int, int) { - switch proto { - case "HTTP/1.0": - return 1, 0 - case "HTTP/1.1": - return 1, 1 - case "HTTP/2.0": - return 2, 0 - } - // invalid proto - return 13, 42 -} - -func kvStr(kvs []otelkv.KeyValue) string { - sb := strings.Builder{} - sb.WriteRune('[') - for idx, kv := range kvs { - if idx > 0 { - sb.WriteString(", ") - } - sb.WriteString((string)(kv.Key)) - sb.WriteString(": ") - sb.WriteString(kv.Value.Emit()) - } - sb.WriteRune(']') - return sb.String() -}