httpconv.go

  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
  7package semconvutil // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconvutil"
  8
  9import (
 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)...)
 30func 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".
 39func 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".
 47func 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.
 53func 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".
 78func 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".
102func 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.
109func 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.
115type 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
131var 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)...)
157func (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".
185func (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".
259func (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".
308func (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".
442func (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
497func (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
504func (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
514func (c *httpConv) scheme(https bool) attribute.KeyValue { // nolint:revive
515	if https {
516		return c.HTTPSchemeHTTPS
517	}
518	return c.HTTPSchemeHTTP
519}
520
521func serverClientIP(xForwardedFor string) string {
522	if idx := strings.Index(xForwardedFor, ","); idx >= 0 {
523		xForwardedFor = xForwardedFor[:idx]
524	}
525	return xForwardedFor
526}
527
528func 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.
542func 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.
554func (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.
567func (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}