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}