summaryrefslogtreecommitdiff
path: root/vendor/go.opentelemetry.io/otel/semconv/internal/http.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/go.opentelemetry.io/otel/semconv/internal/http.go')
-rw-r--r--vendor/go.opentelemetry.io/otel/semconv/internal/http.go336
1 files changed, 336 insertions, 0 deletions
diff --git a/vendor/go.opentelemetry.io/otel/semconv/internal/http.go b/vendor/go.opentelemetry.io/otel/semconv/internal/http.go
new file mode 100644
index 000000000..b580eedef
--- /dev/null
+++ b/vendor/go.opentelemetry.io/otel/semconv/internal/http.go
@@ -0,0 +1,336 @@
+// 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 internal // import "go.opentelemetry.io/otel/semconv/internal"
+
+import (
+ "fmt"
+ "net"
+ "net/http"
+ "strconv"
+ "strings"
+
+ "go.opentelemetry.io/otel/attribute"
+ "go.opentelemetry.io/otel/codes"
+ "go.opentelemetry.io/otel/trace"
+)
+
+// SemanticConventions are the semantic convention values defined for a
+// version of the OpenTelemetry specification.
+type SemanticConventions struct {
+ EnduserIDKey attribute.Key
+ HTTPClientIPKey attribute.Key
+ HTTPFlavorKey attribute.Key
+ HTTPHostKey attribute.Key
+ HTTPMethodKey attribute.Key
+ HTTPRequestContentLengthKey attribute.Key
+ HTTPRouteKey attribute.Key
+ HTTPSchemeHTTP attribute.KeyValue
+ HTTPSchemeHTTPS attribute.KeyValue
+ HTTPServerNameKey attribute.Key
+ HTTPStatusCodeKey attribute.Key
+ HTTPTargetKey attribute.Key
+ HTTPURLKey attribute.Key
+ HTTPUserAgentKey attribute.Key
+ NetHostIPKey attribute.Key
+ NetHostNameKey attribute.Key
+ NetHostPortKey attribute.Key
+ NetPeerIPKey attribute.Key
+ NetPeerNameKey attribute.Key
+ NetPeerPortKey attribute.Key
+ NetTransportIP attribute.KeyValue
+ NetTransportOther attribute.KeyValue
+ NetTransportTCP attribute.KeyValue
+ NetTransportUDP attribute.KeyValue
+ NetTransportUnix attribute.KeyValue
+}
+
+// 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 (sc *SemanticConventions) NetAttributesFromHTTPRequest(network string, request *http.Request) []attribute.KeyValue {
+ attrs := []attribute.KeyValue{}
+
+ switch network {
+ case "tcp", "tcp4", "tcp6":
+ attrs = append(attrs, sc.NetTransportTCP)
+ case "udp", "udp4", "udp6":
+ attrs = append(attrs, sc.NetTransportUDP)
+ case "ip", "ip4", "ip6":
+ attrs = append(attrs, sc.NetTransportIP)
+ case "unix", "unixgram", "unixpacket":
+ attrs = append(attrs, sc.NetTransportUnix)
+ default:
+ attrs = append(attrs, sc.NetTransportOther)
+ }
+
+ peerIP, peerName, peerPort := hostIPNamePort(request.RemoteAddr)
+ if peerIP != "" {
+ attrs = append(attrs, sc.NetPeerIPKey.String(peerIP))
+ }
+ if peerName != "" {
+ attrs = append(attrs, sc.NetPeerNameKey.String(peerName))
+ }
+ if peerPort != 0 {
+ attrs = append(attrs, sc.NetPeerPortKey.Int(peerPort))
+ }
+
+ hostIP, hostName, hostPort := "", "", 0
+ for _, someHost := range []string{request.Host, request.Header.Get("Host"), request.URL.Host} {
+ hostIP, hostName, hostPort = hostIPNamePort(someHost)
+ if hostIP != "" || hostName != "" || hostPort != 0 {
+ break
+ }
+ }
+ if hostIP != "" {
+ attrs = append(attrs, sc.NetHostIPKey.String(hostIP))
+ }
+ if hostName != "" {
+ attrs = append(attrs, sc.NetHostNameKey.String(hostName))
+ }
+ if hostPort != 0 {
+ attrs = append(attrs, sc.NetHostPortKey.Int(hostPort))
+ }
+
+ return attrs
+}
+
+// hostIPNamePort extracts the IP address, name and (optional) port from hostWithPort.
+// It handles both IPv4 and IPv6 addresses. If the host portion is not recognized
+// as a valid IPv4 or IPv6 address, the `ip` result will be empty and the
+// host portion will instead be returned in `name`.
+func hostIPNamePort(hostWithPort string) (ip string, name string, port int) {
+ var (
+ hostPart, portPart string
+ parsedPort uint64
+ err error
+ )
+ if hostPart, portPart, err = net.SplitHostPort(hostWithPort); err != nil {
+ hostPart, portPart = hostWithPort, ""
+ }
+ if parsedIP := net.ParseIP(hostPart); parsedIP != nil {
+ ip = parsedIP.String()
+ } else {
+ name = hostPart
+ }
+ if parsedPort, err = strconv.ParseUint(portPart, 10, 16); err == nil {
+ port = int(parsedPort)
+ }
+ return
+}
+
+// EndUserAttributesFromHTTPRequest generates attributes of the
+// enduser namespace as specified by the OpenTelemetry specification
+// for a span.
+func (sc *SemanticConventions) EndUserAttributesFromHTTPRequest(request *http.Request) []attribute.KeyValue {
+ if username, _, ok := request.BasicAuth(); ok {
+ return []attribute.KeyValue{sc.EnduserIDKey.String(username)}
+ }
+ return nil
+}
+
+// HTTPClientAttributesFromHTTPRequest generates attributes of the
+// http namespace as specified by the OpenTelemetry specification for
+// a span on the client side.
+func (sc *SemanticConventions) HTTPClientAttributesFromHTTPRequest(request *http.Request) []attribute.KeyValue {
+ attrs := []attribute.KeyValue{}
+
+ // remove any username/password info that may be in the URL
+ // before adding it to the attributes
+ userinfo := request.URL.User
+ request.URL.User = nil
+
+ attrs = append(attrs, sc.HTTPURLKey.String(request.URL.String()))
+
+ // restore any username/password info that was removed
+ request.URL.User = userinfo
+
+ return append(attrs, sc.httpCommonAttributesFromHTTPRequest(request)...)
+}
+
+func (sc *SemanticConventions) httpCommonAttributesFromHTTPRequest(request *http.Request) []attribute.KeyValue {
+ attrs := []attribute.KeyValue{}
+ if ua := request.UserAgent(); ua != "" {
+ attrs = append(attrs, sc.HTTPUserAgentKey.String(ua))
+ }
+ if request.ContentLength > 0 {
+ attrs = append(attrs, sc.HTTPRequestContentLengthKey.Int64(request.ContentLength))
+ }
+
+ return append(attrs, sc.httpBasicAttributesFromHTTPRequest(request)...)
+}
+
+func (sc *SemanticConventions) httpBasicAttributesFromHTTPRequest(request *http.Request) []attribute.KeyValue {
+ // as these attributes are used by HTTPServerMetricAttributesFromHTTPRequest, they should be low-cardinality
+ attrs := []attribute.KeyValue{}
+
+ if request.TLS != nil {
+ attrs = append(attrs, sc.HTTPSchemeHTTPS)
+ } else {
+ attrs = append(attrs, sc.HTTPSchemeHTTP)
+ }
+
+ if request.Host != "" {
+ attrs = append(attrs, sc.HTTPHostKey.String(request.Host))
+ } else if request.URL != nil && request.URL.Host != "" {
+ attrs = append(attrs, sc.HTTPHostKey.String(request.URL.Host))
+ }
+
+ flavor := ""
+ if request.ProtoMajor == 1 {
+ flavor = fmt.Sprintf("1.%d", request.ProtoMinor)
+ } else if request.ProtoMajor == 2 {
+ flavor = "2"
+ }
+ if flavor != "" {
+ attrs = append(attrs, sc.HTTPFlavorKey.String(flavor))
+ }
+
+ if request.Method != "" {
+ attrs = append(attrs, sc.HTTPMethodKey.String(request.Method))
+ } else {
+ attrs = append(attrs, sc.HTTPMethodKey.String(http.MethodGet))
+ }
+
+ return attrs
+}
+
+// HTTPServerMetricAttributesFromHTTPRequest generates low-cardinality attributes
+// to be used with server-side HTTP metrics.
+func (sc *SemanticConventions) HTTPServerMetricAttributesFromHTTPRequest(serverName string, request *http.Request) []attribute.KeyValue {
+ attrs := []attribute.KeyValue{}
+ if serverName != "" {
+ attrs = append(attrs, sc.HTTPServerNameKey.String(serverName))
+ }
+ return append(attrs, sc.httpBasicAttributesFromHTTPRequest(request)...)
+}
+
+// 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 (sc *SemanticConventions) HTTPServerAttributesFromHTTPRequest(serverName, route string, request *http.Request) []attribute.KeyValue {
+ attrs := []attribute.KeyValue{
+ sc.HTTPTargetKey.String(request.RequestURI),
+ }
+
+ if serverName != "" {
+ attrs = append(attrs, sc.HTTPServerNameKey.String(serverName))
+ }
+ if route != "" {
+ attrs = append(attrs, sc.HTTPRouteKey.String(route))
+ }
+ if values, ok := request.Header["X-Forwarded-For"]; ok && len(values) > 0 {
+ if addresses := strings.SplitN(values[0], ",", 2); len(addresses) > 0 {
+ attrs = append(attrs, sc.HTTPClientIPKey.String(addresses[0]))
+ }
+ }
+
+ return append(attrs, sc.httpCommonAttributesFromHTTPRequest(request)...)
+}
+
+// HTTPAttributesFromHTTPStatusCode generates attributes of the http
+// namespace as specified by the OpenTelemetry specification for a
+// span.
+func (sc *SemanticConventions) HTTPAttributesFromHTTPStatusCode(code int) []attribute.KeyValue {
+ attrs := []attribute.KeyValue{
+ sc.HTTPStatusCodeKey.Int(code),
+ }
+ 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, valid := validateHTTPStatusCode(code)
+ if !valid {
+ return spanCode, fmt.Sprintf("Invalid HTTP status code %d", code)
+ }
+ return spanCode, ""
+}
+
+// SpanStatusFromHTTPStatusCodeAndSpanKind generates a status code and a message
+// as specified by the OpenTelemetry specification for a span.
+// Exclude 4xx for SERVER to set the appropriate status.
+func SpanStatusFromHTTPStatusCodeAndSpanKind(code int, spanKind trace.SpanKind) (codes.Code, string) {
+ spanCode, valid := validateHTTPStatusCode(code)
+ if !valid {
+ return spanCode, fmt.Sprintf("Invalid HTTP status code %d", code)
+ }
+ category := code / 100
+ if spanKind == trace.SpanKindServer && category == 4 {
+ return codes.Unset, ""
+ }
+ return spanCode, ""
+}
+
+// validateHTTPStatusCode validates the HTTP status code and returns
+// corresponding span status code. If the `code` is not a valid HTTP status
+// code, returns span status Error and false.
+func validateHTTPStatusCode(code int) (codes.Code, bool) {
+ category := code / 100
+ ranges, ok := validRangesPerCategory[category]
+ if !ok {
+ return codes.Error, false
+ }
+ ok = false
+ for _, crange := range ranges {
+ ok = crange.contains(code)
+ if ok {
+ break
+ }
+ }
+ if !ok {
+ return codes.Error, false
+ }
+ if category > 0 && category < 4 {
+ return codes.Unset, true
+ }
+ return codes.Error, true
+}