You've already forked opentelemetry-go
mirror of
https://github.com/open-telemetry/opentelemetry-go.git
synced 2025-07-17 01:12:45 +02:00
Add http content size semantic conventions (#905)
* Add http content size to standard package Signed-off-by: Sam Xie <xsambundy@gmail.com> * Include `http.request_content_length` in HTTP request basic attributes Signed-off-by: Sam Xie <xsambundy@gmail.com> * Add test for api.standard package Signed-off-by: Sam Xie <xsambundy@gmail.com> * Update CHANGELOG Signed-off-by: Sam Xie <xsambundy@gmail.com> * Fix http content size naming Signed-off-by: Sam Xie <xsambundy@gmail.com> Co-authored-by: Tyler Yahn <MrAlias@users.noreply.github.com>
This commit is contained in:
@ -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 `peer.service` semantic attribute. (#898)
|
||||||
- Add database-specific semantic attributes. (#899)
|
- Add database-specific semantic attributes. (#899)
|
||||||
- Add semantic convention for `faas.coldstart` and `container.id`. (#909)
|
- 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
|
### Changed
|
||||||
|
|
||||||
@ -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 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)
|
- Ensure `httptrace.clientTracer` closes `http.headers` span. (#912)
|
||||||
- Prometheus exporter will not apply stale updates or forget inactive metrics. (#903)
|
- 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)
|
- 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)
|
- Update otel-colector example to use the v0.5.0 collector. (#915)
|
||||||
|
|
||||||
|
@ -177,6 +177,9 @@ func httpBasicAttributesFromHTTPRequest(request *http.Request) []kv.KeyValue {
|
|||||||
if flavor != "" {
|
if flavor != "" {
|
||||||
attrs = append(attrs, HTTPFlavorKey.String(flavor))
|
attrs = append(attrs, HTTPFlavorKey.String(flavor))
|
||||||
}
|
}
|
||||||
|
if request.ContentLength > 0 {
|
||||||
|
attrs = append(attrs, HTTPRequestContentLengthKey.Int64(request.ContentLength))
|
||||||
|
}
|
||||||
|
|
||||||
return attrs
|
return attrs
|
||||||
}
|
}
|
||||||
|
@ -420,14 +420,15 @@ func TestHTTPServerAttributesFromHTTPRequest(t *testing.T) {
|
|||||||
serverName string
|
serverName string
|
||||||
route string
|
route string
|
||||||
|
|
||||||
method string
|
method string
|
||||||
requestURI string
|
requestURI string
|
||||||
proto string
|
proto string
|
||||||
remoteAddr string
|
remoteAddr string
|
||||||
host string
|
host string
|
||||||
url *url.URL
|
url *url.URL
|
||||||
header http.Header
|
header http.Header
|
||||||
tls tlsOption
|
tls tlsOption
|
||||||
|
contentLength int64
|
||||||
|
|
||||||
expected []otelkv.KeyValue
|
expected []otelkv.KeyValue
|
||||||
}
|
}
|
||||||
@ -658,9 +659,22 @@ func TestHTTPServerAttributesFromHTTPRequest(t *testing.T) {
|
|||||||
otelkv.String("http.client_ip", "1.2.3.4"),
|
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 {
|
for idx, tc := range testcases {
|
||||||
r := testRequest(tc.method, tc.requestURI, tc.proto, tc.remoteAddr, tc.host, tc.url, tc.header, tc.tls)
|
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)
|
got := HTTPServerAttributesFromHTTPRequest(tc.serverName, tc.route, r)
|
||||||
assertElementsMatch(t, tc.expected, got, "testcase %d - %s", idx, tc.name)
|
assertElementsMatch(t, tc.expected, got, "testcase %d - %s", idx, tc.name)
|
||||||
}
|
}
|
||||||
@ -775,3 +789,184 @@ func kvStr(kvs []otelkv.KeyValue) string {
|
|||||||
sb.WriteRune(']')
|
sb.WriteRune(']')
|
||||||
return sb.String()
|
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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -111,6 +111,20 @@ const (
|
|||||||
// The IP address of the original client behind all proxies, if known
|
// The IP address of the original client behind all proxies, if known
|
||||||
// (e.g. from X-Forwarded-For).
|
// (e.g. from X-Forwarded-For).
|
||||||
HTTPClientIPKey = kv.Key("http.client_ip")
|
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 (
|
var (
|
||||||
|
@ -87,23 +87,24 @@ func TestRoundtrip(t *testing.T) {
|
|||||||
address := ts.Listener.Addr()
|
address := ts.Listener.Addr()
|
||||||
hp := strings.Split(address.String(), ":")
|
hp := strings.Split(address.String(), ":")
|
||||||
expectedAttrs = map[kv.Key]string{
|
expectedAttrs = map[kv.Key]string{
|
||||||
standard.HTTPFlavorKey: "1.1",
|
standard.HTTPFlavorKey: "1.1",
|
||||||
standard.HTTPHostKey: address.String(),
|
standard.HTTPHostKey: address.String(),
|
||||||
standard.HTTPMethodKey: "GET",
|
standard.HTTPMethodKey: "GET",
|
||||||
standard.HTTPSchemeKey: "http",
|
standard.HTTPSchemeKey: "http",
|
||||||
standard.HTTPTargetKey: "/",
|
standard.HTTPTargetKey: "/",
|
||||||
standard.HTTPUserAgentKey: "Go-http-client/1.1",
|
standard.HTTPUserAgentKey: "Go-http-client/1.1",
|
||||||
standard.NetHostIPKey: hp[0],
|
standard.HTTPRequestContentLengthKey: "3",
|
||||||
standard.NetHostPortKey: hp[1],
|
standard.NetHostIPKey: hp[0],
|
||||||
standard.NetPeerIPKey: "127.0.0.1",
|
standard.NetHostPortKey: hp[1],
|
||||||
standard.NetTransportKey: "IP.TCP",
|
standard.NetPeerIPKey: "127.0.0.1",
|
||||||
|
standard.NetTransportKey: "IP.TCP",
|
||||||
}
|
}
|
||||||
|
|
||||||
client := ts.Client()
|
client := ts.Client()
|
||||||
err := tr.WithSpan(context.Background(), "test",
|
err := tr.WithSpan(context.Background(), "test",
|
||||||
func(ctx context.Context) error {
|
func(ctx context.Context) error {
|
||||||
ctx = correlation.ContextWithMap(ctx, correlation.NewMap(correlation.MapUpdate{SingleKV: kv.Key("foo").String("bar")}))
|
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)
|
httptrace.Inject(ctx, req)
|
||||||
|
|
||||||
res, err := client.Do(req)
|
res, err := client.Do(req)
|
||||||
|
@ -19,6 +19,7 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@ -56,7 +57,7 @@ func TestHandlerBasics(t *testing.T) {
|
|||||||
WithMeter(meter),
|
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 {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -71,6 +72,7 @@ func TestHandlerBasics(t *testing.T) {
|
|||||||
standard.HTTPSchemeHTTP,
|
standard.HTTPSchemeHTTP,
|
||||||
standard.HTTPHostKey.String(r.Host),
|
standard.HTTPHostKey.String(r.Host),
|
||||||
standard.HTTPFlavorKey.String(fmt.Sprintf("1.%d", r.ProtoMinor)),
|
standard.HTTPFlavorKey.String(fmt.Sprintf("1.%d", r.ProtoMinor)),
|
||||||
|
standard.HTTPRequestContentLengthKey.Int64(3),
|
||||||
}
|
}
|
||||||
|
|
||||||
assertMetricLabels(t, labelsToVerify, meterimpl.MeasurementBatches)
|
assertMetricLabels(t, labelsToVerify, meterimpl.MeasurementBatches)
|
||||||
|
Reference in New Issue
Block a user