1// Copyright The OpenTelemetry Authors
2// SPDX-License-Identifier: Apache-2.0
3
4package semconv // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconv"
5
6import (
7 "errors"
8 "io"
9 "net/http"
10 "slices"
11 "strings"
12
13 "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconvutil"
14 "go.opentelemetry.io/otel/attribute"
15 "go.opentelemetry.io/otel/metric"
16 "go.opentelemetry.io/otel/metric/noop"
17 semconv "go.opentelemetry.io/otel/semconv/v1.20.0"
18)
19
20type oldHTTPServer struct{}
21
22// RequestTraceAttrs returns trace attributes for an HTTP request received by a
23// server.
24//
25// The server must be the primary server name if it is known. For example this
26// would be the ServerName directive
27// (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache
28// server, and the server_name directive
29// (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an
30// nginx server. More generically, the primary server name would be the host
31// header value that matches the default virtual host of an HTTP server. It
32// should include the host identifier and if a port is used to route to the
33// server that port identifier should be included as an appropriate port
34// suffix.
35//
36// If the primary server name is not known, server should be an empty string.
37// The req Host will be used to determine the server instead.
38func (o oldHTTPServer) RequestTraceAttrs(server string, req *http.Request) []attribute.KeyValue {
39 return semconvutil.HTTPServerRequest(server, req)
40}
41
42// ResponseTraceAttrs returns trace attributes for telemetry from an HTTP response.
43//
44// If any of the fields in the ResponseTelemetry are not set the attribute will be omitted.
45func (o oldHTTPServer) ResponseTraceAttrs(resp ResponseTelemetry) []attribute.KeyValue {
46 attributes := []attribute.KeyValue{}
47
48 if resp.ReadBytes > 0 {
49 attributes = append(attributes, semconv.HTTPRequestContentLength(int(resp.ReadBytes)))
50 }
51 if resp.ReadError != nil && !errors.Is(resp.ReadError, io.EOF) {
52 // This is not in the semantic conventions, but is historically provided
53 attributes = append(attributes, attribute.String("http.read_error", resp.ReadError.Error()))
54 }
55 if resp.WriteBytes > 0 {
56 attributes = append(attributes, semconv.HTTPResponseContentLength(int(resp.WriteBytes)))
57 }
58 if resp.StatusCode > 0 {
59 attributes = append(attributes, semconv.HTTPStatusCode(resp.StatusCode))
60 }
61 if resp.WriteError != nil && !errors.Is(resp.WriteError, io.EOF) {
62 // This is not in the semantic conventions, but is historically provided
63 attributes = append(attributes, attribute.String("http.write_error", resp.WriteError.Error()))
64 }
65
66 return attributes
67}
68
69// Route returns the attribute for the route.
70func (o oldHTTPServer) Route(route string) attribute.KeyValue {
71 return semconv.HTTPRoute(route)
72}
73
74// HTTPStatusCode returns the attribute for the HTTP status code.
75// This is a temporary function needed by metrics. This will be removed when MetricsRequest is added.
76func HTTPStatusCode(status int) attribute.KeyValue {
77 return semconv.HTTPStatusCode(status)
78}
79
80// Server HTTP metrics.
81const (
82 serverRequestSize = "http.server.request.size" // Incoming request bytes total
83 serverResponseSize = "http.server.response.size" // Incoming response bytes total
84 serverDuration = "http.server.duration" // Incoming end to end duration, milliseconds
85)
86
87func (h oldHTTPServer) createMeasures(meter metric.Meter) (metric.Int64Counter, metric.Int64Counter, metric.Float64Histogram) {
88 if meter == nil {
89 return noop.Int64Counter{}, noop.Int64Counter{}, noop.Float64Histogram{}
90 }
91 var err error
92 requestBytesCounter, err := meter.Int64Counter(
93 serverRequestSize,
94 metric.WithUnit("By"),
95 metric.WithDescription("Measures the size of HTTP request messages."),
96 )
97 handleErr(err)
98
99 responseBytesCounter, err := meter.Int64Counter(
100 serverResponseSize,
101 metric.WithUnit("By"),
102 metric.WithDescription("Measures the size of HTTP response messages."),
103 )
104 handleErr(err)
105
106 serverLatencyMeasure, err := meter.Float64Histogram(
107 serverDuration,
108 metric.WithUnit("ms"),
109 metric.WithDescription("Measures the duration of inbound HTTP requests."),
110 )
111 handleErr(err)
112
113 return requestBytesCounter, responseBytesCounter, serverLatencyMeasure
114}
115
116func (o oldHTTPServer) MetricAttributes(server string, req *http.Request, statusCode int, additionalAttributes []attribute.KeyValue) []attribute.KeyValue {
117 n := len(additionalAttributes) + 3
118 var host string
119 var p int
120 if server == "" {
121 host, p = splitHostPort(req.Host)
122 } else {
123 // Prioritize the primary server name.
124 host, p = splitHostPort(server)
125 if p < 0 {
126 _, p = splitHostPort(req.Host)
127 }
128 }
129 hostPort := requiredHTTPPort(req.TLS != nil, p)
130 if hostPort > 0 {
131 n++
132 }
133 protoName, protoVersion := netProtocol(req.Proto)
134 if protoName != "" {
135 n++
136 }
137 if protoVersion != "" {
138 n++
139 }
140
141 if statusCode > 0 {
142 n++
143 }
144
145 attributes := slices.Grow(additionalAttributes, n)
146 attributes = append(attributes,
147 o.methodMetric(req.Method),
148 o.scheme(req.TLS != nil),
149 semconv.NetHostName(host))
150
151 if hostPort > 0 {
152 attributes = append(attributes, semconv.NetHostPort(hostPort))
153 }
154 if protoName != "" {
155 attributes = append(attributes, semconv.NetProtocolName(protoName))
156 }
157 if protoVersion != "" {
158 attributes = append(attributes, semconv.NetProtocolVersion(protoVersion))
159 }
160
161 if statusCode > 0 {
162 attributes = append(attributes, semconv.HTTPStatusCode(statusCode))
163 }
164 return attributes
165}
166
167func (o oldHTTPServer) methodMetric(method string) attribute.KeyValue {
168 method = strings.ToUpper(method)
169 switch method {
170 case http.MethodConnect, http.MethodDelete, http.MethodGet, http.MethodHead, http.MethodOptions, http.MethodPatch, http.MethodPost, http.MethodPut, http.MethodTrace:
171 default:
172 method = "_OTHER"
173 }
174 return semconv.HTTPMethod(method)
175}
176
177func (o oldHTTPServer) scheme(https bool) attribute.KeyValue { // nolint:revive
178 if https {
179 return semconv.HTTPSchemeHTTPS
180 }
181 return semconv.HTTPSchemeHTTP
182}
183
184type oldHTTPClient struct{}
185
186func (o oldHTTPClient) RequestTraceAttrs(req *http.Request) []attribute.KeyValue {
187 return semconvutil.HTTPClientRequest(req)
188}
189
190func (o oldHTTPClient) ResponseTraceAttrs(resp *http.Response) []attribute.KeyValue {
191 return semconvutil.HTTPClientResponse(resp)
192}