File: v1.20.0.go

package info (click to toggle)
golang-opentelemetry-contrib 0.56.0-5
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 4,884 kB
  • sloc: makefile: 278; sh: 211; sed: 1
file content (274 lines) | stat: -rw-r--r-- 8,665 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package semconv // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconv"

import (
	"errors"
	"io"
	"net/http"
	"slices"
	"strings"

	"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconvutil"
	"go.opentelemetry.io/otel/attribute"
	"go.opentelemetry.io/otel/metric"
	"go.opentelemetry.io/otel/metric/noop"
	semconv "go.opentelemetry.io/otel/semconv/v1.20.0"
)

type oldHTTPServer struct{}

// RequestTraceAttrs returns trace attributes for an HTTP request received by a
// server.
//
// The server must be the primary server name if it is known. For example this
// would be the ServerName directive
// (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache
// server, and the server_name directive
// (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an
// nginx server. More generically, the primary server name would be the host
// header value that matches the default virtual host of an HTTP server. It
// should include the host identifier and if a port is used to route to the
// server that port identifier should be included as an appropriate port
// suffix.
//
// If the primary server name is not known, server should be an empty string.
// The req Host will be used to determine the server instead.
func (o oldHTTPServer) RequestTraceAttrs(server string, req *http.Request) []attribute.KeyValue {
	return semconvutil.HTTPServerRequest(server, req)
}

// ResponseTraceAttrs returns trace attributes for telemetry from an HTTP response.
//
// If any of the fields in the ResponseTelemetry are not set the attribute will be omitted.
func (o oldHTTPServer) ResponseTraceAttrs(resp ResponseTelemetry) []attribute.KeyValue {
	attributes := []attribute.KeyValue{}

	if resp.ReadBytes > 0 {
		attributes = append(attributes, semconv.HTTPRequestContentLength(int(resp.ReadBytes)))
	}
	if resp.ReadError != nil && !errors.Is(resp.ReadError, io.EOF) {
		// This is not in the semantic conventions, but is historically provided
		attributes = append(attributes, attribute.String("http.read_error", resp.ReadError.Error()))
	}
	if resp.WriteBytes > 0 {
		attributes = append(attributes, semconv.HTTPResponseContentLength(int(resp.WriteBytes)))
	}
	if resp.StatusCode > 0 {
		attributes = append(attributes, semconv.HTTPStatusCode(resp.StatusCode))
	}
	if resp.WriteError != nil && !errors.Is(resp.WriteError, io.EOF) {
		// This is not in the semantic conventions, but is historically provided
		attributes = append(attributes, attribute.String("http.write_error", resp.WriteError.Error()))
	}

	return attributes
}

// Route returns the attribute for the route.
func (o oldHTTPServer) Route(route string) attribute.KeyValue {
	return semconv.HTTPRoute(route)
}

// HTTPStatusCode returns the attribute for the HTTP status code.
// This is a temporary function needed by metrics.  This will be removed when MetricsRequest is added.
func HTTPStatusCode(status int) attribute.KeyValue {
	return semconv.HTTPStatusCode(status)
}

// Server HTTP metrics.
const (
	serverRequestSize  = "http.server.request.size"  // Incoming request bytes total
	serverResponseSize = "http.server.response.size" // Incoming response bytes total
	serverDuration     = "http.server.duration"      // Incoming end to end duration, milliseconds
)

func (h oldHTTPServer) createMeasures(meter metric.Meter) (metric.Int64Counter, metric.Int64Counter, metric.Float64Histogram) {
	if meter == nil {
		return noop.Int64Counter{}, noop.Int64Counter{}, noop.Float64Histogram{}
	}
	var err error
	requestBytesCounter, err := meter.Int64Counter(
		serverRequestSize,
		metric.WithUnit("By"),
		metric.WithDescription("Measures the size of HTTP request messages."),
	)
	handleErr(err)

	responseBytesCounter, err := meter.Int64Counter(
		serverResponseSize,
		metric.WithUnit("By"),
		metric.WithDescription("Measures the size of HTTP response messages."),
	)
	handleErr(err)

	serverLatencyMeasure, err := meter.Float64Histogram(
		serverDuration,
		metric.WithUnit("ms"),
		metric.WithDescription("Measures the duration of inbound HTTP requests."),
	)
	handleErr(err)

	return requestBytesCounter, responseBytesCounter, serverLatencyMeasure
}

