diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a1eda5dcd7..21cef2402fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm The package contains semantic conventions from the `v1.12.0` version of the OpenTelemetry specification. (#3010) - Add the `go.opentelemetry.io/otel/semconv/v1.11.0` package. The package contains semantic conventions from the `v1.11.0` version of the OpenTelemetry specification. (#3009) +- Add http.method attribute to http server metric. (#3018) ## [1.8.0/0.31.0] - 2022-07-08 diff --git a/semconv/internal/http.go b/semconv/internal/http.go index 864ba3f39df..b580eedeff7 100644 --- a/semconv/internal/http.go +++ b/semconv/internal/http.go @@ -147,12 +147,6 @@ func (sc *SemanticConventions) EndUserAttributesFromHTTPRequest(request *http.Re func (sc *SemanticConventions) HTTPClientAttributesFromHTTPRequest(request *http.Request) []attribute.KeyValue { attrs := []attribute.KeyValue{} - if request.Method != "" { - attrs = append(attrs, sc.HTTPMethodKey.String(request.Method)) - } else { - attrs = append(attrs, sc.HTTPMethodKey.String(http.MethodGet)) - } - // remove any username/password info that may be in the URL // before adding it to the attributes userinfo := request.URL.User @@ -204,6 +198,12 @@ func (sc *SemanticConventions) httpBasicAttributesFromHTTPRequest(request *http. attrs = append(attrs, sc.HTTPFlavorKey.String(flavor)) } + if request.Method != "" { + attrs = append(attrs, sc.HTTPMethodKey.String(request.Method)) + } else { + attrs = append(attrs, sc.HTTPMethodKey.String(http.MethodGet)) + } + return attrs } @@ -223,7 +223,6 @@ func (sc *SemanticConventions) HTTPServerMetricAttributesFromHTTPRequest(serverN // supported. func (sc *SemanticConventions) HTTPServerAttributesFromHTTPRequest(serverName, route string, request *http.Request) []attribute.KeyValue { attrs := []attribute.KeyValue{ - sc.HTTPMethodKey.String(request.Method), sc.HTTPTargetKey.String(request.RequestURI), } diff --git a/semconv/internal/http_test.go b/semconv/internal/http_test.go index 302c3e0ea0a..3e42f02faf3 100644 --- a/semconv/internal/http_test.go +++ b/semconv/internal/http_test.go @@ -1236,3 +1236,288 @@ func TestHTTPClientAttributesFromHTTPRequest(t *testing.T) { }) } } + +func TestHTTPServerMetricAttributesFromHTTPRequest(t *testing.T) { + type testcase struct { + name string + serverName string + method string + requestURI string + proto string + remoteAddr string + host string + url *url.URL + header http.Header + tls tlsOption + contentLength int64 + expected []attribute.KeyValue + } + testcases := []testcase{ + { + name: "stripped", + serverName: "", + method: "GET", + requestURI: "/user/123", + proto: "HTTP/1.0", + remoteAddr: "", + host: "", + url: &url.URL{ + Path: "/user/123", + }, + header: nil, + tls: noTLS, + expected: []attribute.KeyValue{ + attribute.String("http.method", "GET"), + attribute.String("http.scheme", "http"), + attribute.String("http.flavor", "1.0"), + }, + }, + { + name: "with server name", + serverName: "my-server-name", + method: "GET", + requestURI: "/user/123", + proto: "HTTP/1.0", + remoteAddr: "", + host: "", + url: &url.URL{ + Path: "/user/123", + }, + header: nil, + tls: noTLS, + expected: []attribute.KeyValue{ + attribute.String("http.method", "GET"), + attribute.String("http.scheme", "http"), + attribute.String("http.flavor", "1.0"), + attribute.String("http.server_name", "my-server-name"), + }, + }, + { + name: "with tls", + serverName: "my-server-name", + method: "GET", + requestURI: "/user/123", + proto: "HTTP/1.0", + remoteAddr: "", + host: "", + url: &url.URL{ + Path: "/user/123", + }, + header: nil, + tls: withTLS, + expected: []attribute.KeyValue{ + attribute.String("http.method", "GET"), + attribute.String("http.scheme", "https"), + attribute.String("http.flavor", "1.0"), + attribute.String("http.server_name", "my-server-name"), + }, + }, + { + name: "with route", + serverName: "my-server-name", + method: "GET", + requestURI: "/user/123", + proto: "HTTP/1.0", + remoteAddr: "", + host: "", + url: &url.URL{ + Path: "/user/123", + }, + header: nil, + tls: withTLS, + expected: []attribute.KeyValue{ + attribute.String("http.method", "GET"), + attribute.String("http.scheme", "https"), + attribute.String("http.flavor", "1.0"), + attribute.String("http.server_name", "my-server-name"), + }, + }, + { + name: "with host", + serverName: "my-server-name", + method: "GET", + requestURI: "/user/123", + proto: "HTTP/1.0", + remoteAddr: "", + host: "example.com", + url: &url.URL{ + Path: "/user/123", + }, + header: nil, + tls: withTLS, + expected: []attribute.KeyValue{ + attribute.String("http.method", "GET"), + attribute.String("http.scheme", "https"), + attribute.String("http.flavor", "1.0"), + attribute.String("http.server_name", "my-server-name"), + attribute.String("http.host", "example.com"), + }, + }, + { + name: "with host fallback", + serverName: "my-server-name", + method: "GET", + requestURI: "/user/123", + proto: "HTTP/1.0", + remoteAddr: "", + host: "", + url: &url.URL{ + Host: "example.com", + Path: "/user/123", + }, + header: nil, + tls: withTLS, + expected: []attribute.KeyValue{ + attribute.String("http.method", "GET"), + attribute.String("http.scheme", "https"), + attribute.String("http.flavor", "1.0"), + attribute.String("http.server_name", "my-server-name"), + attribute.String("http.host", "example.com"), + }, + }, + { + name: "with user agent", + serverName: "my-server-name", + 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: []attribute.KeyValue{ + attribute.String("http.method", "GET"), + attribute.String("http.scheme", "https"), + attribute.String("http.flavor", "1.0"), + attribute.String("http.server_name", "my-server-name"), + attribute.String("http.host", "example.com"), + }, + }, + { + name: "with proxy info", + serverName: "my-server-name", + 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"}, + "X-Forwarded-For": []string{"203.0.113.195, 70.41.3.18, 150.172.238.178"}, + }, + tls: withTLS, + expected: []attribute.KeyValue{ + attribute.String("http.method", "GET"), + attribute.String("http.scheme", "https"), + attribute.String("http.flavor", "1.0"), + attribute.String("http.server_name", "my-server-name"), + attribute.String("http.host", "example.com"), + }, + }, + { + name: "with http 1.1", + serverName: "my-server-name", + 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"}, + "X-Forwarded-For": []string{"1.2.3.4"}, + }, + tls: withTLS, + expected: []attribute.KeyValue{ + attribute.String("http.method", "GET"), + attribute.String("http.scheme", "https"), + attribute.String("http.flavor", "1.1"), + attribute.String("http.server_name", "my-server-name"), + attribute.String("http.host", "example.com"), + }, + }, + { + name: "with http 2", + serverName: "my-server-name", + 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"}, + "X-Forwarded-For": []string{"1.2.3.4"}, + }, + tls: withTLS, + expected: []attribute.KeyValue{ + attribute.String("http.method", "GET"), + attribute.String("http.scheme", "https"), + attribute.String("http.flavor", "2"), + attribute.String("http.server_name", "my-server-name"), + attribute.String("http.host", "example.com"), + }, + }, + } + 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 := sc.HTTPServerMetricAttributesFromHTTPRequest(tc.serverName, r) + assertElementsMatch(t, tc.expected, got, "testcase %d - %s", idx, tc.name) + } +} + +func TestHttpBasicAttributesFromHTTPRequest(t *testing.T) { + type testcase struct { + name string + method string + requestURI string + proto string + remoteAddr string + host string + url *url.URL + header http.Header + tls tlsOption + contentLength int64 + expected []attribute.KeyValue + } + testcases := []testcase{ + { + name: "stripped", + method: "GET", + requestURI: "/user/123", + proto: "HTTP/1.0", + remoteAddr: "", + host: "example.com", + url: &url.URL{ + Path: "/user/123", + }, + header: nil, + tls: noTLS, + expected: []attribute.KeyValue{ + attribute.String("http.method", "GET"), + attribute.String("http.scheme", "http"), + attribute.String("http.flavor", "1.0"), + attribute.String("http.host", "example.com"), + }, + }, + } + 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 := sc.httpBasicAttributesFromHTTPRequest(r) + assertElementsMatch(t, tc.expected, got, "testcase %d - %s", idx, tc.name) + } +}