httpconv.go raw
1 // Code generated by gotmpl. DO NOT MODIFY.
2 // source: internal/shared/semconv/httpconv.go.tmpl
3
4 // Copyright The OpenTelemetry Authors
5 // SPDX-License-Identifier: Apache-2.0
6
7 // Package semconv provides OpenTelemetry semantic convention types and
8 // functionality.
9 package semconv // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconv"
10
11 import (
12 "fmt"
13 "net/http"
14 "reflect"
15 "slices"
16 "strconv"
17 "strings"
18
19 "go.opentelemetry.io/otel/attribute"
20 "go.opentelemetry.io/otel/metric"
21 "go.opentelemetry.io/otel/metric/noop"
22 semconvNew "go.opentelemetry.io/otel/semconv/v1.26.0"
23 )
24
25 type RequestTraceAttrsOpts struct {
26 // If set, this is used as value for the "http.client_ip" attribute.
27 HTTPClientIP string
28 }
29
30 type CurrentHTTPServer struct{}
31
32 // RequestTraceAttrs returns trace attributes for an HTTP request received by a
33 // server.
34 //
35 // The server must be the primary server name if it is known. For example this
36 // would be the ServerName directive
37 // (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache
38 // server, and the server_name directive
39 // (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an
40 // nginx server. More generically, the primary server name would be the host
41 // header value that matches the default virtual host of an HTTP server. It
42 // should include the host identifier and if a port is used to route to the
43 // server that port identifier should be included as an appropriate port
44 // suffix.
45 //
46 // If the primary server name is not known, server should be an empty string.
47 // The req Host will be used to determine the server instead.
48 func (n CurrentHTTPServer) RequestTraceAttrs(server string, req *http.Request, opts RequestTraceAttrsOpts) []attribute.KeyValue {
49 count := 3 // ServerAddress, Method, Scheme
50
51 var host string
52 var p int
53 if server == "" {
54 host, p = SplitHostPort(req.Host)
55 } else {
56 // Prioritize the primary server name.
57 host, p = SplitHostPort(server)
58 if p < 0 {
59 _, p = SplitHostPort(req.Host)
60 }
61 }
62
63 hostPort := requiredHTTPPort(req.TLS != nil, p)
64 if hostPort > 0 {
65 count++
66 }
67
68 method, methodOriginal := n.method(req.Method)
69 if methodOriginal != (attribute.KeyValue{}) {
70 count++
71 }
72
73 scheme := n.scheme(req.TLS != nil)
74
75 peer, peerPort := SplitHostPort(req.RemoteAddr)
76 if peer != "" {
77 // The Go HTTP server sets RemoteAddr to "IP:port", this will not be a
78 // file-path that would be interpreted with a sock family.
79 count++
80 if peerPort > 0 {
81 count++
82 }
83 }
84
85 useragent := req.UserAgent()
86 if useragent != "" {
87 count++
88 }
89
90 // For client IP, use, in order:
91 // 1. The value passed in the options
92 // 2. The value in the X-Forwarded-For header
93 // 3. The peer address
94 clientIP := opts.HTTPClientIP
95 if clientIP == "" {
96 clientIP = serverClientIP(req.Header.Get("X-Forwarded-For"))
97 if clientIP == "" {
98 clientIP = peer
99 }
100 }
101 if clientIP != "" {
102 count++
103 }
104
105 if req.URL != nil && req.URL.Path != "" {
106 count++
107 }
108
109 protoName, protoVersion := netProtocol(req.Proto)
110 if protoName != "" && protoName != "http" {
111 count++
112 }
113 if protoVersion != "" {
114 count++
115 }
116
117 route := httpRoute(req.Pattern)
118 if route != "" {
119 count++
120 }
121
122 attrs := make([]attribute.KeyValue, 0, count)
123 attrs = append(attrs,
124 semconvNew.ServerAddress(host),
125 method,
126 scheme,
127 )
128
129 if hostPort > 0 {
130 attrs = append(attrs, semconvNew.ServerPort(hostPort))
131 }
132 if methodOriginal != (attribute.KeyValue{}) {
133 attrs = append(attrs, methodOriginal)
134 }
135
136 if peer, peerPort := SplitHostPort(req.RemoteAddr); peer != "" {
137 // The Go HTTP server sets RemoteAddr to "IP:port", this will not be a
138 // file-path that would be interpreted with a sock family.
139 attrs = append(attrs, semconvNew.NetworkPeerAddress(peer))
140 if peerPort > 0 {
141 attrs = append(attrs, semconvNew.NetworkPeerPort(peerPort))
142 }
143 }
144
145 if useragent != "" {
146 attrs = append(attrs, semconvNew.UserAgentOriginal(useragent))
147 }
148
149 if clientIP != "" {
150 attrs = append(attrs, semconvNew.ClientAddress(clientIP))
151 }
152
153 if req.URL != nil && req.URL.Path != "" {
154 attrs = append(attrs, semconvNew.URLPath(req.URL.Path))
155 }
156
157 if protoName != "" && protoName != "http" {
158 attrs = append(attrs, semconvNew.NetworkProtocolName(protoName))
159 }
160 if protoVersion != "" {
161 attrs = append(attrs, semconvNew.NetworkProtocolVersion(protoVersion))
162 }
163
164 if route != "" {
165 attrs = append(attrs, n.Route(route))
166 }
167
168 return attrs
169 }
170
171 func (n CurrentHTTPServer) NetworkTransportAttr(network string) attribute.KeyValue {
172 switch network {
173 case "tcp", "tcp4", "tcp6":
174 return semconvNew.NetworkTransportTCP
175 case "udp", "udp4", "udp6":
176 return semconvNew.NetworkTransportUDP
177 case "unix", "unixgram", "unixpacket":
178 return semconvNew.NetworkTransportUnix
179 default:
180 return semconvNew.NetworkTransportPipe
181 }
182 }
183
184 func (n CurrentHTTPServer) method(method string) (attribute.KeyValue, attribute.KeyValue) {
185 if method == "" {
186 return semconvNew.HTTPRequestMethodGet, attribute.KeyValue{}
187 }
188 if attr, ok := methodLookup[method]; ok {
189 return attr, attribute.KeyValue{}
190 }
191
192 orig := semconvNew.HTTPRequestMethodOriginal(method)
193 if attr, ok := methodLookup[strings.ToUpper(method)]; ok {
194 return attr, orig
195 }
196 return semconvNew.HTTPRequestMethodGet, orig
197 }
198
199 func (n CurrentHTTPServer) scheme(https bool) attribute.KeyValue { // nolint:revive
200 if https {
201 return semconvNew.URLScheme("https")
202 }
203 return semconvNew.URLScheme("http")
204 }
205
206 // ResponseTraceAttrs returns trace attributes for telemetry from an HTTP
207 // response.
208 //
209 // If any of the fields in the ResponseTelemetry are not set the attribute will
210 // be omitted.
211 func (n CurrentHTTPServer) ResponseTraceAttrs(resp ResponseTelemetry) []attribute.KeyValue {
212 var count int
213
214 if resp.ReadBytes > 0 {
215 count++
216 }
217 if resp.WriteBytes > 0 {
218 count++
219 }
220 if resp.StatusCode > 0 {
221 count++
222 }
223
224 attributes := make([]attribute.KeyValue, 0, count)
225
226 if resp.ReadBytes > 0 {
227 attributes = append(attributes,
228 semconvNew.HTTPRequestBodySize(int(resp.ReadBytes)),
229 )
230 }
231 if resp.WriteBytes > 0 {
232 attributes = append(attributes,
233 semconvNew.HTTPResponseBodySize(int(resp.WriteBytes)),
234 )
235 }
236 if resp.StatusCode > 0 {
237 attributes = append(attributes,
238 semconvNew.HTTPResponseStatusCode(resp.StatusCode),
239 )
240 }
241
242 return attributes
243 }
244
245 // Route returns the attribute for the route.
246 func (n CurrentHTTPServer) Route(route string) attribute.KeyValue {
247 return semconvNew.HTTPRoute(route)
248 }
249
250 func (n CurrentHTTPServer) createMeasures(meter metric.Meter) (metric.Int64Histogram, metric.Int64Histogram, metric.Float64Histogram) {
251 if meter == nil {
252 return noop.Int64Histogram{}, noop.Int64Histogram{}, noop.Float64Histogram{}
253 }
254
255 var err error
256 requestBodySizeHistogram, err := meter.Int64Histogram(
257 semconvNew.HTTPServerRequestBodySizeName,
258 metric.WithUnit(semconvNew.HTTPServerRequestBodySizeUnit),
259 metric.WithDescription(semconvNew.HTTPServerRequestBodySizeDescription),
260 )
261 handleErr(err)
262
263 responseBodySizeHistogram, err := meter.Int64Histogram(
264 semconvNew.HTTPServerResponseBodySizeName,
265 metric.WithUnit(semconvNew.HTTPServerResponseBodySizeUnit),
266 metric.WithDescription(semconvNew.HTTPServerResponseBodySizeDescription),
267 )
268 handleErr(err)
269 requestDurationHistogram, err := meter.Float64Histogram(
270 semconvNew.HTTPServerRequestDurationName,
271 metric.WithUnit(semconvNew.HTTPServerRequestDurationUnit),
272 metric.WithDescription(semconvNew.HTTPServerRequestDurationDescription),
273 metric.WithExplicitBucketBoundaries(0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10),
274 )
275 handleErr(err)
276
277 return requestBodySizeHistogram, responseBodySizeHistogram, requestDurationHistogram
278 }
279
280 func (n CurrentHTTPServer) MetricAttributes(server string, req *http.Request, statusCode int, additionalAttributes []attribute.KeyValue) []attribute.KeyValue {
281 num := len(additionalAttributes) + 3
282 var host string
283 var p int
284 if server == "" {
285 host, p = SplitHostPort(req.Host)
286 } else {
287 // Prioritize the primary server name.
288 host, p = SplitHostPort(server)
289 if p < 0 {
290 _, p = SplitHostPort(req.Host)
291 }
292 }
293 hostPort := requiredHTTPPort(req.TLS != nil, p)
294 if hostPort > 0 {
295 num++
296 }
297 protoName, protoVersion := netProtocol(req.Proto)
298 if protoName != "" {
299 num++
300 }
301 if protoVersion != "" {
302 num++
303 }
304
305 if statusCode > 0 {
306 num++
307 }
308
309 attributes := slices.Grow(additionalAttributes, num)
310 attributes = append(attributes,
311 semconvNew.HTTPRequestMethodKey.String(standardizeHTTPMethod(req.Method)),
312 n.scheme(req.TLS != nil),
313 semconvNew.ServerAddress(host))
314
315 if hostPort > 0 {
316 attributes = append(attributes, semconvNew.ServerPort(hostPort))
317 }
318 if protoName != "" {
319 attributes = append(attributes, semconvNew.NetworkProtocolName(protoName))
320 }
321 if protoVersion != "" {
322 attributes = append(attributes, semconvNew.NetworkProtocolVersion(protoVersion))
323 }
324
325 if statusCode > 0 {
326 attributes = append(attributes, semconvNew.HTTPResponseStatusCode(statusCode))
327 }
328 return attributes
329 }
330
331 type CurrentHTTPClient struct{}
332
333 // RequestTraceAttrs returns trace attributes for an HTTP request made by a client.
334 func (n CurrentHTTPClient) RequestTraceAttrs(req *http.Request) []attribute.KeyValue {
335 /*
336 below attributes are returned:
337 - http.request.method
338 - http.request.method.original
339 - url.full
340 - server.address
341 - server.port
342 - network.protocol.name
343 - network.protocol.version
344 */
345 numOfAttributes := 3 // URL, server address, proto, and method.
346
347 var urlHost string
348 if req.URL != nil {
349 urlHost = req.URL.Host
350 }
351 var requestHost string
352 var requestPort int
353 for _, hostport := range []string{urlHost, req.Header.Get("Host")} {
354 requestHost, requestPort = SplitHostPort(hostport)
355 if requestHost != "" || requestPort > 0 {
356 break
357 }
358 }
359
360 eligiblePort := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", requestPort)
361 if eligiblePort > 0 {
362 numOfAttributes++
363 }
364 useragent := req.UserAgent()
365 if useragent != "" {
366 numOfAttributes++
367 }
368
369 protoName, protoVersion := netProtocol(req.Proto)
370 if protoName != "" && protoName != "http" {
371 numOfAttributes++
372 }
373 if protoVersion != "" {
374 numOfAttributes++
375 }
376
377 method, originalMethod := n.method(req.Method)
378 if originalMethod != (attribute.KeyValue{}) {
379 numOfAttributes++
380 }
381
382 attrs := make([]attribute.KeyValue, 0, numOfAttributes)
383
384 attrs = append(attrs, method)
385 if originalMethod != (attribute.KeyValue{}) {
386 attrs = append(attrs, originalMethod)
387 }
388
389 var u string
390 if req.URL != nil {
391 // Remove any username/password info that may be in the URL.
392 userinfo := req.URL.User
393 req.URL.User = nil
394 u = req.URL.String()
395 // Restore any username/password info that was removed.
396 req.URL.User = userinfo
397 }
398 attrs = append(attrs, semconvNew.URLFull(u))
399
400 attrs = append(attrs, semconvNew.ServerAddress(requestHost))
401 if eligiblePort > 0 {
402 attrs = append(attrs, semconvNew.ServerPort(eligiblePort))
403 }
404
405 if protoName != "" && protoName != "http" {
406 attrs = append(attrs, semconvNew.NetworkProtocolName(protoName))
407 }
408 if protoVersion != "" {
409 attrs = append(attrs, semconvNew.NetworkProtocolVersion(protoVersion))
410 }
411
412 return attrs
413 }
414
415 // ResponseTraceAttrs returns trace attributes for an HTTP response made by a client.
416 func (n CurrentHTTPClient) ResponseTraceAttrs(resp *http.Response) []attribute.KeyValue {
417 /*
418 below attributes are returned:
419 - http.response.status_code
420 - error.type
421 */
422 var count int
423 if resp.StatusCode > 0 {
424 count++
425 }
426
427 if isErrorStatusCode(resp.StatusCode) {
428 count++
429 }
430
431 attrs := make([]attribute.KeyValue, 0, count)
432 if resp.StatusCode > 0 {
433 attrs = append(attrs, semconvNew.HTTPResponseStatusCode(resp.StatusCode))
434 }
435
436 if isErrorStatusCode(resp.StatusCode) {
437 errorType := strconv.Itoa(resp.StatusCode)
438 attrs = append(attrs, semconvNew.ErrorTypeKey.String(errorType))
439 }
440 return attrs
441 }
442
443 func (n CurrentHTTPClient) ErrorType(err error) attribute.KeyValue {
444 t := reflect.TypeOf(err)
445 var value string
446 if t.PkgPath() == "" && t.Name() == "" {
447 // Likely a builtin type.
448 value = t.String()
449 } else {
450 value = fmt.Sprintf("%s.%s", t.PkgPath(), t.Name())
451 }
452
453 if value == "" {
454 return semconvNew.ErrorTypeOther
455 }
456
457 return semconvNew.ErrorTypeKey.String(value)
458 }
459
460 func (n CurrentHTTPClient) method(method string) (attribute.KeyValue, attribute.KeyValue) {
461 if method == "" {
462 return semconvNew.HTTPRequestMethodGet, attribute.KeyValue{}
463 }
464 if attr, ok := methodLookup[method]; ok {
465 return attr, attribute.KeyValue{}
466 }
467
468 orig := semconvNew.HTTPRequestMethodOriginal(method)
469 if attr, ok := methodLookup[strings.ToUpper(method)]; ok {
470 return attr, orig
471 }
472 return semconvNew.HTTPRequestMethodGet, orig
473 }
474
475 func (n CurrentHTTPClient) createMeasures(meter metric.Meter) (metric.Int64Histogram, metric.Float64Histogram) {
476 if meter == nil {
477 return noop.Int64Histogram{}, noop.Float64Histogram{}
478 }
479
480 var err error
481 requestBodySize, err := meter.Int64Histogram(
482 semconvNew.HTTPClientRequestBodySizeName,
483 metric.WithUnit(semconvNew.HTTPClientRequestBodySizeUnit),
484 metric.WithDescription(semconvNew.HTTPClientRequestBodySizeDescription),
485 )
486 handleErr(err)
487
488 requestDuration, err := meter.Float64Histogram(
489 semconvNew.HTTPClientRequestDurationName,
490 metric.WithUnit(semconvNew.HTTPClientRequestDurationUnit),
491 metric.WithDescription(semconvNew.HTTPClientRequestDurationDescription),
492 metric.WithExplicitBucketBoundaries(0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10),
493 )
494 handleErr(err)
495
496 return requestBodySize, requestDuration
497 }
498
499 func (n CurrentHTTPClient) MetricAttributes(req *http.Request, statusCode int, additionalAttributes []attribute.KeyValue) []attribute.KeyValue {
500 num := len(additionalAttributes) + 2
501 var h string
502 if req.URL != nil {
503 h = req.URL.Host
504 }
505 var requestHost string
506 var requestPort int
507 for _, hostport := range []string{h, req.Header.Get("Host")} {
508 requestHost, requestPort = SplitHostPort(hostport)
509 if requestHost != "" || requestPort > 0 {
510 break
511 }
512 }
513
514 port := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", requestPort)
515 if port > 0 {
516 num++
517 }
518
519 protoName, protoVersion := netProtocol(req.Proto)
520 if protoName != "" {
521 num++
522 }
523 if protoVersion != "" {
524 num++
525 }
526
527 if statusCode > 0 {
528 num++
529 }
530
531 attributes := slices.Grow(additionalAttributes, num)
532 attributes = append(attributes,
533 semconvNew.HTTPRequestMethodKey.String(standardizeHTTPMethod(req.Method)),
534 semconvNew.ServerAddress(requestHost),
535 n.scheme(req),
536 )
537
538 if port > 0 {
539 attributes = append(attributes, semconvNew.ServerPort(port))
540 }
541 if protoName != "" {
542 attributes = append(attributes, semconvNew.NetworkProtocolName(protoName))
543 }
544 if protoVersion != "" {
545 attributes = append(attributes, semconvNew.NetworkProtocolVersion(protoVersion))
546 }
547
548 if statusCode > 0 {
549 attributes = append(attributes, semconvNew.HTTPResponseStatusCode(statusCode))
550 }
551 return attributes
552 }
553
554 // TraceAttributes returns attributes for httptrace.
555 func (n CurrentHTTPClient) TraceAttributes(host string) []attribute.KeyValue {
556 return []attribute.KeyValue{
557 semconvNew.ServerAddress(host),
558 }
559 }
560
561 func (n CurrentHTTPClient) scheme(req *http.Request) attribute.KeyValue {
562 if req.URL != nil && req.URL.Scheme != "" {
563 return semconvNew.URLScheme(req.URL.Scheme)
564 }
565 if req.TLS != nil {
566 return semconvNew.URLScheme("https")
567 }
568 return semconvNew.URLScheme("http")
569 }
570
571 func isErrorStatusCode(code int) bool {
572 return code >= 400 || code < 100
573 }
574