httpconv.go raw

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