v1.20.0.go

  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}