func (o oldHTTPServer) MetricAttributes(server string, req *http.Request, statusCode int, additionalAttributes []attribute.KeyValue) []attribute.KeyValue {
	n := len(additionalAttributes) + 3
	var host string
	var p int
	if server == "" {
		host, p = splitHostPort(req.Host)
	} else {
		// Prioritize the primary server name.
		host, p = splitHostPort(server)
		if p < 0 {
			_, p = splitHostPort(req.Host)
		}
	}
	hostPort := requiredHTTPPort(req.TLS != nil, p)
	if hostPort > 0 {
		n++
	}
	protoName, protoVersion := netProtocol(req.Proto)
	if protoName != "" {
		n++
	}
	if protoVersion != "" {
		n++
	}

	if statusCode > 0 {
		n++
	}

	attributes := slices.Grow(additionalAttributes, n)
	attributes = append(attributes,
		standardizeHTTPMethodMetric(req.Method),
		o.scheme(req.TLS != nil),
		semconv.NetHostName(host))

	if hostPort > 0 {
		attributes = append(attributes, semconv.NetHostPort(hostPort))
	}
	if protoName != "" {
		attributes = append(attributes, semconv.NetProtocolName(protoName))
	}
	if protoVersion != "" {
		attributes = append(attributes, semconv.NetProtocolVersion(protoVersion))
	}

	if statusCode > 0 {
		attributes = append(attributes, semconv.HTTPStatusCode(statusCode))
	}
	return attributes
}

func (o oldHTTPServer) scheme(https bool) attribute.KeyValue { // nolint:revive
	if https {
		return semconv.HTTPSchemeHTTPS
	}
	return semconv.HTTPSchemeHTTP
}

type oldHTTPClient struct{}

func (o oldHTTPClient) RequestTraceAttrs(req *http.Request) []attribute.KeyValue {
	return semconvutil.HTTPClientRequest(req)
}

func (o oldHTTPClient) ResponseTraceAttrs(resp *http.Response) []attribute.KeyValue {
	return semconvutil.HTTPClientResponse(resp)
}

func (o oldHTTPClient) MetricAttributes(req *http.Request, statusCode int, additionalAttributes []attribute.KeyValue) []attribute.KeyValue {
	/* The following semantic conventions are returned if present:
	http.method                     string
	http.status_code             int
	net.peer.name                   string
	net.peer.port                   int
	*/

	n := 2 // method, peer name.
	var h string
	if req.URL != nil {
		h = req.URL.Host
	}
	var requestHost string
	var requestPort int
	for _, hostport := range []string{h, req.Header.Get("Host")} {
		requestHost, requestPort = splitHostPort(hostport)
		if requestHost != "" || requestPort > 0 {
			break
		}
	}

	port := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", requestPort)
	if port > 0 {
		n++
	}

	if statusCode > 0 {
		n++
	}

	attributes := slices.Grow(additionalAttributes, n)
	attributes = append(attributes,
		standardizeHTTPMethodMetric(req.Method),
		semconv.NetPeerName(requestHost),
	)

	if port > 0 {
		attributes = append(attributes, semconv.NetPeerPort(port))
	}

	if statusCode > 0 {
		attributes = append(attributes, semconv.HTTPStatusCode(statusCode))
	}
	return attributes
}

// Client HTTP metrics.
const (
	clientRequestSize  = "http.client.request.size"  // Incoming request bytes total
	clientResponseSize = "http.client.response.size" // Incoming response bytes total
	clientDuration     = "http.client.duration"      // Incoming end to end duration, milliseconds
)

func (o oldHTTPClient) createMeasures(meter metric.Meter) (metric.Int64Counter, metric.Int64Counter, metric.Float64Histogram) {
	if meter == nil {
		return noop.Int64Counter{}, noop.Int64Counter{}, noop.Float64Histogram{}
	}
	requestBytesCounter, err := meter.Int64Counter(
		clientRequestSize,
		metric.WithUnit("By"),
		metric.WithDescription("Measures the size of HTTP request messages."),
	)
	handleErr(err)

	responseBytesCounter, err := meter.Int64Counter(
		clientResponseSize,
		metric.WithUnit("By"),
		metric.WithDescription("Measures the size of HTTP response messages."),
	)
	handleErr(err)

	latencyMeasure, err := meter.Float64Histogram(
		clientDuration,
		metric.WithUnit("ms"),
		metric.WithDescription("Measures the duration of outbound HTTP requests."),
	)
	handleErr(err)

	return requestBytesCounter, responseBytesCounter, latencyMeasure
}

func standardizeHTTPMethodMetric(method string) attribute.KeyValue {
	method = strings.ToUpper(method)
	switch method {
	case http.MethodConnect, http.MethodDelete, http.MethodGet, http.MethodHead, http.MethodOptions, http.MethodPatch, http.MethodPost, http.MethodPut, http.MethodTrace:
	default:
		method = "_OTHER"
	}
	return semconv.HTTPMethod(method)
}