Skip to content

Commit

Permalink
otelhttp: client metrics (#4707)
Browse files Browse the repository at this point in the history
  • Loading branch information
Sovietaced committed Feb 2, 2024
1 parent 5047be2 commit b76d81c
Show file tree
Hide file tree
Showing 23 changed files with 1,202 additions and 44 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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 @@ -256,6 +264,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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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 @@ -256,6 +264,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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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 @@ -256,6 +264,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
Original file line number Diff line number Diff line change
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

0 comments on commit b76d81c

Please sign in to comment.