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