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 }