You've already forked opentelemetry-go
							
							
				mirror of
				https://github.com/open-telemetry/opentelemetry-go.git
				synced 2025-10-31 00:07:40 +02:00 
			
		
		
		
	* Rename otel/label -> otel/attr Leave the imported name alone, to avoid a large diff and conflicts * Better import comment * Update CHANGELOG.md Co-authored-by: Tyler Yahn <MrAlias@users.noreply.github.com> * otel/attr -> otel/attribute * Missed the changelog entry * Get rid of import renaming * Merge remaining conflicts Co-authored-by: Tyler Yahn <MrAlias@users.noreply.github.com> Co-authored-by: Anthony Mirabella <a9@aneurysm9.com>
		
			
				
	
	
		
			293 lines
		
	
	
		
			8.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			293 lines
		
	
	
		
			8.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // 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 semconv // import "go.opentelemetry.io/otel/semconv"
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"net"
 | |
| 	"net/http"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| 
 | |
| 	"go.opentelemetry.io/otel/attribute"
 | |
| 	"go.opentelemetry.io/otel/codes"
 | |
| )
 | |
| 
 | |
| // 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) []attribute.KeyValue {
 | |
| 	attrs := []attribute.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) []attribute.KeyValue {
 | |
| 	if username, _, ok := request.BasicAuth(); ok {
 | |
| 		return []attribute.KeyValue{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 HTTPClientAttributesFromHTTPRequest(request *http.Request) []attribute.KeyValue {
 | |
| 	attrs := []attribute.KeyValue{}
 | |
| 
 | |
| 	if request.Method != "" {
 | |
| 		attrs = append(attrs, HTTPMethodKey.String(request.Method))
 | |
| 	} else {
 | |
| 		attrs = append(attrs, HTTPMethodKey.String(http.MethodGet))
 | |
| 	}
 | |
| 
 | |
| 	attrs = append(attrs, HTTPURLKey.String(request.URL.String()))
 | |
| 
 | |
| 	return append(attrs, httpCommonAttributesFromHTTPRequest(request)...)
 | |
| }
 | |
| 
 | |
| func httpCommonAttributesFromHTTPRequest(request *http.Request) []attribute.KeyValue {
 | |
| 	attrs := []attribute.KeyValue{}
 | |
| 	if ua := request.UserAgent(); ua != "" {
 | |
| 		attrs = append(attrs, HTTPUserAgentKey.String(ua))
 | |
| 	}
 | |
| 	if request.ContentLength > 0 {
 | |
| 		attrs = append(attrs, HTTPRequestContentLengthKey.Int64(request.ContentLength))
 | |
| 	}
 | |
| 
 | |
| 	return append(attrs, httpBasicAttributesFromHTTPRequest(request)...)
 | |
| }
 | |
| 
 | |
| func 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, HTTPSchemeHTTPS)
 | |
| 	} else {
 | |
| 		attrs = append(attrs, HTTPSchemeHTTP)
 | |
| 	}
 | |
| 
 | |
| 	if request.Host != "" {
 | |
| 		attrs = append(attrs, HTTPHostKey.String(request.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, HTTPFlavorKey.String(flavor))
 | |
| 	}
 | |
| 
 | |
| 	return attrs
 | |
| }
 | |
| 
 | |
| // HTTPServerMetricAttributesFromHTTPRequest generates low-cardinality attributes
 | |
| // to be used with server-side HTTP metrics.
 | |
| func HTTPServerMetricAttributesFromHTTPRequest(serverName string, request *http.Request) []attribute.KeyValue {
 | |
| 	attrs := []attribute.KeyValue{}
 | |
| 	if serverName != "" {
 | |
| 		attrs = append(attrs, HTTPServerNameKey.String(serverName))
 | |
| 	}
 | |
| 	return append(attrs, 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 HTTPServerAttributesFromHTTPRequest(serverName, route string, request *http.Request) []attribute.KeyValue {
 | |
| 	attrs := []attribute.KeyValue{
 | |
| 		HTTPMethodKey.String(request.Method),
 | |
| 		HTTPTargetKey.String(request.RequestURI),
 | |
| 	}
 | |
| 
 | |
| 	if serverName != "" {
 | |
| 		attrs = append(attrs, HTTPServerNameKey.String(serverName))
 | |
| 	}
 | |
| 	if route != "" {
 | |
| 		attrs = append(attrs, HTTPRouteKey.String(route))
 | |
| 	}
 | |
| 	if values, ok := request.Header["X-Forwarded-For"]; ok && len(values) > 0 {
 | |
| 		attrs = append(attrs, HTTPClientIPKey.String(values[0]))
 | |
| 	}
 | |
| 
 | |
| 	return append(attrs, httpCommonAttributesFromHTTPRequest(request)...)
 | |
| }
 | |
| 
 | |
| // HTTPAttributesFromHTTPStatusCode generates attributes of the http
 | |
| // namespace as specified by the OpenTelemetry specification for a
 | |
| // span.
 | |
| func HTTPAttributesFromHTTPStatusCode(code int) []attribute.KeyValue {
 | |
| 	attrs := []attribute.KeyValue{
 | |
| 		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 := func() (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
 | |
| 	}()
 | |
| 	if !valid {
 | |
| 		return spanCode, fmt.Sprintf("Invalid HTTP status code %d", code)
 | |
| 	}
 | |
| 	return spanCode, fmt.Sprintf("HTTP status code: %d", code)
 | |
| }
 |