Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

otelhttp: client metrics #4707

Merged
merged 16 commits into from Feb 2, 2024
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Expand Up @@ -11,8 +11,13 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
### Added

- Add the new `go.opentelemetry.io/contrib/instrgen` package to provide auto-generated source code instrumentation. (#3068, #3108)
- Add client metric support to `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp`. (#4707)
- Add peer attributes to spans recorded by `NewClientHandler`, `NewServerHandler` in `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc`. (#4873)

### Deprecated

- The `RequestCount`, `RequestContentLength`, `ResponseContentLength`, `ServerLatency` constants in `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp` are deprecated. (#4707)

### Fixed

- Do not panic in `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc` if `MeterProvider` returns a `nil` instrument. (#4875)
Expand Down
Expand Up @@ -51,6 +51,14 @@ func HTTPClientRequest(req *http.Request) []attribute.KeyValue {
return hc.ClientRequest(req)
}

// HTTPClientRequestMetrics returns metric attributes for an HTTP request made by a client.
// The following attributes are always returned: "http.method", "net.peer.name".
// The following attributes are returned if the
// related values are defined in req: "net.peer.port".
func HTTPClientRequestMetrics(req *http.Request) []attribute.KeyValue {
return hc.ClientRequestMetrics(req)
}

// HTTPClientStatus returns a span status code and message for an HTTP status code
// value received by a client.
func HTTPClientStatus(code int) (codes.Code, string) {
Expand Down Expand Up @@ -286,6 +294,38 @@ func (c *httpConv) ClientRequest(req *http.Request) []attribute.KeyValue {
return attrs
}

// ClientRequestMetrics returns metric attributes for an HTTP request made by a client. The
// following attributes are always returned: "http.method", "net.peer.name".
// The following attributes are returned if the related values
// are defined in req: "net.peer.port".
func (c *httpConv) ClientRequestMetrics(req *http.Request) []attribute.KeyValue {
/* The following semantic conventions are returned if present:
http.method string
net.peer.name string
net.peer.port int
*/

n := 2 // method, peer name.
var h string
if req.URL != nil {
h = req.URL.Host
}
peer, p := firstHostPort(h, req.Header.Get("Host"))
port := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", p)
if port > 0 {
n++
}

attrs := make([]attribute.KeyValue, 0, n)
attrs = append(attrs, c.method(req.Method), c.NetConv.PeerName(peer))

if port > 0 {
attrs = append(attrs, c.NetConv.PeerPort(port))
}

return attrs
}

// ServerRequest returns attributes for an HTTP request received by a server.
//
// The server must be the primary server name if it is known. For example this
Expand Down
Expand Up @@ -68,6 +68,29 @@ func TestHTTPSClientRequest(t *testing.T) {
)
}

func TestHTTPSClientRequestMetrics(t *testing.T) {
req := &http.Request{
Method: http.MethodGet,
URL: &url.URL{
Scheme: "https",
Host: "127.0.0.1:443",
Path: "/resource",
},
Proto: "HTTP/1.0",
ProtoMajor: 1,
ProtoMinor: 0,
}

assert.ElementsMatch(
t,
[]attribute.KeyValue{
attribute.String("http.method", "GET"),
attribute.String("net.peer.name", "127.0.0.1"),
},
HTTPClientRequestMetrics(req),
)
}

func TestHTTPClientRequest(t *testing.T) {
const (
user = "alice"
Expand Down Expand Up @@ -105,6 +128,40 @@ func TestHTTPClientRequest(t *testing.T) {
)
}

func TestHTTPClientRequestMetrics(t *testing.T) {
const (
user = "alice"
n = 128
agent = "Go-http-client/1.1"
)
req := &http.Request{
Method: http.MethodGet,
URL: &url.URL{
Scheme: "http",
Host: "127.0.0.1:8080",
Path: "/resource",
},
Proto: "HTTP/1.0",
ProtoMajor: 1,
ProtoMinor: 0,
Header: http.Header{
"User-Agent": []string{agent},
},
ContentLength: n,
}
req.SetBasicAuth(user, "pswrd")

assert.ElementsMatch(
t,
[]attribute.KeyValue{
attribute.String("http.method", "GET"),
attribute.String("net.peer.name", "127.0.0.1"),
attribute.Int("net.peer.port", 8080),
},
HTTPClientRequestMetrics(req),
)
}

func TestHTTPClientRequestRequired(t *testing.T) {
req := new(http.Request)
var got []attribute.KeyValue
Expand Down
Expand Up @@ -51,6 +51,14 @@ func HTTPClientRequest(req *http.Request) []attribute.KeyValue {
return hc.ClientRequest(req)
}

// HTTPClientRequestMetrics returns metric attributes for an HTTP request made by a client.
// The following attributes are always returned: "http.method", "net.peer.name".
// The following attributes are returned if the
// related values are defined in req: "net.peer.port".
func HTTPClientRequestMetrics(req *http.Request) []attribute.KeyValue {
return hc.ClientRequestMetrics(req)
}

// HTTPClientStatus returns a span status code and message for an HTTP status code
// value received by a client.
func HTTPClientStatus(code int) (codes.Code, string) {
Expand Down Expand Up @@ -286,6 +294,38 @@ func (c *httpConv) ClientRequest(req *http.Request) []attribute.KeyValue {
return attrs
}

// ClientRequestMetrics returns metric attributes for an HTTP request made by a client. The
// following attributes are always returned: "http.method", "net.peer.name".
// The following attributes are returned if the related values
// are defined in req: "net.peer.port".
func (c *httpConv) ClientRequestMetrics(req *http.Request) []attribute.KeyValue {
/* The following semantic conventions are returned if present:
http.method string
net.peer.name string
net.peer.port int
*/

n := 2 // method, peer name.
var h string
if req.URL != nil {
h = req.URL.Host
}
peer, p := firstHostPort(h, req.Header.Get("Host"))
port := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", p)
if port > 0 {
n++
}

attrs := make([]attribute.KeyValue, 0, n)
attrs = append(attrs, c.method(req.Method), c.NetConv.PeerName(peer))

if port > 0 {
attrs = append(attrs, c.NetConv.PeerPort(port))
}

return attrs
}

// ServerRequest returns attributes for an HTTP request received by a server.
//
// The server must be the primary server name if it is known. For example this
Expand Down
Expand Up @@ -68,6 +68,29 @@ func TestHTTPSClientRequest(t *testing.T) {
)
}

func TestHTTPSClientRequestMetrics(t *testing.T) {
req := &http.Request{
Method: http.MethodGet,
URL: &url.URL{
Scheme: "https",
Host: "127.0.0.1:443",
Path: "/resource",
},
Proto: "HTTP/1.0",
ProtoMajor: 1,
ProtoMinor: 0,
}

assert.ElementsMatch(
t,
[]attribute.KeyValue{
attribute.String("http.method", "GET"),
attribute.String("net.peer.name", "127.0.0.1"),
},
HTTPClientRequestMetrics(req),
)
}

func TestHTTPClientRequest(t *testing.T) {
const (
user = "alice"
Expand Down Expand Up @@ -105,6 +128,40 @@ func TestHTTPClientRequest(t *testing.T) {
)
}

func TestHTTPClientRequestMetrics(t *testing.T) {
const (
user = "alice"
n = 128
agent = "Go-http-client/1.1"
)
req := &http.Request{
Method: http.MethodGet,
URL: &url.URL{
Scheme: "http",
Host: "127.0.0.1:8080",
Path: "/resource",
},
Proto: "HTTP/1.0",
ProtoMajor: 1,
ProtoMinor: 0,
Header: http.Header{
"User-Agent": []string{agent},
},
ContentLength: n,
}
req.SetBasicAuth(user, "pswrd")

assert.ElementsMatch(
t,
[]attribute.KeyValue{
attribute.String("http.method", "GET"),
attribute.String("net.peer.name", "127.0.0.1"),
attribute.Int("net.peer.port", 8080),
},
HTTPClientRequestMetrics(req),
)
}

func TestHTTPClientRequestRequired(t *testing.T) {
req := new(http.Request)
var got []attribute.KeyValue
Expand Down
Expand Up @@ -51,6 +51,14 @@ func HTTPClientRequest(req *http.Request) []attribute.KeyValue {
return hc.ClientRequest(req)
}

// HTTPClientRequestMetrics returns metric attributes for an HTTP request made by a client.
// The following attributes are always returned: "http.method", "net.peer.name".
// The following attributes are returned if the
// related values are defined in req: "net.peer.port".
func HTTPClientRequestMetrics(req *http.Request) []attribute.KeyValue {
return hc.ClientRequestMetrics(req)
}

// HTTPClientStatus returns a span status code and message for an HTTP status code
// value received by a client.
func HTTPClientStatus(code int) (codes.Code, string) {
Expand Down Expand Up @@ -286,6 +294,38 @@ func (c *httpConv) ClientRequest(req *http.Request) []attribute.KeyValue {
return attrs
}

// ClientRequestMetrics returns metric attributes for an HTTP request made by a client. The
// following attributes are always returned: "http.method", "net.peer.name".
// The following attributes are returned if the related values
// are defined in req: "net.peer.port".
func (c *httpConv) ClientRequestMetrics(req *http.Request) []attribute.KeyValue {
/* The following semantic conventions are returned if present:
http.method string
net.peer.name string
net.peer.port int
*/

n := 2 // method, peer name.
var h string
if req.URL != nil {
h = req.URL.Host
}
peer, p := firstHostPort(h, req.Header.Get("Host"))
port := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", p)
if port > 0 {
n++
}

attrs := make([]attribute.KeyValue, 0, n)
attrs = append(attrs, c.method(req.Method), c.NetConv.PeerName(peer))

if port > 0 {
attrs = append(attrs, c.NetConv.PeerPort(port))
}

return attrs
}

// ServerRequest returns attributes for an HTTP request received by a server.
//
// The server must be the primary server name if it is known. For example this
Expand Down
Expand Up @@ -68,6 +68,29 @@ func TestHTTPSClientRequest(t *testing.T) {
)
}

func TestHTTPSClientRequestMetrics(t *testing.T) {
req := &http.Request{
Method: http.MethodGet,
URL: &url.URL{
Scheme: "https",
Host: "127.0.0.1:443",
Path: "/resource",
},
Proto: "HTTP/1.0",
ProtoMajor: 1,
ProtoMinor: 0,
}

assert.ElementsMatch(
t,
[]attribute.KeyValue{
attribute.String("http.method", "GET"),
attribute.String("net.peer.name", "127.0.0.1"),
},
HTTPClientRequestMetrics(req),
)
}

func TestHTTPClientRequest(t *testing.T) {
const (
user = "alice"
Expand Down Expand Up @@ -105,6 +128,40 @@ func TestHTTPClientRequest(t *testing.T) {
)
}

func TestHTTPClientRequestMetrics(t *testing.T) {
const (
user = "alice"
n = 128
agent = "Go-http-client/1.1"
)
req := &http.Request{
Method: http.MethodGet,
URL: &url.URL{
Scheme: "http",
Host: "127.0.0.1:8080",
Path: "/resource",
},
Proto: "HTTP/1.0",
ProtoMajor: 1,
ProtoMinor: 0,
Header: http.Header{
"User-Agent": []string{agent},
},
ContentLength: n,
}
req.SetBasicAuth(user, "pswrd")

assert.ElementsMatch(
t,
[]attribute.KeyValue{
attribute.String("http.method", "GET"),
attribute.String("net.peer.name", "127.0.0.1"),
attribute.Int("net.peer.port", 8080),
},
HTTPClientRequestMetrics(req),
)
}

func TestHTTPClientRequestRequired(t *testing.T) {
req := new(http.Request)
var got []attribute.KeyValue
Expand Down