mirror of
https://github.com/open-telemetry/opentelemetry-go.git
synced 2025-01-12 02:28:07 +02:00
Move HTTP request semantic convention extractors from contrib
This commit is contained in:
parent
f8d53694be
commit
90703756d4
277
api/standard/http.go
Normal file
277
api/standard/http.go
Normal file
@ -0,0 +1,277 @@
|
||||
// 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 standard
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"google.golang.org/grpc/codes"
|
||||
|
||||
"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) []kv.KeyValue {
|
||||
attrs := []kv.KeyValue{}
|
||||
|
||||
switch network {
|
||||
case "tcp", "tcp4", "tcp6":
|
||||
attrs = append(attrs, NetTransportTCP)
|
||||
case "udp", "udp4", "udp6":
|
||||
attrs = append(attrs, NetTransportUDP)
|
||||
case "ip", "ip4", "ip6":
|
||||
attrs = append(attrs, NetTransportIP)
|
||||
case "unix", "unixgram", "unixpacket":
|
||||
attrs = append(attrs, NetTransportUnix)
|
||||
default:
|
||||
attrs = append(attrs, NetTransportOther)
|
||||
}
|
||||
|
||||
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, NetPeerNameKey.String(peerName))
|
||||
}
|
||||
if peerIP != "" {
|
||||
attrs = append(attrs, NetPeerIPKey.String(peerIP))
|
||||
}
|
||||
if peerPort != 0 {
|
||||
attrs = append(attrs, NetPeerPortKey.Int(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, NetHostIPKey.String(hostIP))
|
||||
}
|
||||
if hostName != "" {
|
||||
attrs = append(attrs, NetHostNameKey.String(hostName))
|
||||
}
|
||||
if hostPort != 0 {
|
||||
attrs = append(attrs, NetHostPortKey.Int(hostPort))
|
||||
}
|
||||
|
||||
return attrs
|
||||
}
|
||||
|
||||
// EndUserAttributesFromHTTPRequest generates attributes of the
|
||||
// enduser namespace as specified by the OpenTelemetry specification
|
||||
// for a span.
|
||||
func EndUserAttributesFromHTTPRequest(request *http.Request) []kv.KeyValue {
|
||||
if username, _, ok := request.BasicAuth(); ok {
|
||||
return []kv.KeyValue{EnduserIDKey.String(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) []kv.KeyValue {
|
||||
attrs := []kv.KeyValue{
|
||||
HTTPMethodKey.String(request.Method),
|
||||
HTTPTargetKey.String(request.RequestURI),
|
||||
}
|
||||
|
||||
if serverName != "" {
|
||||
attrs = append(attrs, HTTPServerNameKey.String(serverName))
|
||||
}
|
||||
if request.TLS != nil {
|
||||
attrs = append(attrs, HTTPSchemeHTTPS)
|
||||
} else {
|
||||
attrs = append(attrs, HTTPSchemeHTTP)
|
||||
}
|
||||
if route != "" {
|
||||
attrs = append(attrs, HTTPRouteKey.String(route))
|
||||
}
|
||||
if request.Host != "" {
|
||||
attrs = append(attrs, HTTPHostKey.String(request.Host))
|
||||
}
|
||||
if ua := request.UserAgent(); ua != "" {
|
||||
attrs = append(attrs, HTTPUserAgentKey.String(ua))
|
||||
}
|
||||
if values, ok := request.Header["X-Forwarded-For"]; ok && len(values) > 0 {
|
||||
attrs = append(attrs, HTTPClientIPKey.String(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, HTTPFlavorKey.String(flavor))
|
||||
}
|
||||
|
||||
return attrs
|
||||
}
|
||||
|
||||
// HTTPAttributesFromHTTPStatusCode generates attributes of the http
|
||||
// namespace as specified by the OpenTelemetry specification for a
|
||||
// span.
|
||||
func HTTPAttributesFromHTTPStatusCode(code int) []kv.KeyValue {
|
||||
attrs := []kv.KeyValue{
|
||||
HTTPStatusCodeKey.Int(code),
|
||||
}
|
||||
text := http.StatusText(code)
|
||||
if text != "" {
|
||||
attrs = append(attrs, HTTPStatusTextKey.String(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)
|
||||
}
|
777
api/standard/http_test.go
Normal file
777
api/standard/http_test.go
Normal file
@ -0,0 +1,777 @@
|
||||
// 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 standard
|
||||
|
||||
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()
|
||||
}
|
Loading…
Reference in New Issue
Block a user