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 "context"
8 "fmt"
9 "net/http"
10 "os"
11 "strings"
12
13 "go.opentelemetry.io/otel/attribute"
14 "go.opentelemetry.io/otel/codes"
15 "go.opentelemetry.io/otel/metric"
16)
17
18type ResponseTelemetry struct {
19 StatusCode int
20 ReadBytes int64
21 ReadError error
22 WriteBytes int64
23 WriteError error
24}
25
26type HTTPServer struct {
27 duplicate bool
28
29 // Old metrics
30 requestBytesCounter metric.Int64Counter
31 responseBytesCounter metric.Int64Counter
32 serverLatencyMeasure metric.Float64Histogram
33}
34
35// RequestTraceAttrs returns trace attributes for an HTTP request received by a
36// server.
37//
38// The server must be the primary server name if it is known. For example this
39// would be the ServerName directive
40// (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache
41// server, and the server_name directive
42// (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an
43// nginx server. More generically, the primary server name would be the host
44// header value that matches the default virtual host of an HTTP server. It
45// should include the host identifier and if a port is used to route to the
46// server that port identifier should be included as an appropriate port
47// suffix.
48//
49// If the primary server name is not known, server should be an empty string.
50// The req Host will be used to determine the server instead.
51func (s HTTPServer) RequestTraceAttrs(server string, req *http.Request) []attribute.KeyValue {
52 if s.duplicate {
53 return append(oldHTTPServer{}.RequestTraceAttrs(server, req), newHTTPServer{}.RequestTraceAttrs(server, req)...)
54 }
55 return oldHTTPServer{}.RequestTraceAttrs(server, req)
56}
57
58// ResponseTraceAttrs returns trace attributes for telemetry from an HTTP response.
59//
60// If any of the fields in the ResponseTelemetry are not set the attribute will be omitted.
61func (s HTTPServer) ResponseTraceAttrs(resp ResponseTelemetry) []attribute.KeyValue {
62 if s.duplicate {
63 return append(oldHTTPServer{}.ResponseTraceAttrs(resp), newHTTPServer{}.ResponseTraceAttrs(resp)...)
64 }
65 return oldHTTPServer{}.ResponseTraceAttrs(resp)
66}
67
68// Route returns the attribute for the route.
69func (s HTTPServer) Route(route string) attribute.KeyValue {
70 return oldHTTPServer{}.Route(route)
71}
72
73// Status returns a span status code and message for an HTTP status code
74// value returned by a server. Status codes in the 400-499 range are not
75// returned as errors.
76func (s HTTPServer) Status(code int) (codes.Code, string) {
77 if code < 100 || code >= 600 {
78 return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code)
79 }
80 if code >= 500 {
81 return codes.Error, ""
82 }
83 return codes.Unset, ""
84}
85
86type MetricData struct {
87 ServerName string
88 Req *http.Request
89 StatusCode int
90 AdditionalAttributes []attribute.KeyValue
91
92 RequestSize int64
93 ResponseSize int64
94 ElapsedTime float64
95}
96
97func (s HTTPServer) RecordMetrics(ctx context.Context, md MetricData) {
98 if s.requestBytesCounter == nil || s.responseBytesCounter == nil || s.serverLatencyMeasure == nil {
99 // This will happen if an HTTPServer{} is used insted of NewHTTPServer.
100 return
101 }
102
103 attributes := oldHTTPServer{}.MetricAttributes(md.ServerName, md.Req, md.StatusCode, md.AdditionalAttributes)
104 o := metric.WithAttributeSet(attribute.NewSet(attributes...))
105 addOpts := []metric.AddOption{o} // Allocate vararg slice once.
106 s.requestBytesCounter.Add(ctx, md.RequestSize, addOpts...)
107 s.responseBytesCounter.Add(ctx, md.ResponseSize, addOpts...)
108 s.serverLatencyMeasure.Record(ctx, md.ElapsedTime, o)
109
110 // TODO: Duplicate Metrics
111}
112
113func NewHTTPServer(meter metric.Meter) HTTPServer {
114 env := strings.ToLower(os.Getenv("OTEL_SEMCONV_STABILITY_OPT_IN"))
115 duplicate := env == "http/dup"
116 server := HTTPServer{
117 duplicate: duplicate,
118 }
119 server.requestBytesCounter, server.responseBytesCounter, server.serverLatencyMeasure = oldHTTPServer{}.createMeasures(meter)
120 return server
121}
122
123type HTTPClient struct {
124 duplicate bool
125}
126
127func NewHTTPClient() HTTPClient {
128 env := strings.ToLower(os.Getenv("OTEL_SEMCONV_STABILITY_OPT_IN"))
129 return HTTPClient{duplicate: env == "http/dup"}
130}
131
132// RequestTraceAttrs returns attributes for an HTTP request made by a client.
133func (c HTTPClient) RequestTraceAttrs(req *http.Request) []attribute.KeyValue {
134 if c.duplicate {
135 return append(oldHTTPClient{}.RequestTraceAttrs(req), newHTTPClient{}.RequestTraceAttrs(req)...)
136 }
137 return oldHTTPClient{}.RequestTraceAttrs(req)
138}
139
140// ResponseTraceAttrs returns metric attributes for an HTTP request made by a client.
141func (c HTTPClient) ResponseTraceAttrs(resp *http.Response) []attribute.KeyValue {
142 if c.duplicate {
143 return append(oldHTTPClient{}.ResponseTraceAttrs(resp), newHTTPClient{}.ResponseTraceAttrs(resp)...)
144 }
145
146 return oldHTTPClient{}.ResponseTraceAttrs(resp)
147}
148
149func (c HTTPClient) Status(code int) (codes.Code, string) {
150 if code < 100 || code >= 600 {
151 return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code)
152 }
153 if code >= 400 {
154 return codes.Error, ""
155 }
156 return codes.Unset, ""
157}
158
159func (c HTTPClient) ErrorType(err error) attribute.KeyValue {
160 if c.duplicate {
161 return newHTTPClient{}.ErrorType(err)
162 }
163
164 return attribute.KeyValue{}
165}