gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

http.go (11312B)


      1 // Copyright The OpenTelemetry Authors
      2 //
      3 // Licensed under the Apache License, Version 2.0 (the "License");
      4 // you may not use this file except in compliance with the License.
      5 // You may obtain a copy of the License at
      6 //
      7 //     http://www.apache.org/licenses/LICENSE-2.0
      8 //
      9 // Unless required by applicable law or agreed to in writing, software
     10 // distributed under the License is distributed on an "AS IS" BASIS,
     11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     12 // See the License for the specific language governing permissions and
     13 // limitations under the License.
     14 
     15 package internal // import "go.opentelemetry.io/otel/semconv/internal"
     16 
     17 import (
     18 	"fmt"
     19 	"net"
     20 	"net/http"
     21 	"strconv"
     22 	"strings"
     23 
     24 	"go.opentelemetry.io/otel/attribute"
     25 	"go.opentelemetry.io/otel/codes"
     26 	"go.opentelemetry.io/otel/trace"
     27 )
     28 
     29 // SemanticConventions are the semantic convention values defined for a
     30 // version of the OpenTelemetry specification.
     31 type SemanticConventions struct {
     32 	EnduserIDKey                attribute.Key
     33 	HTTPClientIPKey             attribute.Key
     34 	HTTPFlavorKey               attribute.Key
     35 	HTTPHostKey                 attribute.Key
     36 	HTTPMethodKey               attribute.Key
     37 	HTTPRequestContentLengthKey attribute.Key
     38 	HTTPRouteKey                attribute.Key
     39 	HTTPSchemeHTTP              attribute.KeyValue
     40 	HTTPSchemeHTTPS             attribute.KeyValue
     41 	HTTPServerNameKey           attribute.Key
     42 	HTTPStatusCodeKey           attribute.Key
     43 	HTTPTargetKey               attribute.Key
     44 	HTTPURLKey                  attribute.Key
     45 	HTTPUserAgentKey            attribute.Key
     46 	NetHostIPKey                attribute.Key
     47 	NetHostNameKey              attribute.Key
     48 	NetHostPortKey              attribute.Key
     49 	NetPeerIPKey                attribute.Key
     50 	NetPeerNameKey              attribute.Key
     51 	NetPeerPortKey              attribute.Key
     52 	NetTransportIP              attribute.KeyValue
     53 	NetTransportOther           attribute.KeyValue
     54 	NetTransportTCP             attribute.KeyValue
     55 	NetTransportUDP             attribute.KeyValue
     56 	NetTransportUnix            attribute.KeyValue
     57 }
     58 
     59 // NetAttributesFromHTTPRequest generates attributes of the net
     60 // namespace as specified by the OpenTelemetry specification for a
     61 // span.  The network parameter is a string that net.Dial function
     62 // from standard library can understand.
     63 func (sc *SemanticConventions) NetAttributesFromHTTPRequest(network string, request *http.Request) []attribute.KeyValue {
     64 	attrs := []attribute.KeyValue{}
     65 
     66 	switch network {
     67 	case "tcp", "tcp4", "tcp6":
     68 		attrs = append(attrs, sc.NetTransportTCP)
     69 	case "udp", "udp4", "udp6":
     70 		attrs = append(attrs, sc.NetTransportUDP)
     71 	case "ip", "ip4", "ip6":
     72 		attrs = append(attrs, sc.NetTransportIP)
     73 	case "unix", "unixgram", "unixpacket":
     74 		attrs = append(attrs, sc.NetTransportUnix)
     75 	default:
     76 		attrs = append(attrs, sc.NetTransportOther)
     77 	}
     78 
     79 	peerIP, peerName, peerPort := hostIPNamePort(request.RemoteAddr)
     80 	if peerIP != "" {
     81 		attrs = append(attrs, sc.NetPeerIPKey.String(peerIP))
     82 	}
     83 	if peerName != "" {
     84 		attrs = append(attrs, sc.NetPeerNameKey.String(peerName))
     85 	}
     86 	if peerPort != 0 {
     87 		attrs = append(attrs, sc.NetPeerPortKey.Int(peerPort))
     88 	}
     89 
     90 	hostIP, hostName, hostPort := "", "", 0
     91 	for _, someHost := range []string{request.Host, request.Header.Get("Host"), request.URL.Host} {
     92 		hostIP, hostName, hostPort = hostIPNamePort(someHost)
     93 		if hostIP != "" || hostName != "" || hostPort != 0 {
     94 			break
     95 		}
     96 	}
     97 	if hostIP != "" {
     98 		attrs = append(attrs, sc.NetHostIPKey.String(hostIP))
     99 	}
    100 	if hostName != "" {
    101 		attrs = append(attrs, sc.NetHostNameKey.String(hostName))
    102 	}
    103 	if hostPort != 0 {
    104 		attrs = append(attrs, sc.NetHostPortKey.Int(hostPort))
    105 	}
    106 
    107 	return attrs
    108 }
    109 
    110 // hostIPNamePort extracts the IP address, name and (optional) port from hostWithPort.
    111 // It handles both IPv4 and IPv6 addresses. If the host portion is not recognized
    112 // as a valid IPv4 or IPv6 address, the `ip` result will be empty and the
    113 // host portion will instead be returned in `name`.
    114 func hostIPNamePort(hostWithPort string) (ip string, name string, port int) {
    115 	var (
    116 		hostPart, portPart string
    117 		parsedPort         uint64
    118 		err                error
    119 	)
    120 	if hostPart, portPart, err = net.SplitHostPort(hostWithPort); err != nil {
    121 		hostPart, portPart = hostWithPort, ""
    122 	}
    123 	if parsedIP := net.ParseIP(hostPart); parsedIP != nil {
    124 		ip = parsedIP.String()
    125 	} else {
    126 		name = hostPart
    127 	}
    128 	if parsedPort, err = strconv.ParseUint(portPart, 10, 16); err == nil {
    129 		port = int(parsedPort)
    130 	}
    131 	return
    132 }
    133 
    134 // EndUserAttributesFromHTTPRequest generates attributes of the
    135 // enduser namespace as specified by the OpenTelemetry specification
    136 // for a span.
    137 func (sc *SemanticConventions) EndUserAttributesFromHTTPRequest(request *http.Request) []attribute.KeyValue {
    138 	if username, _, ok := request.BasicAuth(); ok {
    139 		return []attribute.KeyValue{sc.EnduserIDKey.String(username)}
    140 	}
    141 	return nil
    142 }
    143 
    144 // HTTPClientAttributesFromHTTPRequest generates attributes of the
    145 // http namespace as specified by the OpenTelemetry specification for
    146 // a span on the client side.
    147 func (sc *SemanticConventions) HTTPClientAttributesFromHTTPRequest(request *http.Request) []attribute.KeyValue {
    148 	attrs := []attribute.KeyValue{}
    149 
    150 	// remove any username/password info that may be in the URL
    151 	// before adding it to the attributes
    152 	userinfo := request.URL.User
    153 	request.URL.User = nil
    154 
    155 	attrs = append(attrs, sc.HTTPURLKey.String(request.URL.String()))
    156 
    157 	// restore any username/password info that was removed
    158 	request.URL.User = userinfo
    159 
    160 	return append(attrs, sc.httpCommonAttributesFromHTTPRequest(request)...)
    161 }
    162 
    163 func (sc *SemanticConventions) httpCommonAttributesFromHTTPRequest(request *http.Request) []attribute.KeyValue {
    164 	attrs := []attribute.KeyValue{}
    165 	if ua := request.UserAgent(); ua != "" {
    166 		attrs = append(attrs, sc.HTTPUserAgentKey.String(ua))
    167 	}
    168 	if request.ContentLength > 0 {
    169 		attrs = append(attrs, sc.HTTPRequestContentLengthKey.Int64(request.ContentLength))
    170 	}
    171 
    172 	return append(attrs, sc.httpBasicAttributesFromHTTPRequest(request)...)
    173 }
    174 
    175 func (sc *SemanticConventions) httpBasicAttributesFromHTTPRequest(request *http.Request) []attribute.KeyValue {
    176 	// as these attributes are used by HTTPServerMetricAttributesFromHTTPRequest, they should be low-cardinality
    177 	attrs := []attribute.KeyValue{}
    178 
    179 	if request.TLS != nil {
    180 		attrs = append(attrs, sc.HTTPSchemeHTTPS)
    181 	} else {
    182 		attrs = append(attrs, sc.HTTPSchemeHTTP)
    183 	}
    184 
    185 	if request.Host != "" {
    186 		attrs = append(attrs, sc.HTTPHostKey.String(request.Host))
    187 	} else if request.URL != nil && request.URL.Host != "" {
    188 		attrs = append(attrs, sc.HTTPHostKey.String(request.URL.Host))
    189 	}
    190 
    191 	flavor := ""
    192 	if request.ProtoMajor == 1 {
    193 		flavor = fmt.Sprintf("1.%d", request.ProtoMinor)
    194 	} else if request.ProtoMajor == 2 {
    195 		flavor = "2"
    196 	}
    197 	if flavor != "" {
    198 		attrs = append(attrs, sc.HTTPFlavorKey.String(flavor))
    199 	}
    200 
    201 	if request.Method != "" {
    202 		attrs = append(attrs, sc.HTTPMethodKey.String(request.Method))
    203 	} else {
    204 		attrs = append(attrs, sc.HTTPMethodKey.String(http.MethodGet))
    205 	}
    206 
    207 	return attrs
    208 }
    209 
    210 // HTTPServerMetricAttributesFromHTTPRequest generates low-cardinality attributes
    211 // to be used with server-side HTTP metrics.
    212 func (sc *SemanticConventions) HTTPServerMetricAttributesFromHTTPRequest(serverName string, request *http.Request) []attribute.KeyValue {
    213 	attrs := []attribute.KeyValue{}
    214 	if serverName != "" {
    215 		attrs = append(attrs, sc.HTTPServerNameKey.String(serverName))
    216 	}
    217 	return append(attrs, sc.httpBasicAttributesFromHTTPRequest(request)...)
    218 }
    219 
    220 // HTTPServerAttributesFromHTTPRequest generates attributes of the
    221 // http namespace as specified by the OpenTelemetry specification for
    222 // a span on the server side. Currently, only basic authentication is
    223 // supported.
    224 func (sc *SemanticConventions) HTTPServerAttributesFromHTTPRequest(serverName, route string, request *http.Request) []attribute.KeyValue {
    225 	attrs := []attribute.KeyValue{
    226 		sc.HTTPTargetKey.String(request.RequestURI),
    227 	}
    228 
    229 	if serverName != "" {
    230 		attrs = append(attrs, sc.HTTPServerNameKey.String(serverName))
    231 	}
    232 	if route != "" {
    233 		attrs = append(attrs, sc.HTTPRouteKey.String(route))
    234 	}
    235 	if values := request.Header["X-Forwarded-For"]; len(values) > 0 {
    236 		addr := values[0]
    237 		if i := strings.Index(addr, ","); i > 0 {
    238 			addr = addr[:i]
    239 		}
    240 		attrs = append(attrs, sc.HTTPClientIPKey.String(addr))
    241 	}
    242 
    243 	return append(attrs, sc.httpCommonAttributesFromHTTPRequest(request)...)
    244 }
    245 
    246 // HTTPAttributesFromHTTPStatusCode generates attributes of the http
    247 // namespace as specified by the OpenTelemetry specification for a
    248 // span.
    249 func (sc *SemanticConventions) HTTPAttributesFromHTTPStatusCode(code int) []attribute.KeyValue {
    250 	attrs := []attribute.KeyValue{
    251 		sc.HTTPStatusCodeKey.Int(code),
    252 	}
    253 	return attrs
    254 }
    255 
    256 type codeRange struct {
    257 	fromInclusive int
    258 	toInclusive   int
    259 }
    260 
    261 func (r codeRange) contains(code int) bool {
    262 	return r.fromInclusive <= code && code <= r.toInclusive
    263 }
    264 
    265 var validRangesPerCategory = map[int][]codeRange{
    266 	1: {
    267 		{http.StatusContinue, http.StatusEarlyHints},
    268 	},
    269 	2: {
    270 		{http.StatusOK, http.StatusAlreadyReported},
    271 		{http.StatusIMUsed, http.StatusIMUsed},
    272 	},
    273 	3: {
    274 		{http.StatusMultipleChoices, http.StatusUseProxy},
    275 		{http.StatusTemporaryRedirect, http.StatusPermanentRedirect},
    276 	},
    277 	4: {
    278 		{http.StatusBadRequest, http.StatusTeapot}, // yes, teapot is so useful…
    279 		{http.StatusMisdirectedRequest, http.StatusUpgradeRequired},
    280 		{http.StatusPreconditionRequired, http.StatusTooManyRequests},
    281 		{http.StatusRequestHeaderFieldsTooLarge, http.StatusRequestHeaderFieldsTooLarge},
    282 		{http.StatusUnavailableForLegalReasons, http.StatusUnavailableForLegalReasons},
    283 	},
    284 	5: {
    285 		{http.StatusInternalServerError, http.StatusLoopDetected},
    286 		{http.StatusNotExtended, http.StatusNetworkAuthenticationRequired},
    287 	},
    288 }
    289 
    290 // SpanStatusFromHTTPStatusCode generates a status code and a message
    291 // as specified by the OpenTelemetry specification for a span.
    292 func SpanStatusFromHTTPStatusCode(code int) (codes.Code, string) {
    293 	spanCode, valid := validateHTTPStatusCode(code)
    294 	if !valid {
    295 		return spanCode, fmt.Sprintf("Invalid HTTP status code %d", code)
    296 	}
    297 	return spanCode, ""
    298 }
    299 
    300 // SpanStatusFromHTTPStatusCodeAndSpanKind generates a status code and a message
    301 // as specified by the OpenTelemetry specification for a span.
    302 // Exclude 4xx for SERVER to set the appropriate status.
    303 func SpanStatusFromHTTPStatusCodeAndSpanKind(code int, spanKind trace.SpanKind) (codes.Code, string) {
    304 	spanCode, valid := validateHTTPStatusCode(code)
    305 	if !valid {
    306 		return spanCode, fmt.Sprintf("Invalid HTTP status code %d", code)
    307 	}
    308 	category := code / 100
    309 	if spanKind == trace.SpanKindServer && category == 4 {
    310 		return codes.Unset, ""
    311 	}
    312 	return spanCode, ""
    313 }
    314 
    315 // validateHTTPStatusCode validates the HTTP status code and returns
    316 // corresponding span status code. If the `code` is not a valid HTTP status
    317 // code, returns span status Error and false.
    318 func validateHTTPStatusCode(code int) (codes.Code, bool) {
    319 	category := code / 100
    320 	ranges, ok := validRangesPerCategory[category]
    321 	if !ok {
    322 		return codes.Error, false
    323 	}
    324 	ok = false
    325 	for _, crange := range ranges {
    326 		ok = crange.contains(code)
    327 		if ok {
    328 			break
    329 		}
    330 	}
    331 	if !ok {
    332 		return codes.Error, false
    333 	}
    334 	if category > 0 && category < 4 {
    335 		return codes.Unset, true
    336 	}
    337 	return codes.Error, true
    338 }