1 // Code created by gotmpl. DO NOT MODIFY. 2 // source: internal/shared/semconvutil/httpconv.go.tmpl 3 4 // Copyright The OpenTelemetry Authors 5 // SPDX-License-Identifier: Apache-2.0 6 7 package semconvutil // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconvutil" 8 9 import ( 10 "fmt" 11 "net/http" 12 "strings" 13 14 "go.opentelemetry.io/otel/attribute" 15 "go.opentelemetry.io/otel/codes" 16 semconv "go.opentelemetry.io/otel/semconv/v1.20.0" 17 ) 18 19 // HTTPClientResponse returns trace attributes for an HTTP response received by a 20 // client from a server. It will return the following attributes if the related 21 // values are defined in resp: "http.status.code", 22 // "http.response_content_length". 23 // 24 // This does not add all OpenTelemetry required attributes for an HTTP event, 25 // it assumes ClientRequest was used to create the span with a complete set of 26 // attributes. If a complete set of attributes can be generated using the 27 // request contained in resp. For example: 28 // 29 // append(HTTPClientResponse(resp), ClientRequest(resp.Request)...) 30 func HTTPClientResponse(resp *http.Response) []attribute.KeyValue { 31 return hc.ClientResponse(resp) 32 } 33 34 // HTTPClientRequest returns trace attributes for an HTTP request made by a client. 35 // The following attributes are always returned: "http.url", "http.method", 36 // "net.peer.name". The following attributes are returned if the related values 37 // are defined in req: "net.peer.port", "user_agent.original", 38 // "http.request_content_length". 39 func HTTPClientRequest(req *http.Request) []attribute.KeyValue { 40 return hc.ClientRequest(req) 41 } 42 43 // HTTPClientRequestMetrics returns metric attributes for an HTTP request made by a client. 44 // The following attributes are always returned: "http.method", "net.peer.name". 45 // The following attributes are returned if the 46 // related values are defined in req: "net.peer.port". 47 func HTTPClientRequestMetrics(req *http.Request) []attribute.KeyValue { 48 return hc.ClientRequestMetrics(req) 49 } 50 51 // HTTPClientStatus returns a span status code and message for an HTTP status code 52 // value received by a client. 53 func HTTPClientStatus(code int) (codes.Code, string) { 54 return hc.ClientStatus(code) 55 } 56 57 // HTTPServerRequest returns trace attributes for an HTTP request received by a 58 // server. 59 // 60 // The server must be the primary server name if it is known. For example this 61 // would be the ServerName directive 62 // (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache 63 // server, and the server_name directive 64 // (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an 65 // nginx server. More generically, the primary server name would be the host 66 // header value that matches the default virtual host of an HTTP server. It 67 // should include the host identifier and if a port is used to route to the 68 // server that port identifier should be included as an appropriate port 69 // suffix. 70 // 71 // If the primary server name is not known, server should be an empty string. 72 // The req Host will be used to determine the server instead. 73 // 74 // The following attributes are always returned: "http.method", "http.scheme", 75 // "http.target", "net.host.name". The following attributes are returned if 76 // they related values are defined in req: "net.host.port", "net.sock.peer.addr", 77 // "net.sock.peer.port", "user_agent.original", "http.client_ip". 78 func HTTPServerRequest(server string, req *http.Request) []attribute.KeyValue { 79 return hc.ServerRequest(server, req) 80 } 81 82 // HTTPServerRequestMetrics returns metric attributes for an HTTP request received by a 83 // server. 84 // 85 // The server must be the primary server name if it is known. For example this 86 // would be the ServerName directive 87 // (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache 88 // server, and the server_name directive 89 // (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an 90 // nginx server. More generically, the primary server name would be the host 91 // header value that matches the default virtual host of an HTTP server. It 92 // should include the host identifier and if a port is used to route to the 93 // server that port identifier should be included as an appropriate port 94 // suffix. 95 // 96 // If the primary server name is not known, server should be an empty string. 97 // The req Host will be used to determine the server instead. 98 // 99 // The following attributes are always returned: "http.method", "http.scheme", 100 // "net.host.name". The following attributes are returned if they related 101 // values are defined in req: "net.host.port". 102 func HTTPServerRequestMetrics(server string, req *http.Request) []attribute.KeyValue { 103 return hc.ServerRequestMetrics(server, req) 104 } 105 106 // HTTPServerStatus returns a span status code and message for an HTTP status code 107 // value returned by a server. Status codes in the 400-499 range are not 108 // returned as errors. 109 func HTTPServerStatus(code int) (codes.Code, string) { 110 return hc.ServerStatus(code) 111 } 112 113 // httpConv are the HTTP semantic convention attributes defined for a version 114 // of the OpenTelemetry specification. 115 type httpConv struct { 116 NetConv *netConv 117 118 HTTPClientIPKey attribute.Key 119 HTTPMethodKey attribute.Key 120 HTTPRequestContentLengthKey attribute.Key 121 HTTPResponseContentLengthKey attribute.Key 122 HTTPRouteKey attribute.Key 123 HTTPSchemeHTTP attribute.KeyValue 124 HTTPSchemeHTTPS attribute.KeyValue 125 HTTPStatusCodeKey attribute.Key 126 HTTPTargetKey attribute.Key 127 HTTPURLKey attribute.Key 128 UserAgentOriginalKey attribute.Key 129 } 130 131 var hc = &httpConv{ 132 NetConv: nc, 133 134 HTTPClientIPKey: semconv.HTTPClientIPKey, 135 HTTPMethodKey: semconv.HTTPMethodKey, 136 HTTPRequestContentLengthKey: semconv.HTTPRequestContentLengthKey, 137 HTTPResponseContentLengthKey: semconv.HTTPResponseContentLengthKey, 138 HTTPRouteKey: semconv.HTTPRouteKey, 139 HTTPSchemeHTTP: semconv.HTTPSchemeHTTP, 140 HTTPSchemeHTTPS: semconv.HTTPSchemeHTTPS, 141 HTTPStatusCodeKey: semconv.HTTPStatusCodeKey, 142 HTTPTargetKey: semconv.HTTPTargetKey, 143 HTTPURLKey: semconv.HTTPURLKey, 144 UserAgentOriginalKey: semconv.UserAgentOriginalKey, 145 } 146 147 // ClientResponse returns attributes for an HTTP response received by a client 148 // from a server. The following attributes are returned if the related values 149 // are defined in resp: "http.status.code", "http.response_content_length". 150 // 151 // This does not add all OpenTelemetry required attributes for an HTTP event, 152 // it assumes ClientRequest was used to create the span with a complete set of 153 // attributes. If a complete set of attributes can be generated using the 154 // request contained in resp. For example: 155 // 156 // append(ClientResponse(resp), ClientRequest(resp.Request)...) 157 func (c *httpConv) ClientResponse(resp *http.Response) []attribute.KeyValue { 158 /* The following semantic conventions are returned if present: 159 http.status_code int 160 http.response_content_length int 161 */ 162 var n int 163 if resp.StatusCode > 0 { 164 n++ 165 } 166 if resp.ContentLength > 0 { 167 n++ 168 } 169 170 attrs := make([]attribute.KeyValue, 0, n) 171 if resp.StatusCode > 0 { 172 attrs = append(attrs, c.HTTPStatusCodeKey.Int(resp.StatusCode)) 173 } 174 if resp.ContentLength > 0 { 175 attrs = append(attrs, c.HTTPResponseContentLengthKey.Int(int(resp.ContentLength))) 176 } 177 return attrs 178 } 179 180 // ClientRequest returns attributes for an HTTP request made by a client. The 181 // following attributes are always returned: "http.url", "http.method", 182 // "net.peer.name". The following attributes are returned if the related values 183 // are defined in req: "net.peer.port", "user_agent.original", 184 // "http.request_content_length", "user_agent.original". 185 func (c *httpConv) ClientRequest(req *http.Request) []attribute.KeyValue { 186 /* The following semantic conventions are returned if present: 187 http.method string 188 user_agent.original string 189 http.url string 190 net.peer.name string 191 net.peer.port int 192 http.request_content_length int 193 */ 194 195 /* The following semantic conventions are not returned: 196 http.status_code This requires the response. See ClientResponse. 197 http.response_content_length This requires the response. See ClientResponse. 198 net.sock.family This requires the socket used. 199 net.sock.peer.addr This requires the socket used. 200 net.sock.peer.name This requires the socket used. 201 net.sock.peer.port This requires the socket used. 202 http.resend_count This is something outside of a single request. 203 net.protocol.name The value is the Request is ignored, and the go client will always use "http". 204 net.protocol.version The value in the Request is ignored, and the go client will always use 1.1 or 2.0. 205 */ 206 n := 3 // URL, peer name, proto, and method. 207 var h string 208 if req.URL != nil { 209 h = req.URL.Host 210 } 211 peer, p := firstHostPort(h, req.Header.Get("Host")) 212 port := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", p) 213 if port > 0 { 214 n++ 215 } 216 useragent := req.UserAgent() 217 if useragent != "" { 218 n++ 219 } 220 if req.ContentLength > 0 { 221 n++ 222 } 223 224 attrs := make([]attribute.KeyValue, 0, n) 225 226 attrs = append(attrs, c.method(req.Method)) 227 228 var u string 229 if req.URL != nil { 230 // Remove any username/password info that may be in the URL. 231 userinfo := req.URL.User 232 req.URL.User = nil 233 u = req.URL.String() 234 // Restore any username/password info that was removed. 235 req.URL.User = userinfo 236 } 237 attrs = append(attrs, c.HTTPURLKey.String(u)) 238 239 attrs = append(attrs, c.NetConv.PeerName(peer)) 240 if port > 0 { 241 attrs = append(attrs, c.NetConv.PeerPort(port)) 242 } 243 244 if useragent != "" { 245 attrs = append(attrs, c.UserAgentOriginalKey.String(useragent)) 246 } 247 248 if l := req.ContentLength; l > 0 { 249 attrs = append(attrs, c.HTTPRequestContentLengthKey.Int64(l)) 250 } 251 252 return attrs 253 } 254 255 // ClientRequestMetrics returns metric attributes for an HTTP request made by a client. The 256 // following attributes are always returned: "http.method", "net.peer.name". 257 // The following attributes are returned if the related values 258 // are defined in req: "net.peer.port". 259 func (c *httpConv) ClientRequestMetrics(req *http.Request) []attribute.KeyValue { 260 /* The following semantic conventions are returned if present: 261 http.method string 262 net.peer.name string 263 net.peer.port int 264 */ 265 266 n := 2 // method, peer name. 267 var h string 268 if req.URL != nil { 269 h = req.URL.Host 270 } 271 peer, p := firstHostPort(h, req.Header.Get("Host")) 272 port := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", p) 273 if port > 0 { 274 n++ 275 } 276 277 attrs := make([]attribute.KeyValue, 0, n) 278 attrs = append(attrs, c.method(req.Method), c.NetConv.PeerName(peer)) 279 280 if port > 0 { 281 attrs = append(attrs, c.NetConv.PeerPort(port)) 282 } 283 284 return attrs 285 } 286 287 // ServerRequest returns attributes for an HTTP request received by a server. 288 // 289 // The server must be the primary server name if it is known. For example this 290 // would be the ServerName directive 291 // (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache 292 // server, and the server_name directive 293 // (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an 294 // nginx server. More generically, the primary server name would be the host 295 // header value that matches the default virtual host of an HTTP server. It 296 // should include the host identifier and if a port is used to route to the 297 // server that port identifier should be included as an appropriate port 298 // suffix. 299 // 300 // If the primary server name is not known, server should be an empty string. 301 // The req Host will be used to determine the server instead. 302 // 303 // The following attributes are always returned: "http.method", "http.scheme", 304 // "http.target", "net.host.name". The following attributes are returned if they 305 // related values are defined in req: "net.host.port", "net.sock.peer.addr", 306 // "net.sock.peer.port", "user_agent.original", "http.client_ip", 307 // "net.protocol.name", "net.protocol.version". 308 func (c *httpConv) ServerRequest(server string, req *http.Request) []attribute.KeyValue { 309 /* The following semantic conventions are returned if present: 310 http.method string 311 http.scheme string 312 net.host.name string 313 net.host.port int 314 net.sock.peer.addr string 315 net.sock.peer.port int 316 user_agent.original string 317 http.client_ip string 318 net.protocol.name string Note: not set if the value is "http". 319 net.protocol.version string 320 http.target string Note: doesn't include the query parameter. 321 */ 322 323 /* The following semantic conventions are not returned: 324 http.status_code This requires the response. 325 http.request_content_length This requires the len() of body, which can mutate it. 326 http.response_content_length This requires the response. 327 http.route This is not available. 328 net.sock.peer.name This would require a DNS lookup. 329 net.sock.host.addr The request doesn't have access to the underlying socket. 330 net.sock.host.port The request doesn't have access to the underlying socket. 331 332 */ 333 n := 4 // Method, scheme, proto, and host name. 334 var host string 335 var p int 336 if server == "" { 337 host, p = splitHostPort(req.Host) 338 } else { 339 // Prioritize the primary server name. 340 host, p = splitHostPort(server) 341 if p < 0 { 342 _, p = splitHostPort(req.Host) 343 } 344 } 345 hostPort := requiredHTTPPort(req.TLS != nil, p) 346 if hostPort > 0 { 347 n++ 348 } 349 peer, peerPort := splitHostPort(req.RemoteAddr) 350 if peer != "" { 351 n++ 352 if peerPort > 0 { 353 n++ 354 } 355 } 356 useragent := req.UserAgent() 357 if useragent != "" { 358 n++ 359 } 360 361 clientIP := serverClientIP(req.Header.Get("X-Forwarded-For")) 362 if clientIP != "" { 363 n++ 364 } 365 366 var target string 367 if req.URL != nil { 368 target = req.URL.Path 369 if target != "" { 370 n++ 371 } 372 } 373 protoName, protoVersion := netProtocol(req.Proto) 374 if protoName != "" && protoName != "http" { 375 n++ 376 } 377 if protoVersion != "" { 378 n++ 379 } 380 381 attrs := make([]attribute.KeyValue, 0, n) 382 383 attrs = append(attrs, c.method(req.Method)) 384 attrs = append(attrs, c.scheme(req.TLS != nil)) 385 attrs = append(attrs, c.NetConv.HostName(host)) 386 387 if hostPort > 0 { 388 attrs = append(attrs, c.NetConv.HostPort(hostPort)) 389 } 390 391 if peer != "" { 392 // The Go HTTP server sets RemoteAddr to "IP:port", this will not be a 393 // file-path that would be interpreted with a sock family. 394 attrs = append(attrs, c.NetConv.SockPeerAddr(peer)) 395 if peerPort > 0 { 396 attrs = append(attrs, c.NetConv.SockPeerPort(peerPort)) 397 } 398 } 399 400 if useragent != "" { 401 attrs = append(attrs, c.UserAgentOriginalKey.String(useragent)) 402 } 403 404 if clientIP != "" { 405 attrs = append(attrs, c.HTTPClientIPKey.String(clientIP)) 406 } 407 408 if target != "" { 409 attrs = append(attrs, c.HTTPTargetKey.String(target)) 410 } 411 412 if protoName != "" && protoName != "http" { 413 attrs = append(attrs, c.NetConv.NetProtocolName.String(protoName)) 414 } 415 if protoVersion != "" { 416 attrs = append(attrs, c.NetConv.NetProtocolVersion.String(protoVersion)) 417 } 418 419 return attrs 420 } 421 422 // ServerRequestMetrics returns metric attributes for an HTTP request received 423 // by a server. 424 // 425 // The server must be the primary server name if it is known. For example this 426 // would be the ServerName directive 427 // (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache 428 // server, and the server_name directive 429 // (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an 430 // nginx server. More generically, the primary server name would be the host 431 // header value that matches the default virtual host of an HTTP server. It 432 // should include the host identifier and if a port is used to route to the 433 // server that port identifier should be included as an appropriate port 434 // suffix. 435 // 436 // If the primary server name is not known, server should be an empty string. 437 // The req Host will be used to determine the server instead. 438 // 439 // The following attributes are always returned: "http.method", "http.scheme", 440 // "net.host.name". The following attributes are returned if they related 441 // values are defined in req: "net.host.port". 442 func (c *httpConv) ServerRequestMetrics(server string, req *http.Request) []attribute.KeyValue { 443 /* The following semantic conventions are returned if present: 444 http.scheme string 445 http.route string 446 http.method string 447 http.status_code int 448 net.host.name string 449 net.host.port int 450 net.protocol.name string Note: not set if the value is "http". 451 net.protocol.version string 452 */ 453 454 n := 3 // Method, scheme, and host name. 455 var host string 456 var p int 457 if server == "" { 458 host, p = splitHostPort(req.Host) 459 } else { 460 // Prioritize the primary server name. 461 host, p = splitHostPort(server) 462 if p < 0 { 463 _, p = splitHostPort(req.Host) 464 } 465 } 466 hostPort := requiredHTTPPort(req.TLS != nil, p) 467 if hostPort > 0 { 468 n++ 469 } 470 protoName, protoVersion := netProtocol(req.Proto) 471 if protoName != "" { 472 n++ 473 } 474 if protoVersion != "" { 475 n++ 476 } 477 478 attrs := make([]attribute.KeyValue, 0, n) 479 480 attrs = append(attrs, c.methodMetric(req.Method)) 481 attrs = append(attrs, c.scheme(req.TLS != nil)) 482 attrs = append(attrs, c.NetConv.HostName(host)) 483 484 if hostPort > 0 { 485 attrs = append(attrs, c.NetConv.HostPort(hostPort)) 486 } 487 if protoName != "" { 488 attrs = append(attrs, c.NetConv.NetProtocolName.String(protoName)) 489 } 490 if protoVersion != "" { 491 attrs = append(attrs, c.NetConv.NetProtocolVersion.String(protoVersion)) 492 } 493 494 return attrs 495 } 496 497 func (c *httpConv) method(method string) attribute.KeyValue { 498 if method == "" { 499 return c.HTTPMethodKey.String(http.MethodGet) 500 } 501 return c.HTTPMethodKey.String(method) 502 } 503 504 func (c *httpConv) methodMetric(method string) attribute.KeyValue { 505 method = strings.ToUpper(method) 506 switch method { 507 case http.MethodConnect, http.MethodDelete, http.MethodGet, http.MethodHead, http.MethodOptions, http.MethodPatch, http.MethodPost, http.MethodPut, http.MethodTrace: 508 default: 509 method = "_OTHER" 510 } 511 return c.HTTPMethodKey.String(method) 512 } 513 514 func (c *httpConv) scheme(https bool) attribute.KeyValue { // nolint:revive 515 if https { 516 return c.HTTPSchemeHTTPS 517 } 518 return c.HTTPSchemeHTTP 519 } 520 521 func serverClientIP(xForwardedFor string) string { 522 if idx := strings.Index(xForwardedFor, ","); idx >= 0 { 523 xForwardedFor = xForwardedFor[:idx] 524 } 525 return xForwardedFor 526 } 527 528 func requiredHTTPPort(https bool, port int) int { // nolint:revive 529 if https { 530 if port > 0 && port != 443 { 531 return port 532 } 533 } else { 534 if port > 0 && port != 80 { 535 return port 536 } 537 } 538 return -1 539 } 540 541 // Return the request host and port from the first non-empty source. 542 func firstHostPort(source ...string) (host string, port int) { 543 for _, hostport := range source { 544 host, port = splitHostPort(hostport) 545 if host != "" || port > 0 { 546 break 547 } 548 } 549 return 550 } 551 552 // ClientStatus returns a span status code and message for an HTTP status code 553 // value received by a client. 554 func (c *httpConv) ClientStatus(code int) (codes.Code, string) { 555 if code < 100 || code >= 600 { 556 return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code) 557 } 558 if code >= 400 { 559 return codes.Error, "" 560 } 561 return codes.Unset, "" 562 } 563 564 // ServerStatus returns a span status code and message for an HTTP status code 565 // value returned by a server. Status codes in the 400-499 range are not 566 // returned as errors. 567 func (c *httpConv) ServerStatus(code int) (codes.Code, string) { 568 if code < 100 || code >= 600 { 569 return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code) 570 } 571 if code >= 500 { 572 return codes.Error, "" 573 } 574 return codes.Unset, "" 575 } 576