...

Source file src/go.opentelemetry.io/otel/semconv/internal/http.go

Documentation: go.opentelemetry.io/otel/semconv/internal

     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  }
   339  

View as plain text