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

Add http content size semantic conventions #905

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- Add `peer.service` semantic attribute. (#898)
- Add database-specific semantic attributes. (#899)
- Add semantic convention for `faas.coldstart` and `container.id`. (#909)
- Add http content size semantic conventions. (#905)
- Include `http.request_content_length` in HTTP request basic attributes. (#905)

### Changed

Expand Down Expand Up @@ -51,6 +53,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- Ensure span status is not set to `Unknown` when no HTTP status code is provided as it is assumed to be `200 OK`. (#908)
- Ensure `httptrace.clientTracer` closes `http.headers` span. (#912)
- Prometheus exporter will not apply stale updates or forget inactive metrics. (#903)
- Add test for api.standard `HTTPClientAttributesFromHTTPRequest`. (#905)
- Bump github.com/golangci/golangci-lint from 1.27.0 to 1.28.1 in /tools. (#901, #913)
- Update otel-colector example to use the v0.5.0 collector. (#915)

Expand Down
3 changes: 3 additions & 0 deletions api/standard/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,9 @@ func httpBasicAttributesFromHTTPRequest(request *http.Request) []kv.KeyValue {
if flavor != "" {
attrs = append(attrs, HTTPFlavorKey.String(flavor))
}
if request.ContentLength > 0 {
attrs = append(attrs, HTTPRequestContentLengthKey.Int64(request.ContentLength))
}

return attrs
}
Expand Down
211 changes: 203 additions & 8 deletions api/standard/http_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -420,14 +420,15 @@ func TestHTTPServerAttributesFromHTTPRequest(t *testing.T) {
serverName string
route string

method string
requestURI string
proto string
remoteAddr string
host string
url *url.URL
header http.Header
tls tlsOption
method string
requestURI string
proto string
remoteAddr string
host string
url *url.URL
header http.Header
tls tlsOption
contentLength int64

expected []otelkv.KeyValue
}
Expand Down Expand Up @@ -658,9 +659,22 @@ func TestHTTPServerAttributesFromHTTPRequest(t *testing.T) {
otelkv.String("http.client_ip", "1.2.3.4"),
},
},
{
name: "with content length",
method: "GET",
requestURI: "/user/123",
contentLength: 100,
expected: []otelkv.KeyValue{
otelkv.String("http.method", "GET"),
otelkv.String("http.target", "/user/123"),
otelkv.String("http.scheme", "http"),
otelkv.Int64("http.request_content_length", 100),
},
},
}
for idx, tc := range testcases {
r := testRequest(tc.method, tc.requestURI, tc.proto, tc.remoteAddr, tc.host, tc.url, tc.header, tc.tls)
r.ContentLength = tc.contentLength
got := HTTPServerAttributesFromHTTPRequest(tc.serverName, tc.route, r)
assertElementsMatch(t, tc.expected, got, "testcase %d - %s", idx, tc.name)
}
Expand Down Expand Up @@ -775,3 +789,184 @@ func kvStr(kvs []otelkv.KeyValue) string {
sb.WriteRune(']')
return sb.String()
}

func TestHTTPClientAttributesFromHTTPRequest(t *testing.T) {
testCases := []struct {
name string

method string
requestURI string
proto string
remoteAddr string
host string
url *url.URL
header http.Header
tls tlsOption
contentLength int64

expected []otelkv.KeyValue
}{
{
name: "stripped",
method: "GET",
requestURI: "/user/123",
proto: "HTTP/1.0",
remoteAddr: "",
host: "",
url: &url.URL{
Path: "/user/123",
},
header: nil,
tls: noTLS,
expected: []otelkv.KeyValue{
otelkv.String("http.method", "GET"),
otelkv.String("http.url", "/user/123"),
otelkv.String("http.scheme", "http"),
otelkv.String("http.flavor", "1.0"),
},
},
{
name: "with tls",
method: "GET",
requestURI: "/user/123",
proto: "HTTP/1.0",
remoteAddr: "",
host: "",
url: &url.URL{
Path: "/user/123",
},
header: nil,
tls: withTLS,
expected: []otelkv.KeyValue{
otelkv.String("http.method", "GET"),
otelkv.String("http.url", "/user/123"),
otelkv.String("http.scheme", "https"),
otelkv.String("http.flavor", "1.0"),
},
},
{
name: "with host",
method: "GET",
requestURI: "/user/123",
proto: "HTTP/1.0",
remoteAddr: "",
host: "example.com",
url: &url.URL{
Path: "/user/123",
},
header: nil,
tls: withTLS,
expected: []otelkv.KeyValue{
otelkv.String("http.method", "GET"),
otelkv.String("http.url", "/user/123"),
otelkv.String("http.scheme", "https"),
otelkv.String("http.flavor", "1.0"),
otelkv.String("http.host", "example.com"),
},
},
{
name: "with user agent",
method: "GET",
requestURI: "/user/123",
proto: "HTTP/1.0",
remoteAddr: "",
host: "example.com",
url: &url.URL{
Path: "/user/123",
},
header: http.Header{
"User-Agent": []string{"foodownloader"},
},
tls: withTLS,
expected: []otelkv.KeyValue{
otelkv.String("http.method", "GET"),
otelkv.String("http.url", "/user/123"),
otelkv.String("http.scheme", "https"),
otelkv.String("http.flavor", "1.0"),
otelkv.String("http.host", "example.com"),
otelkv.String("http.user_agent", "foodownloader"),
},
},
{
name: "with http 1.1",
method: "GET",
requestURI: "/user/123",
proto: "HTTP/1.1",
remoteAddr: "",
host: "example.com",
url: &url.URL{
Path: "/user/123",
},
header: http.Header{
"User-Agent": []string{"foodownloader"},
},
tls: withTLS,
expected: []otelkv.KeyValue{
otelkv.String("http.method", "GET"),
otelkv.String("http.url", "/user/123"),
otelkv.String("http.scheme", "https"),
otelkv.String("http.flavor", "1.1"),
otelkv.String("http.host", "example.com"),
otelkv.String("http.user_agent", "foodownloader"),
},
},
{
name: "with http 2",
method: "GET",
requestURI: "/user/123",
proto: "HTTP/2.0",
remoteAddr: "",
host: "example.com",
url: &url.URL{
Path: "/user/123",
},
header: http.Header{
"User-Agent": []string{"foodownloader"},
},
tls: withTLS,
expected: []otelkv.KeyValue{
otelkv.String("http.method", "GET"),
otelkv.String("http.url", "/user/123"),
otelkv.String("http.scheme", "https"),
otelkv.String("http.flavor", "2"),
otelkv.String("http.host", "example.com"),
otelkv.String("http.user_agent", "foodownloader"),
},
},
{
name: "with content length",
method: "GET",
url: &url.URL{
Path: "/user/123",
},
contentLength: 100,
expected: []otelkv.KeyValue{
otelkv.String("http.method", "GET"),
otelkv.String("http.url", "/user/123"),
otelkv.String("http.scheme", "http"),
otelkv.Int64("http.request_content_length", 100),
},
},
{
name: "with empty method (fallback to GET)",
method: "",
url: &url.URL{
Path: "/user/123",
},
expected: []otelkv.KeyValue{
otelkv.String("http.method", "GET"),
otelkv.String("http.url", "/user/123"),
otelkv.String("http.scheme", "http"),
},
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
r := testRequest(tc.method, tc.requestURI, tc.proto, tc.remoteAddr, tc.host, tc.url, tc.header, tc.tls)
r.ContentLength = tc.contentLength
got := HTTPClientAttributesFromHTTPRequest(r)
assert.ElementsMatch(t, tc.expected, got)
})
}
}
14 changes: 14 additions & 0 deletions api/standard/trace.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,20 @@ const (
// The IP address of the original client behind all proxies, if known
// (e.g. from X-Forwarded-For).
HTTPClientIPKey = kv.Key("http.client_ip")

// The size of the request payload body in bytes.
HTTPRequestContentLengthKey = kv.Key("http.request_content_length")

// The size of the uncompressed request payload body after transport decoding.
// Not set if transport encoding not used.
HTTPRequestContentLengthUncompressedKey = kv.Key("http.request_content_length_uncompressed")

// The size of the response payload body in bytes.
HTTPResponseContentLengthKey = kv.Key("http.response_content_length")

// The size of the uncompressed response payload body after transport decoding.
// Not set if transport encoding not used.
HTTPResponseContentLengthUncompressedKey = kv.Key("http.response_content_length_uncompressed")
)

var (
Expand Down
23 changes: 12 additions & 11 deletions instrumentation/httptrace/httptrace_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,23 +87,24 @@ func TestRoundtrip(t *testing.T) {
address := ts.Listener.Addr()
hp := strings.Split(address.String(), ":")
expectedAttrs = map[kv.Key]string{
standard.HTTPFlavorKey: "1.1",
standard.HTTPHostKey: address.String(),
standard.HTTPMethodKey: "GET",
standard.HTTPSchemeKey: "http",
standard.HTTPTargetKey: "/",
standard.HTTPUserAgentKey: "Go-http-client/1.1",
standard.NetHostIPKey: hp[0],
standard.NetHostPortKey: hp[1],
standard.NetPeerIPKey: "127.0.0.1",
standard.NetTransportKey: "IP.TCP",
standard.HTTPFlavorKey: "1.1",
standard.HTTPHostKey: address.String(),
standard.HTTPMethodKey: "GET",
standard.HTTPSchemeKey: "http",
standard.HTTPTargetKey: "/",
standard.HTTPUserAgentKey: "Go-http-client/1.1",
standard.HTTPRequestContentLengthKey: "3",
standard.NetHostIPKey: hp[0],
standard.NetHostPortKey: hp[1],
standard.NetPeerIPKey: "127.0.0.1",
standard.NetTransportKey: "IP.TCP",
}

client := ts.Client()
err := tr.WithSpan(context.Background(), "test",
func(ctx context.Context) error {
ctx = correlation.ContextWithMap(ctx, correlation.NewMap(correlation.MapUpdate{SingleKV: kv.Key("foo").String("bar")}))
req, _ := http.NewRequest("GET", ts.URL, nil)
req, _ := http.NewRequest("GET", ts.URL, strings.NewReader("foo"))
httptrace.Inject(ctx, req)

res, err := client.Do(req)
Expand Down
4 changes: 3 additions & 1 deletion instrumentation/othttp/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"io/ioutil"
"net/http"
"net/http/httptest"
"strings"
"testing"

"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -56,7 +57,7 @@ func TestHandlerBasics(t *testing.T) {
WithMeter(meter),
)

r, err := http.NewRequest(http.MethodGet, "http://localhost/", nil)
r, err := http.NewRequest(http.MethodGet, "http://localhost/", strings.NewReader("foo"))
if err != nil {
t.Fatal(err)
}
Expand All @@ -71,6 +72,7 @@ func TestHandlerBasics(t *testing.T) {
standard.HTTPSchemeHTTP,
standard.HTTPHostKey.String(r.Host),
standard.HTTPFlavorKey.String(fmt.Sprintf("1.%d", r.ProtoMinor)),
standard.HTTPRequestContentLengthKey.Int64(3),
}

assertMetricLabels(t, labelsToVerify, meterimpl.MeasurementBatches)
Expand Down