From 6b6cd97c06eca30782de67e455b9b3048b35ff5d Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Thu, 24 Nov 2022 08:05:21 -0800 Subject: [PATCH 01/15] WIP --- RELEASING.md | 6 +- internal/tools/semconvkit/main.go | 19 +- .../tools/semconvkit/templates/http.go.tmpl | 93 - .../templates/httpconv/http.go.tmpl | 125 ++ .../semconvkit/templates/netconv/net.go.tmpl | 43 + semconv/internal/v2/http.go | 373 ++++ semconv/internal/v2/http_test.go | 1523 ++++++++++++++ semconv/internal/v2/net.go | 70 + semconv/v1.13.0/doc.go | 20 + semconv/v1.13.0/exception.go | 20 + semconv/v1.13.0/http.go | 21 + semconv/v1.13.0/httpconv/http.go | 125 ++ semconv/v1.13.0/netconv/net.go | 43 + semconv/v1.13.0/resource.go | 1049 ++++++++++ semconv/v1.13.0/schema.go | 20 + semconv/v1.13.0/trace.go | 1772 +++++++++++++++++ 16 files changed, 5223 insertions(+), 99 deletions(-) create mode 100644 internal/tools/semconvkit/templates/httpconv/http.go.tmpl create mode 100644 internal/tools/semconvkit/templates/netconv/net.go.tmpl create mode 100644 semconv/internal/v2/http.go create mode 100644 semconv/internal/v2/http_test.go create mode 100644 semconv/internal/v2/net.go create mode 100644 semconv/v1.13.0/doc.go create mode 100644 semconv/v1.13.0/exception.go create mode 100644 semconv/v1.13.0/http.go create mode 100644 semconv/v1.13.0/httpconv/http.go create mode 100644 semconv/v1.13.0/netconv/net.go create mode 100644 semconv/v1.13.0/resource.go create mode 100644 semconv/v1.13.0/schema.go create mode 100644 semconv/v1.13.0/trace.go diff --git a/RELEASING.md b/RELEASING.md index 71e57625479..b6302ac776e 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -6,14 +6,16 @@ New versions of the [OpenTelemetry specification] mean new versions of the `semc The `semconv-generate` make target is used for this. 1. Checkout a local copy of the [OpenTelemetry specification] to the desired release tag. -2. Run the `make semconv-generate ...` target from this repository. +2. Pull the latest `otel/semconvgen` image: `docker pull otel/semconvgen:latest` +3. Run the `make semconv-generate ...` target from this repository. For example, ```sh export TAG="v1.7.0" # Change to the release version you are generating. export OTEL_SPEC_REPO="/absolute/path/to/opentelemetry-specification" -git -C "$OTEL_SPEC_REPO" checkout "tags/$TAG" +git -C "$OTEL_SPEC_REPO" checkout "tags/$TAG" -b "$TAG" +docker pull otel/semconvgen:latest make semconv-generate # Uses the exported TAG and OTEL_SPEC_REPO. ``` diff --git a/internal/tools/semconvkit/main.go b/internal/tools/semconvkit/main.go index 913a9ac126f..d3d1c1a2e5f 100644 --- a/internal/tools/semconvkit/main.go +++ b/internal/tools/semconvkit/main.go @@ -21,6 +21,7 @@ package main import ( "embed" "flag" + "fmt" "log" "os" "path/filepath" @@ -32,7 +33,7 @@ var ( out = flag.String("output", "./", "output directory") tag = flag.String("tag", "", "OpenTelemetry tagged version") - //go:embed templates/*.tmpl + //go:embed templates/*.tmpl templates/netconv/*.tmpl templates/httpconv/*.tmpl rootFS embed.FS ) @@ -48,8 +49,8 @@ func (sc SemanticConventions) SemVer() string { } // render renders all templates to the dest directory using the data. -func render(dest string, data *SemanticConventions) error { - tmpls, err := template.ParseFS(rootFS, "templates/*.tmpl") +func render(src, dest string, data *SemanticConventions) error { + tmpls, err := template.ParseFS(rootFS, src) if err != nil { return err } @@ -78,7 +79,17 @@ func main() { sc := &SemanticConventions{TagVer: *tag} - if err := render(*out, sc); err != nil { + if err := render("templates/*.tmpl", *out, sc); err != nil { + log.Fatal(err) + } + + dest := fmt.Sprintf("%s/netconv", *out) + if err := render("templates/netconv/*.tmpl", dest, sc); err != nil { + log.Fatal(err) + } + + dest = fmt.Sprintf("%s/httpconv", *out) + if err := render("templates/httpconv/*.tmpl", dest, sc); err != nil { log.Fatal(err) } } diff --git a/internal/tools/semconvkit/templates/http.go.tmpl b/internal/tools/semconvkit/templates/http.go.tmpl index e1334d501d4..06d08932590 100644 --- a/internal/tools/semconvkit/templates/http.go.tmpl +++ b/internal/tools/semconvkit/templates/http.go.tmpl @@ -14,101 +14,8 @@ package semconv // import "go.opentelemetry.io/otel/semconv/{{.TagVer}}" -import ( - "net/http" - - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/codes" - "go.opentelemetry.io/otel/semconv/internal" - "go.opentelemetry.io/otel/trace" -) - // HTTP scheme attributes. var ( HTTPSchemeHTTP = HTTPSchemeKey.String("http") HTTPSchemeHTTPS = HTTPSchemeKey.String("https") ) - -var sc = &internal.SemanticConventions{ - EnduserIDKey: EnduserIDKey, - HTTPClientIPKey: HTTPClientIPKey, - HTTPFlavorKey: HTTPFlavorKey, - HTTPHostKey: HTTPHostKey, - HTTPMethodKey: HTTPMethodKey, - HTTPRequestContentLengthKey: HTTPRequestContentLengthKey, - HTTPRouteKey: HTTPRouteKey, - HTTPSchemeHTTP: HTTPSchemeHTTP, - HTTPSchemeHTTPS: HTTPSchemeHTTPS, - HTTPServerNameKey: HTTPServerNameKey, - HTTPStatusCodeKey: HTTPStatusCodeKey, - HTTPTargetKey: HTTPTargetKey, - HTTPURLKey: HTTPURLKey, - HTTPUserAgentKey: HTTPUserAgentKey, - NetHostIPKey: NetHostIPKey, - NetHostNameKey: NetHostNameKey, - NetHostPortKey: NetHostPortKey, - NetPeerIPKey: NetPeerIPKey, - NetPeerNameKey: NetPeerNameKey, - NetPeerPortKey: NetPeerPortKey, - NetTransportIP: NetTransportIP, - NetTransportOther: NetTransportOther, - NetTransportTCP: NetTransportTCP, - NetTransportUDP: NetTransportUDP, - NetTransportUnix: NetTransportUnix, -} - -// NetAttributesFromHTTPRequest generates attributes of the net -// namespace as specified by the OpenTelemetry specification for a -// span. The network parameter is a string that net.Dial function -// from standard library can understand. -func NetAttributesFromHTTPRequest(network string, request *http.Request) []attribute.KeyValue { - return sc.NetAttributesFromHTTPRequest(network, request) -} - -// EndUserAttributesFromHTTPRequest generates attributes of the -// enduser namespace as specified by the OpenTelemetry specification -// for a span. -func EndUserAttributesFromHTTPRequest(request *http.Request) []attribute.KeyValue { - return sc.EndUserAttributesFromHTTPRequest(request) -} - -// HTTPClientAttributesFromHTTPRequest generates attributes of the -// http namespace as specified by the OpenTelemetry specification for -// a span on the client side. -func HTTPClientAttributesFromHTTPRequest(request *http.Request) []attribute.KeyValue { - return sc.HTTPClientAttributesFromHTTPRequest(request) -} - -// HTTPServerMetricAttributesFromHTTPRequest generates low-cardinality attributes -// to be used with server-side HTTP metrics. -func HTTPServerMetricAttributesFromHTTPRequest(serverName string, request *http.Request) []attribute.KeyValue { - return sc.HTTPServerMetricAttributesFromHTTPRequest(serverName, request) -} - -// HTTPServerAttributesFromHTTPRequest generates attributes of the -// http namespace as specified by the OpenTelemetry specification for -// a span on the server side. Currently, only basic authentication is -// supported. -func HTTPServerAttributesFromHTTPRequest(serverName, route string, request *http.Request) []attribute.KeyValue { - return sc.HTTPServerAttributesFromHTTPRequest(serverName, route, request) -} - -// HTTPAttributesFromHTTPStatusCode generates attributes of the http -// namespace as specified by the OpenTelemetry specification for a -// span. -func HTTPAttributesFromHTTPStatusCode(code int) []attribute.KeyValue { - return sc.HTTPAttributesFromHTTPStatusCode(code) -} - -// SpanStatusFromHTTPStatusCode generates a status code and a message -// as specified by the OpenTelemetry specification for a span. -func SpanStatusFromHTTPStatusCode(code int) (codes.Code, string) { - return internal.SpanStatusFromHTTPStatusCode(code) -} - -// SpanStatusFromHTTPStatusCodeAndSpanKind generates a status code and a message -// as specified by the OpenTelemetry specification for a span. -// Exclude 4xx for SERVER to set the appropriate status. -func SpanStatusFromHTTPStatusCodeAndSpanKind(code int, spanKind trace.SpanKind) (codes.Code, string) { - return internal.SpanStatusFromHTTPStatusCodeAndSpanKind(code, spanKind) -} diff --git a/internal/tools/semconvkit/templates/httpconv/http.go.tmpl b/internal/tools/semconvkit/templates/httpconv/http.go.tmpl new file mode 100644 index 00000000000..ff9e1a008a8 --- /dev/null +++ b/internal/tools/semconvkit/templates/httpconv/http.go.tmpl @@ -0,0 +1,125 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package httpconv provides OpenTelemetry semantic convetions for the net/http +// package from the standard library. +package httpconv // import "go.opentelemetry.io/otel/semconv/{{.TagVer}}/httpconv" + +import ( + "net/http" + + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" + "go.opentelemetry.io/otel/semconv/internal/v2" + semconv "go.opentelemetry.io/otel/semconv/{{.TagVer}}" +) + +var ( + nc = &internal.NetConv{ + NetHostNameKey: semconv.NetHostNameKey, + NetHostPortKey: semconv.NetHostPortKey, + NetPeerNameKey: semconv.NetPeerNameKey, + NetPeerPortKey: semconv.NetPeerPortKey, + NetSockPeerAddrKey: semconv.NetSockPeerAddrKey, + NetSockPeerPortKey: semconv.NetSockPeerPortKey, + NetTransportOther: semconv.NetTransportOther, + NetTransportTCP: semconv.NetTransportTCP, + NetTransportUDP: semconv.NetTransportUDP, + NetTransportInProc: semconv.NetTransportInProc, + } + + hc = &internal.HTTPConv{ + NetConv: nc, + + EnduserIDKey: semconv.EnduserIDKey, + HTTPClientIPKey: semconv.HTTPClientIPKey, + HTTPFlavorKey: semconv.HTTPFlavorKey, + HTTPMethodKey: semconv.HTTPMethodKey, + HTTPRequestContentLengthKey: semconv.HTTPRequestContentLengthKey, + HTTPResponseContentLengthKey: semconv.HTTPResponseContentLengthKey, + HTTPRouteKey: semconv.HTTPRouteKey, + HTTPSchemeHTTP: semconv.HTTPSchemeHTTP, + HTTPSchemeHTTPS: semconv.HTTPSchemeHTTPS, + HTTPStatusCodeKey: semconv.HTTPStatusCodeKey, + HTTPTargetKey: semconv.HTTPTargetKey, + HTTPURLKey: semconv.HTTPURLKey, + HTTPUserAgentKey: semconv.HTTPUserAgentKey, + } +) + +// ClientResponse returns attributes for an HTTP response received by a client +// from a server. +// +// This does not add all OpenTelemetry required attributes for an HTTP event, +// it assumes ClientRequest was used to create the span with a complete set of +// attributes. If a complete set of attributes can be generated using the +// request contained in resp. For example: +// +// append(ClientResponse(resp), ClientRequest(resp.Request)...) +func ClientResponse(resp http.Response) []attribute.KeyValue { + return hc.ClientResponse(resp) +} + +// ClientRequest returns attributes for an HTTP request made by a client. +func ClientRequest(req *http.Request) []attribute.KeyValue { + return hc.ClientRequest(req) +} + +// ClientStatus returns a span status code and message for an HTTP status code +// value received by a client. +func ClientStatus(code int) (codes.Code, string) { + return internal.SpanStatusFromHTTPStatusCode() +} + +// ServerRequest returns attributes for an HTTP request received by a server. +func ServerRequest(req *http.Request) []attribute.KeyValue { + return hc.ServerRequest(req) +} + +// ServerStatus returns a span status code and message for an HTTP status code +// value returned by a server. Status codes in the 400-499 range are not +// returned as errors. +func ServerStatus(code int) (codes.Code, string) { + return hc.ServerStatus(code) +} + +// RequestHeader returns the contents of h as attributes. +// +// Instrumentation should require an explicit configuration of which headers to +// captured and then prune what they pass here. Including all headers can be a +// security risk - explicit configuration helps avoid leaking sensitive +// information. +// +// The User-Agent header is already captured in the http.user_agent attribute +// from ClientRequest and ServerRequest. Instrumentation may provide an option +// to capture that header here even though it is not recommended. Otherwise, +// instrumentation should filter that out of what is passed. +func RequestHeader(h http.Header) []attribute.KeyValue { + return hc.RequestHeader(h) +} + +// ResponseHeader returns the contents of h as attributes. +// +// Instrumentation should require an explicit configuration of which headers to +// captured and then prune what they pass here. Including all headers can be a +// security risk - explicit configuration helps avoid leaking sensitive +// information. +// +// The User-Agent header is already captured in the http.user_agent attribute +// from ClientRequest and ServerRequest. Instrumentation may provide an option +// to capture that header here even though it is not recommended. Otherwise, +// instrumentation should filter that out of what is passed. +func ResponseHeader(h http.Header) []attribute.KeyValue { + return hc.ResponseHeader(h) +} diff --git a/internal/tools/semconvkit/templates/netconv/net.go.tmpl b/internal/tools/semconvkit/templates/netconv/net.go.tmpl new file mode 100644 index 00000000000..11250337232 --- /dev/null +++ b/internal/tools/semconvkit/templates/netconv/net.go.tmpl @@ -0,0 +1,43 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package netconv provides OpenTelemetry semantic convetions for the net +// package from the standard library. +package netconv // import "go.opentelemetry.io/otel/semconv/{{.TagVer}}/netconv" + +import ( + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/semconv/internal/v2" + semconv "go.opentelemetry.io/otel/semconv/{{.TagVer}}" +) + +var nc = &internal.NetConv{ + NetHostNameKey: semconv.NetHostNameKey, + NetHostPortKey: semconv.NetHostPortKey, + NetPeerNameKey: semconv.NetPeerNameKey, + NetPeerPortKey: semconv.NetPeerPortKey, + NetSockPeerAddrKey: semconv.NetSockPeerAddrKey, + NetSockPeerPortKey: semconv.NetSockPeerPortKey, + NetTransportOther: semconv.NetTransportOther, + NetTransportTCP: semconv.NetTransportTCP, + NetTransportUDP: semconv.NetTransportUDP, + NetTransportInProc: semconv.NetTransportInProc, +} + +// Transport returns an attribute describing the transport protocol of the +// passed network. See the net.Dial for information about acceptable network +// values. +func Transport(network string) attribute.KeyValue { + return nc.Transport(network) +} diff --git a/semconv/internal/v2/http.go b/semconv/internal/v2/http.go new file mode 100644 index 00000000000..efc4c0ec08b --- /dev/null +++ b/semconv/internal/v2/http.go @@ -0,0 +1,373 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package internal // import "go.opentelemetry.io/otel/semconv/internal/v2" + +import ( + "fmt" + "net" + "net/http" + "strconv" + "strings" + + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" +) + +// HTTPConv are the HTTP semantic convention attributes defined for a version +// of the OpenTelemetry specification. +type HTTPConv struct { + NetConv *NetConv + + EnduserIDKey attribute.Key + HTTPClientIPKey attribute.Key + HTTPFlavorKey attribute.Key + HTTPMethodKey attribute.Key + HTTPRequestContentLengthKey attribute.Key + HTTPResponseContentLengthKey attribute.Key + HTTPRouteKey attribute.Key + HTTPSchemeHTTP attribute.KeyValue + HTTPSchemeHTTPS attribute.KeyValue + HTTPStatusCodeKey attribute.Key + HTTPTargetKey attribute.Key + HTTPURLKey attribute.Key + HTTPUserAgentKey attribute.Key +} + +// ClientResponse returns OpenTelemetry attributes for an HTTP response +// received by a client from a server. This does not add all OpenTelemetry +// required attributes for an HTTP event, it assumes HTTPClientRequest was used +// to create the span with a complete set of attributes. If a complete set of +// attributes can be generated using the request contained in resp. For +// example: +// +// append(ClientResponse(resp), HTTPClientRequest(resp.Request)...) +func (c *HTTPConv) ClientResponse(resp http.Response) []attribute.KeyValue { + var n int + if resp.StatusCode > 0 { + n++ + } + if resp.ContentLength > 0 { + n++ + } + + attrs := make([]attribute.KeyValue, 0, n) + if resp.StatusCode > 0 { + attrs = append(attrs, c.HTTPStatusCodeKey.Int(resp.StatusCode)) + } + if resp.ContentLength > 0 { + attrs = append(attrs, c.HTTPResponseContentLengthKey.Int(int(resp.ContentLength))) + } + return attrs +} + +// ClientRequest returns OpenTelemetry attributes for an HTTP request made +// by a client. +func (c *HTTPConv) ClientRequest(req *http.Request) []attribute.KeyValue { + n := 3 // URL, peer name, proto, and method. + peer, p := firstHostPort(req.URL.Host, req.Header.Get("Host")) + port := requiredHTTPPort(req.TLS != nil, p) + if port > 0 { + n++ + } + useragent := req.UserAgent() + if useragent != "" { + n++ + } + if req.ContentLength > 0 { + n++ + } + userID, _, hasUserID := req.BasicAuth() + if hasUserID { + n++ + } + attrs := make([]attribute.KeyValue, 0, n) + + attrs = append(attrs, c.method(req.Method)) + attrs = append(attrs, c.proto(req.Proto)) + + // Remove any username/password info that may be in the URL. + userinfo := req.URL.User + req.URL.User = nil + attrs = append(attrs, c.HTTPURLKey.String(req.URL.String())) + // Restore any username/password info that was removed. + req.URL.User = userinfo + + attrs = append(attrs, c.NetConv.PeerName(peer)) + if port > 0 { + attrs = append(attrs, c.NetConv.PeerPort(port)) + } + + if useragent != "" { + attrs = append(attrs, c.HTTPUserAgentKey.String(useragent)) + } + + if l := req.ContentLength; l > 0 { + attrs = append(attrs, c.HTTPRequestContentLengthKey.Int64(l)) + } + + if hasUserID { + attrs = append(attrs, c.EnduserIDKey.String(userID)) + } + + return attrs +} + +func (c *HTTPConv) ServerRequest(req *http.Request) []attribute.KeyValue { + n := 5 // Method, scheme, target, proto, and host name. + host, p := splitHostPort(req.Host) + hostPort := requiredHTTPPort(req.TLS != nil, p) + if hostPort > 0 { + n++ + } + peer, peerPort := splitHostPort(req.RemoteAddr) + if peer != "" { + n++ + if peerPort > 0 { + n++ + } + } + useragent := req.UserAgent() + if useragent != "" { + n++ + } + userID, _, hasUserID := req.BasicAuth() + if hasUserID { + n++ + } + clientIP := serverClientIP(req.Header.Get("X-Forwarded-For")) + if clientIP != "" { + n++ + } + attrs := make([]attribute.KeyValue, 0, n) + + attrs = append(attrs, c.method(req.Method)) + attrs = append(attrs, c.scheme(req.TLS != nil)) + attrs = append(attrs, c.HTTPTargetKey.String(req.URL.RequestURI())) + attrs = append(attrs, c.proto(req.Proto)) + attrs = append(attrs, c.NetConv.HostName(host)) + + if hostPort > 0 { + attrs = append(attrs, c.NetConv.HostPort(hostPort)) + } + + if peer != "" { + // The Go HTTP server sets RemoteAddr to "IP:port", this will not be a + // file-path that would be interpreted with a sock family. + attrs = append(attrs, c.NetConv.SockPeerAddr(peer)) + if peerPort > 0 { + attrs = append(attrs, c.NetConv.SockPeerPort(peerPort)) + } + } + + if useragent != "" { + attrs = append(attrs, c.HTTPUserAgentKey.String(useragent)) + } + + if hasUserID { + attrs = append(attrs, c.EnduserIDKey.String(userID)) + } + + if clientIP != "" { + attrs = append(attrs, c.HTTPClientIPKey.String(clientIP)) + } + + return attrs +} + +func (c *HTTPConv) method(method string) attribute.KeyValue { + if method == "" { + return c.HTTPMethodKey.String(http.MethodGet) + } + return c.HTTPMethodKey.String(method) +} + +func (c *HTTPConv) scheme(https bool) attribute.KeyValue { + if https { + return c.HTTPSchemeHTTPS + } + return c.HTTPSchemeHTTP +} + +func (c *HTTPConv) proto(proto string) attribute.KeyValue { + switch proto { + case "HTTP/1.0": + return c.HTTPFlavorKey.String("1.0") + case "HTTP/1.1": + return c.HTTPFlavorKey.String("1.1") + case "HTTP/2": + return c.HTTPFlavorKey.String("2.0") + case "HTTP/3": + return c.HTTPFlavorKey.String("3.0") + default: + return c.HTTPFlavorKey.String(proto) + } +} + +func serverClientIP(xForwardedFor string) string { + if idx := strings.Index(xForwardedFor, ","); idx >= 0 { + xForwardedFor = xForwardedFor[:idx] + } + return xForwardedFor +} + +func requiredHTTPPort(https bool, port int) int { + if https { + if port > 0 && port != 443 { + return port + } + } else { + if port > 0 && port != 80 { + return port + } + } + return -1 +} + +// Return the request host and port from the first non-empty source. +func firstHostPort(source ...string) (host string, port int) { + for _, hostport := range source { + host, port = splitHostPort(hostport) + if host != "" || port > 0 { + break + } + } + return +} + +// splitHostPort splits a network address of the form "host:port", +// "host%zone:port", "[host]:port" or "[host%zone]:port" into host or +// host%zone and port. +// +// A negative port is returned if no parsable port is found. +func splitHostPort(hostport string) (host string, port int) { + host, portStr, err := net.SplitHostPort(hostport) + if err != nil { + return host, -1 + } + p, err := strconv.ParseUint(portStr, 10, 16) + if err != nil { + return host, -1 + } + return host, int(p) +} + +// RequestHeader returns the contents of h as OpenTelemetry attributes. +func (c *HTTPConv) RequestHeader(h http.Header) []attribute.KeyValue { + return c.header("http.request.header", h) +} + +// ResponseHeader returns the contents of h as OpenTelemetry attributes. +func (c *HTTPConv) ResponseHeader(h http.Header) []attribute.KeyValue { + return c.header("http.response.header", h) +} + +func (c *HTTPConv) header(prefix string, h http.Header) []attribute.KeyValue { + key := func(k string) attribute.Key { + k = strings.ToLower(k) + k = strings.ReplaceAll(k, "-", "_") + k = fmt.Sprintf("%s.%s", prefix, k) + return attribute.Key(k) + } + + attrs := make([]attribute.KeyValue, 0, len(h)) + for k, v := range h { + attrs = append(attrs, key(k).StringSlice(v)) + } + return attrs +} + +// ClientStatus returns a span status code and message for an HTTP status code +// value received by a client. +func (c *HTTPConv) ClientStatus(code int) (codes.Code, string) { + stat, valid := validateHTTPStatusCode(code) + if !valid { + return stat, fmt.Sprintf("Invalid HTTP status code %d", code) + } + return stat, "" +} + +// ServerStatus returns a span status code and message for an HTTP status code +// value returned by a server. Status codes in the 400-499 range are not +// returned as errors. +func (c *HTTPConv) ServerStatus(code int) (codes.Code, string) { + stat, valid := validateHTTPStatusCode(code) + if !valid { + return stat, fmt.Sprintf("Invalid HTTP status code %d", code) + } + + if code/100 == 4 { + return codes.Unset, "" + } + return stat, "" +} + +type codeRange struct { + fromInclusive int + toInclusive int +} + +func (r codeRange) contains(code int) bool { + return r.fromInclusive <= code && code <= r.toInclusive +} + +var validRangesPerCategory = map[int][]codeRange{ + 1: { + {http.StatusContinue, http.StatusEarlyHints}, + }, + 2: { + {http.StatusOK, http.StatusAlreadyReported}, + {http.StatusIMUsed, http.StatusIMUsed}, + }, + 3: { + {http.StatusMultipleChoices, http.StatusUseProxy}, + {http.StatusTemporaryRedirect, http.StatusPermanentRedirect}, + }, + 4: { + {http.StatusBadRequest, http.StatusTeapot}, // yes, teapot is so useful… + {http.StatusMisdirectedRequest, http.StatusUpgradeRequired}, + {http.StatusPreconditionRequired, http.StatusTooManyRequests}, + {http.StatusRequestHeaderFieldsTooLarge, http.StatusRequestHeaderFieldsTooLarge}, + {http.StatusUnavailableForLegalReasons, http.StatusUnavailableForLegalReasons}, + }, + 5: { + {http.StatusInternalServerError, http.StatusLoopDetected}, + {http.StatusNotExtended, http.StatusNetworkAuthenticationRequired}, + }, +} + +// validateHTTPStatusCode validates the HTTP status code and returns +// corresponding span status code. If the `code` is not a valid HTTP status +// code, returns span status Error and false. +func validateHTTPStatusCode(code int) (codes.Code, bool) { + category := code / 100 + ranges, ok := validRangesPerCategory[category] + if !ok { + return codes.Error, false + } + ok = false + for _, crange := range ranges { + ok = crange.contains(code) + if ok { + break + } + } + if !ok { + return codes.Error, false + } + if category > 0 && category < 4 { + return codes.Unset, true + } + return codes.Error, true +} diff --git a/semconv/internal/v2/http_test.go b/semconv/internal/v2/http_test.go new file mode 100644 index 00000000000..d478b32c094 --- /dev/null +++ b/semconv/internal/v2/http_test.go @@ -0,0 +1,1523 @@ +// Copyright The OpenTelemetry Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package internal + +import ( + "crypto/tls" + "net/http" + "net/url" + "strings" + "testing" + + "go.opentelemetry.io/otel/trace" + + "github.com/google/go-cmp/cmp" + "github.com/stretchr/testify/assert" + + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" +) + +type tlsOption int + +const ( + noTLS tlsOption = iota + withTLS +) + +var sc = &HTTPConv{ + EnduserIDKey: attribute.Key("enduser.id"), + HTTPClientIPKey: attribute.Key("http.client_ip"), + HTTPFlavorKey: attribute.Key("http.flavor"), + HTTPHostKey: attribute.Key("http.host"), + HTTPMethodKey: attribute.Key("http.method"), + HTTPRequestContentLengthKey: attribute.Key("http.request_content_length"), + HTTPRouteKey: attribute.Key("http.route"), + HTTPSchemeHTTP: attribute.String("http.scheme", "http"), + HTTPSchemeHTTPS: attribute.String("http.scheme", "https"), + HTTPServerNameKey: attribute.Key("http.server_name"), + HTTPStatusCodeKey: attribute.Key("http.status_code"), + HTTPTargetKey: attribute.Key("http.target"), + HTTPURLKey: attribute.Key("http.url"), + HTTPUserAgentKey: attribute.Key("http.user_agent"), + NetHostIPKey: attribute.Key("net.host.ip"), + NetHostNameKey: attribute.Key("net.host.name"), + NetHostPortKey: attribute.Key("net.host.port"), + NetPeerIPKey: attribute.Key("net.peer.ip"), + NetPeerNameKey: attribute.Key("net.peer.name"), + NetPeerPortKey: attribute.Key("net.peer.port"), + NetTransportIP: attribute.String("net.transport", "ip"), + NetTransportOther: attribute.String("net.transport", "other"), + NetTransportTCP: attribute.String("net.transport", "ip_tcp"), + NetTransportUDP: attribute.String("net.transport", "ip_udp"), + NetTransportUnix: attribute.String("net.transport", "unix"), +} + +func TestNetAttributesFromHTTPRequest(t *testing.T) { + type testcase struct { + name string + + network string + + method string + requestURI string + proto string + remoteAddr string + host string + url *url.URL + header http.Header + + expected []attribute.KeyValue + } + testcases := []testcase{ + { + name: "stripped, tcp", + network: "tcp", + method: "GET", + requestURI: "/user/123", + proto: "HTTP/1.0", + remoteAddr: "", + host: "", + url: &url.URL{ + Path: "/user/123", + }, + header: nil, + expected: []attribute.KeyValue{ + attribute.String("net.transport", "ip_tcp"), + }, + }, + { + name: "stripped, udp", + network: "udp", + method: "GET", + requestURI: "/user/123", + proto: "HTTP/1.0", + remoteAddr: "", + host: "", + url: &url.URL{ + Path: "/user/123", + }, + header: nil, + expected: []attribute.KeyValue{ + attribute.String("net.transport", "ip_udp"), + }, + }, + { + name: "stripped, ip", + network: "ip", + method: "GET", + requestURI: "/user/123", + proto: "HTTP/1.0", + remoteAddr: "", + host: "", + url: &url.URL{ + Path: "/user/123", + }, + header: nil, + expected: []attribute.KeyValue{ + attribute.String("net.transport", "ip"), + }, + }, + { + name: "stripped, unix", + network: "unix", + method: "GET", + requestURI: "/user/123", + proto: "HTTP/1.0", + remoteAddr: "", + host: "", + url: &url.URL{ + Path: "/user/123", + }, + header: nil, + expected: []attribute.KeyValue{ + attribute.String("net.transport", "unix"), + }, + }, + { + name: "stripped, other", + network: "nih", + method: "GET", + requestURI: "/user/123", + proto: "HTTP/1.0", + remoteAddr: "", + host: "", + url: &url.URL{ + Path: "/user/123", + }, + header: nil, + expected: []attribute.KeyValue{ + attribute.String("net.transport", "other"), + }, + }, + { + name: "with remote ipv4 and port", + network: "tcp", + method: "GET", + requestURI: "/user/123", + proto: "HTTP/1.0", + remoteAddr: "1.2.3.4:56", + host: "", + url: &url.URL{ + Path: "/user/123", + }, + header: nil, + expected: []attribute.KeyValue{ + attribute.String("net.transport", "ip_tcp"), + attribute.String("net.peer.ip", "1.2.3.4"), + attribute.Int("net.peer.port", 56), + }, + }, + { + name: "with remote ipv6 and port", + network: "tcp", + method: "GET", + requestURI: "/user/123", + proto: "HTTP/1.0", + remoteAddr: "[fe80::0202:b3ff:fe1e:8329]:56", + host: "", + url: &url.URL{ + Path: "/user/123", + }, + header: nil, + expected: []attribute.KeyValue{ + attribute.String("net.transport", "ip_tcp"), + attribute.String("net.peer.ip", "fe80::202:b3ff:fe1e:8329"), + attribute.Int("net.peer.port", 56), + }, + }, + { + name: "with remote ipv4-in-v6 and port", + network: "tcp", + method: "GET", + requestURI: "/user/123", + proto: "HTTP/1.0", + remoteAddr: "[::ffff:192.168.0.1]:56", + host: "", + url: &url.URL{ + Path: "/user/123", + }, + header: nil, + expected: []attribute.KeyValue{ + attribute.String("net.transport", "ip_tcp"), + attribute.String("net.peer.ip", "192.168.0.1"), + attribute.Int("net.peer.port", 56), + }, + }, + { + name: "with remote name and port", + network: "tcp", + method: "GET", + requestURI: "/user/123", + proto: "HTTP/1.0", + remoteAddr: "example.com:56", + host: "", + url: &url.URL{ + Path: "/user/123", + }, + header: nil, + expected: []attribute.KeyValue{ + attribute.String("net.transport", "ip_tcp"), + attribute.String("net.peer.name", "example.com"), + attribute.Int("net.peer.port", 56), + }, + }, + { + name: "with remote ipv4 only", + network: "tcp", + method: "GET", + requestURI: "/user/123", + proto: "HTTP/1.0", + remoteAddr: "1.2.3.4", + host: "", + url: &url.URL{ + Path: "/user/123", + }, + header: nil, + expected: []attribute.KeyValue{ + attribute.String("net.transport", "ip_tcp"), + attribute.String("net.peer.ip", "1.2.3.4"), + }, + }, + { + name: "with remote ipv6 only", + network: "tcp", + method: "GET", + requestURI: "/user/123", + proto: "HTTP/1.0", + remoteAddr: "fe80::0202:b3ff:fe1e:8329", + host: "", + url: &url.URL{ + Path: "/user/123", + }, + header: nil, + expected: []attribute.KeyValue{ + attribute.String("net.transport", "ip_tcp"), + attribute.String("net.peer.ip", "fe80::202:b3ff:fe1e:8329"), + }, + }, + { + name: "with remote ipv4_in_v6 only", + network: "tcp", + method: "GET", + requestURI: "/user/123", + proto: "HTTP/1.0", + remoteAddr: "::ffff:192.168.0.1", // section 2.5.5.2 of RFC4291 + host: "", + url: &url.URL{ + Path: "/user/123", + }, + header: nil, + expected: []attribute.KeyValue{ + attribute.String("net.transport", "ip_tcp"), + attribute.String("net.peer.ip", "192.168.0.1"), + }, + }, + { + name: "with remote name only", + network: "tcp", + method: "GET", + requestURI: "/user/123", + proto: "HTTP/1.0", + remoteAddr: "example.com", + host: "", + url: &url.URL{ + Path: "/user/123", + }, + header: nil, + expected: []attribute.KeyValue{ + attribute.String("net.transport", "ip_tcp"), + attribute.String("net.peer.name", "example.com"), + }, + }, + { + name: "with remote port only", + network: "tcp", + method: "GET", + requestURI: "/user/123", + proto: "HTTP/1.0", + remoteAddr: ":56", + host: "", + url: &url.URL{ + Path: "/user/123", + }, + header: nil, + expected: []attribute.KeyValue{ + attribute.String("net.transport", "ip_tcp"), + attribute.Int("net.peer.port", 56), + }, + }, + { + name: "with host name only", + network: "tcp", + method: "GET", + requestURI: "/user/123", + proto: "HTTP/1.0", + remoteAddr: "1.2.3.4:56", + host: "example.com", + url: &url.URL{ + Path: "/user/123", + }, + header: nil, + expected: []attribute.KeyValue{ + attribute.String("net.transport", "ip_tcp"), + attribute.String("net.peer.ip", "1.2.3.4"), + attribute.Int("net.peer.port", 56), + attribute.String("net.host.name", "example.com"), + }, + }, + { + name: "with host ipv4 only", + network: "tcp", + method: "GET", + requestURI: "/user/123", + proto: "HTTP/1.0", + remoteAddr: "1.2.3.4:56", + host: "4.3.2.1", + url: &url.URL{ + Path: "/user/123", + }, + header: nil, + expected: []attribute.KeyValue{ + attribute.String("net.transport", "ip_tcp"), + attribute.String("net.peer.ip", "1.2.3.4"), + attribute.Int("net.peer.port", 56), + attribute.String("net.host.ip", "4.3.2.1"), + }, + }, + { + name: "with host ipv6 only", + network: "tcp", + method: "GET", + requestURI: "/user/123", + proto: "HTTP/1.0", + remoteAddr: "1.2.3.4:56", + host: "fe80::0202:b3ff:fe1e:8329", + url: &url.URL{ + Path: "/user/123", + }, + header: nil, + expected: []attribute.KeyValue{ + attribute.String("net.transport", "ip_tcp"), + attribute.String("net.peer.ip", "1.2.3.4"), + attribute.Int("net.peer.port", 56), + attribute.String("net.host.ip", "fe80::202:b3ff:fe1e:8329"), + }, + }, + { + name: "with host name and port", + network: "tcp", + method: "GET", + requestURI: "/user/123", + proto: "HTTP/1.0", + remoteAddr: "1.2.3.4:56", + host: "example.com:78", + url: &url.URL{ + Path: "/user/123", + }, + header: nil, + expected: []attribute.KeyValue{ + attribute.String("net.transport", "ip_tcp"), + attribute.String("net.peer.ip", "1.2.3.4"), + attribute.Int("net.peer.port", 56), + attribute.String("net.host.name", "example.com"), + attribute.Int("net.host.port", 78), + }, + }, + { + name: "with host ipv4 and port", + network: "tcp", + method: "GET", + requestURI: "/user/123", + proto: "HTTP/1.0", + remoteAddr: "1.2.3.4:56", + host: "4.3.2.1:78", + url: &url.URL{ + Path: "/user/123", + }, + header: nil, + expected: []attribute.KeyValue{ + attribute.String("net.transport", "ip_tcp"), + attribute.String("net.peer.ip", "1.2.3.4"), + attribute.Int("net.peer.port", 56), + attribute.String("net.host.ip", "4.3.2.1"), + attribute.Int("net.host.port", 78), + }, + }, + { + name: "with host ipv6 and port", + network: "tcp", + method: "GET", + requestURI: "/user/123", + proto: "HTTP/1.0", + remoteAddr: "1.2.3.4:56", + host: "[fe80::202:b3ff:fe1e:8329]:78", + url: &url.URL{ + Path: "/user/123", + }, + header: nil, + expected: []attribute.KeyValue{ + attribute.String("net.transport", "ip_tcp"), + attribute.String("net.peer.ip", "1.2.3.4"), + attribute.Int("net.peer.port", 56), + attribute.String("net.host.ip", "fe80::202:b3ff:fe1e:8329"), + attribute.Int("net.host.port", 78), + }, + }, + { + name: "with host name and bogus port", + network: "tcp", + method: "GET", + requestURI: "/user/123", + proto: "HTTP/1.0", + remoteAddr: "1.2.3.4:56", + host: "example.com:qwerty", + url: &url.URL{ + Path: "/user/123", + }, + header: nil, + expected: []attribute.KeyValue{ + attribute.String("net.transport", "ip_tcp"), + attribute.String("net.peer.ip", "1.2.3.4"), + attribute.Int("net.peer.port", 56), + attribute.String("net.host.name", "example.com"), + }, + }, + { + name: "with host ipv4 and bogus port", + network: "tcp", + method: "GET", + requestURI: "/user/123", + proto: "HTTP/1.0", + remoteAddr: "1.2.3.4:56", + host: "4.3.2.1:qwerty", + url: &url.URL{ + Path: "/user/123", + }, + header: nil, + expected: []attribute.KeyValue{ + attribute.String("net.transport", "ip_tcp"), + attribute.String("net.peer.ip", "1.2.3.4"), + attribute.Int("net.peer.port", 56), + attribute.String("net.host.ip", "4.3.2.1"), + }, + }, + { + name: "with host ipv6 and bogus port", + network: "tcp", + method: "GET", + requestURI: "/user/123", + proto: "HTTP/1.0", + remoteAddr: "1.2.3.4:56", + host: "[fe80::202:b3ff:fe1e:8329]:qwerty", + url: &url.URL{ + Path: "/user/123", + }, + header: nil, + expected: []attribute.KeyValue{ + attribute.String("net.transport", "ip_tcp"), + attribute.String("net.peer.ip", "1.2.3.4"), + attribute.Int("net.peer.port", 56), + attribute.String("net.host.ip", "fe80::202:b3ff:fe1e:8329"), + }, + }, + { + name: "with empty host and port", + network: "tcp", + method: "GET", + requestURI: "/user/123", + proto: "HTTP/1.0", + remoteAddr: "1.2.3.4:56", + host: ":80", + url: &url.URL{ + Path: "/user/123", + }, + header: nil, + expected: []attribute.KeyValue{ + attribute.String("net.transport", "ip_tcp"), + attribute.String("net.peer.ip", "1.2.3.4"), + attribute.Int("net.peer.port", 56), + attribute.Int("net.host.port", 80), + }, + }, + { + name: "with host ip and port in headers", + network: "tcp", + method: "GET", + requestURI: "/user/123", + proto: "HTTP/1.0", + remoteAddr: "1.2.3.4:56", + host: "", + url: &url.URL{ + Path: "/user/123", + }, + header: http.Header{ + "Host": []string{"4.3.2.1:78"}, + }, + expected: []attribute.KeyValue{ + attribute.String("net.transport", "ip_tcp"), + attribute.String("net.peer.ip", "1.2.3.4"), + attribute.Int("net.peer.port", 56), + attribute.String("net.host.ip", "4.3.2.1"), + attribute.Int("net.host.port", 78), + }, + }, + { + name: "with host ipv4 and port in url", + network: "tcp", + method: "GET", + requestURI: "http://4.3.2.1:78/user/123", + proto: "HTTP/1.0", + remoteAddr: "1.2.3.4:56", + host: "", + url: &url.URL{ + Host: "4.3.2.1:78", + Path: "/user/123", + }, + header: nil, + expected: []attribute.KeyValue{ + attribute.String("net.transport", "ip_tcp"), + attribute.String("net.peer.ip", "1.2.3.4"), + attribute.Int("net.peer.port", 56), + attribute.String("net.host.ip", "4.3.2.1"), + attribute.Int("net.host.port", 78), + }, + }, + { + name: "with host ipv6 and port in url", + network: "tcp", + method: "GET", + requestURI: "http://4.3.2.1:78/user/123", + proto: "HTTP/1.0", + remoteAddr: "1.2.3.4:56", + host: "", + url: &url.URL{ + Host: "[fe80::202:b3ff:fe1e:8329]:78", + Path: "/user/123", + }, + header: nil, + expected: []attribute.KeyValue{ + attribute.String("net.transport", "ip_tcp"), + attribute.String("net.peer.ip", "1.2.3.4"), + attribute.Int("net.peer.port", 56), + attribute.String("net.host.ip", "fe80::202:b3ff:fe1e:8329"), + attribute.Int("net.host.port", 78), + }, + }, + } + 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, noTLS) + got := sc.NetAttributesFromHTTPRequest(tc.network, r) + if diff := cmp.Diff( + tc.expected, + got, + cmp.AllowUnexported(attribute.Value{})); diff != "" { + t.Fatalf("attributes differ: diff %+v,", diff) + } + }) + } +} + +func TestEndUserAttributesFromHTTPRequest(t *testing.T) { + r := testRequest("GET", "/user/123", "HTTP/1.1", "", "", nil, http.Header{}, withTLS) + var expected []attribute.KeyValue + got := sc.EndUserAttributesFromHTTPRequest(r) + assert.ElementsMatch(t, expected, got) + r.SetBasicAuth("admin", "password") + expected = []attribute.KeyValue{attribute.String("enduser.id", "admin")} + got = sc.EndUserAttributesFromHTTPRequest(r) + assert.ElementsMatch(t, expected, got) +} + +func TestHTTPServerAttributesFromHTTPRequest(t *testing.T) { + type testcase struct { + name string + + serverName string + route 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: "", + route: "", + 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.target", "/user/123"), + attribute.String("http.scheme", "http"), + attribute.String("http.flavor", "1.0"), + }, + }, + { + name: "with server name", + serverName: "my-server-name", + route: "", + 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.target", "/user/123"), + 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", + route: "", + 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.target", "/user/123"), + 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", + route: "/user/:id", + 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.target", "/user/123"), + attribute.String("http.scheme", "https"), + attribute.String("http.flavor", "1.0"), + attribute.String("http.server_name", "my-server-name"), + attribute.String("http.route", "/user/:id"), + }, + }, + { + name: "with host", + serverName: "my-server-name", + route: "/user/:id", + 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.target", "/user/123"), + attribute.String("http.scheme", "https"), + attribute.String("http.flavor", "1.0"), + attribute.String("http.server_name", "my-server-name"), + attribute.String("http.route", "/user/:id"), + attribute.String("http.host", "example.com"), + }, + }, + { + name: "with host fallback", + serverName: "my-server-name", + route: "/user/:id", + 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.target", "/user/123"), + attribute.String("http.scheme", "https"), + attribute.String("http.flavor", "1.0"), + attribute.String("http.server_name", "my-server-name"), + attribute.String("http.route", "/user/:id"), + attribute.String("http.host", "example.com"), + }, + }, + { + name: "with user agent", + serverName: "my-server-name", + route: "/user/:id", + 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.target", "/user/123"), + attribute.String("http.scheme", "https"), + attribute.String("http.flavor", "1.0"), + attribute.String("http.server_name", "my-server-name"), + attribute.String("http.route", "/user/:id"), + attribute.String("http.host", "example.com"), + attribute.String("http.user_agent", "foodownloader"), + }, + }, + { + name: "with proxy info", + serverName: "my-server-name", + route: "/user/:id", + 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.target", "/user/123"), + attribute.String("http.scheme", "https"), + attribute.String("http.flavor", "1.0"), + attribute.String("http.server_name", "my-server-name"), + attribute.String("http.route", "/user/:id"), + attribute.String("http.host", "example.com"), + attribute.String("http.user_agent", "foodownloader"), + attribute.String("http.client_ip", "203.0.113.195"), + }, + }, + { + name: "with http 1.1", + serverName: "my-server-name", + route: "/user/:id", + 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.target", "/user/123"), + attribute.String("http.scheme", "https"), + attribute.String("http.flavor", "1.1"), + attribute.String("http.server_name", "my-server-name"), + attribute.String("http.route", "/user/:id"), + attribute.String("http.host", "example.com"), + attribute.String("http.user_agent", "foodownloader"), + attribute.String("http.client_ip", "1.2.3.4"), + }, + }, + { + name: "with http 2", + serverName: "my-server-name", + route: "/user/:id", + 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.target", "/user/123"), + attribute.String("http.scheme", "https"), + attribute.String("http.flavor", "2"), + attribute.String("http.server_name", "my-server-name"), + attribute.String("http.route", "/user/:id"), + attribute.String("http.host", "example.com"), + attribute.String("http.user_agent", "foodownloader"), + attribute.String("http.client_ip", "1.2.3.4"), + }, + }, + { + name: "with content length", + method: "GET", + requestURI: "/user/123", + contentLength: 100, + expected: []attribute.KeyValue{ + attribute.String("http.method", "GET"), + attribute.String("http.target", "/user/123"), + attribute.String("http.scheme", "http"), + attribute.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 := sc.HTTPServerAttributesFromHTTPRequest(tc.serverName, tc.route, r) + assertElementsMatch(t, tc.expected, got, "testcase %d - %s", idx, tc.name) + } +} + +func TestHTTPAttributesFromHTTPStatusCode(t *testing.T) { + expected := []attribute.KeyValue{ + attribute.Int("http.status_code", 404), + } + got := sc.HTTPAttributesFromHTTPStatusCode(http.StatusNotFound) + assertElementsMatch(t, expected, got, "with valid HTTP status code") + assert.ElementsMatch(t, expected, got) + expected = []attribute.KeyValue{ + attribute.Int("http.status_code", 499), + } + got = sc.HTTPAttributesFromHTTPStatusCode(499) + assertElementsMatch(t, expected, got, "with invalid HTTP status code") +} + +func TestSpanStatusFromHTTPStatusCode(t *testing.T) { + for code := 0; code < 1000; code++ { + expected := getExpectedCodeForHTTPCode(code, trace.SpanKindClient) + got, msg := SpanStatusFromHTTPStatusCode(code) + assert.Equalf(t, expected, got, "%s vs %s", expected, got) + + _, valid := validateHTTPStatusCode(code) + if !valid { + assert.NotEmpty(t, msg, "message should be set if error cannot be inferred from code") + } else { + assert.Empty(t, msg, "message should not be set if error can be inferred from code") + } + } +} + +func TestSpanStatusFromHTTPStatusCodeAndSpanKind(t *testing.T) { + for code := 0; code < 1000; code++ { + expected := getExpectedCodeForHTTPCode(code, trace.SpanKindClient) + got, msg := SpanStatusFromHTTPStatusCodeAndSpanKind(code, trace.SpanKindClient) + assert.Equalf(t, expected, got, "%s vs %s", expected, got) + + _, valid := validateHTTPStatusCode(code) + if !valid { + assert.NotEmpty(t, msg, "message should be set if error cannot be inferred from code") + } else { + assert.Empty(t, msg, "message should not be set if error can be inferred from code") + } + } + code, _ := SpanStatusFromHTTPStatusCodeAndSpanKind(400, trace.SpanKindServer) + assert.Equalf(t, codes.Unset, code, "message should be set if error cannot be inferred from code") +} + +func getExpectedCodeForHTTPCode(code int, spanKind trace.SpanKind) codes.Code { + if http.StatusText(code) == "" { + return codes.Error + } + switch code { + case + http.StatusUnauthorized, + http.StatusForbidden, + http.StatusNotFound, + http.StatusTooManyRequests, + http.StatusNotImplemented, + http.StatusServiceUnavailable, + http.StatusGatewayTimeout: + return codes.Error + } + category := code / 100 + if category > 0 && category < 4 { + return codes.Unset + } + if spanKind == trace.SpanKindServer && category == 4 { + return codes.Unset + } + return codes.Error +} + +func assertElementsMatch(t *testing.T, expected, got []attribute.KeyValue, format string, args ...interface{}) { + if !assert.ElementsMatchf(t, expected, got, format, args...) { + t.Log("expected:", kvStr(expected)) + t.Log("got:", kvStr(got)) + } +} + +func testRequest(method, requestURI, proto, remoteAddr, host string, u *url.URL, header http.Header, tlsopt tlsOption) *http.Request { + major, minor := protoToInts(proto) + var tlsConn *tls.ConnectionState + switch tlsopt { + case noTLS: + case withTLS: + tlsConn = &tls.ConnectionState{} + } + return &http.Request{ + Method: method, + URL: u, + Proto: proto, + ProtoMajor: major, + ProtoMinor: minor, + Header: header, + Host: host, + RemoteAddr: remoteAddr, + RequestURI: requestURI, + TLS: tlsConn, + } +} + +func protoToInts(proto string) (int, int) { + switch proto { + case "HTTP/1.0": + return 1, 0 + case "HTTP/1.1": + return 1, 1 + case "HTTP/2.0": + return 2, 0 + } + // invalid proto + return 13, 42 +} + +func kvStr(kvs []attribute.KeyValue) string { + sb := strings.Builder{} + _, _ = sb.WriteRune('[') + for idx, attr := range kvs { + if idx > 0 { + _, _ = sb.WriteString(", ") + } + _, _ = sb.WriteString((string)(attr.Key)) + _, _ = sb.WriteString(": ") + _, _ = sb.WriteString(attr.Value.Emit()) + } + _, _ = 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 []attribute.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: []attribute.KeyValue{ + attribute.String("http.method", "GET"), + attribute.String("http.url", "/user/123"), + attribute.String("http.scheme", "http"), + attribute.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: []attribute.KeyValue{ + attribute.String("http.method", "GET"), + attribute.String("http.url", "/user/123"), + attribute.String("http.scheme", "https"), + attribute.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: []attribute.KeyValue{ + attribute.String("http.method", "GET"), + attribute.String("http.url", "/user/123"), + attribute.String("http.scheme", "https"), + attribute.String("http.flavor", "1.0"), + attribute.String("http.host", "example.com"), + }, + }, + { + name: "with host fallback", + method: "GET", + requestURI: "/user/123", + proto: "HTTP/1.0", + remoteAddr: "", + host: "", + url: &url.URL{ + Scheme: "https", + Host: "example.com", + Path: "/user/123", + }, + header: nil, + tls: withTLS, + expected: []attribute.KeyValue{ + attribute.String("http.method", "GET"), + attribute.String("http.url", "https://example.com/user/123"), + attribute.String("http.scheme", "https"), + attribute.String("http.flavor", "1.0"), + attribute.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: []attribute.KeyValue{ + attribute.String("http.method", "GET"), + attribute.String("http.url", "/user/123"), + attribute.String("http.scheme", "https"), + attribute.String("http.flavor", "1.0"), + attribute.String("http.host", "example.com"), + attribute.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: []attribute.KeyValue{ + attribute.String("http.method", "GET"), + attribute.String("http.url", "/user/123"), + attribute.String("http.scheme", "https"), + attribute.String("http.flavor", "1.1"), + attribute.String("http.host", "example.com"), + attribute.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: []attribute.KeyValue{ + attribute.String("http.method", "GET"), + attribute.String("http.url", "/user/123"), + attribute.String("http.scheme", "https"), + attribute.String("http.flavor", "2"), + attribute.String("http.host", "example.com"), + attribute.String("http.user_agent", "foodownloader"), + }, + }, + { + name: "with content length", + method: "GET", + url: &url.URL{ + Path: "/user/123", + }, + contentLength: 100, + expected: []attribute.KeyValue{ + attribute.String("http.method", "GET"), + attribute.String("http.url", "/user/123"), + attribute.String("http.scheme", "http"), + attribute.Int64("http.request_content_length", 100), + }, + }, + { + name: "with empty method (fallback to GET)", + method: "", + url: &url.URL{ + Path: "/user/123", + }, + expected: []attribute.KeyValue{ + attribute.String("http.method", "GET"), + attribute.String("http.url", "/user/123"), + attribute.String("http.scheme", "http"), + }, + }, + { + name: "authentication information is stripped", + method: "", + url: &url.URL{ + Path: "/user/123", + User: url.UserPassword("foo", "bar"), + }, + expected: []attribute.KeyValue{ + attribute.String("http.method", "GET"), + attribute.String("http.url", "/user/123"), + attribute.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 := sc.HTTPClientAttributesFromHTTPRequest(r) + assert.ElementsMatch(t, tc.expected, got) + }) + } +} + +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) + } +} diff --git a/semconv/internal/v2/net.go b/semconv/internal/v2/net.go new file mode 100644 index 00000000000..d4b33fd24f8 --- /dev/null +++ b/semconv/internal/v2/net.go @@ -0,0 +1,70 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package internal // import "go.opentelemetry.io/otel/semconv/internal/v2" + +import "go.opentelemetry.io/otel/attribute" + +// NetConv are the network semantic convention attributes defined for a version +// of the OpenTelemetry specification. +type NetConv struct { + NetHostNameKey attribute.Key + NetHostPortKey attribute.Key + NetPeerNameKey attribute.Key + NetPeerPortKey attribute.Key + NetSockPeerAddrKey attribute.Key + NetSockPeerPortKey attribute.Key + NetTransportOther attribute.KeyValue + NetTransportTCP attribute.KeyValue + NetTransportUDP attribute.KeyValue + NetTransportInProc attribute.KeyValue +} + +func (c *NetConv) Transport(network string) attribute.KeyValue { + switch network { + case "tcp", "tcp4", "tcp6": + return c.NetTransportTCP + case "udp", "udp4", "udp6": + return c.NetTransportUDP + case "unix", "unixgram", "unixpacket": + return c.NetTransportInProc + default: + // "ip:*", "ip4:*", and "ip6:*" all are considered other. + return c.NetTransportOther + } +} + +func (c *NetConv) HostName(name string) attribute.KeyValue { + return c.NetHostNameKey.String(name) +} + +func (c *NetConv) HostPort(port int) attribute.KeyValue { + return c.NetHostPortKey.Int(port) +} + +func (c *NetConv) PeerName(name string) attribute.KeyValue { + return c.NetPeerNameKey.String(name) +} + +func (c *NetConv) PeerPort(port int) attribute.KeyValue { + return c.NetPeerPortKey.Int(port) +} + +func (c *NetConv) SockPeerAddr(addr string) attribute.KeyValue { + return c.NetSockPeerAddrKey.String(addr) +} + +func (c *NetConv) SockPeerPort(port int) attribute.KeyValue { + return c.NetSockPeerPortKey.Int(port) +} diff --git a/semconv/v1.13.0/doc.go b/semconv/v1.13.0/doc.go new file mode 100644 index 00000000000..0cb2aabc789 --- /dev/null +++ b/semconv/v1.13.0/doc.go @@ -0,0 +1,20 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package semconv implements OpenTelemetry semantic conventions. +// +// OpenTelemetry semantic conventions are agreed standardized naming +// patterns for OpenTelemetry things. This package represents the conventions +// as of the v1.13.0 version of the OpenTelemetry specification. +package semconv // import "go.opentelemetry.io/otel/semconv/v1.13.0" diff --git a/semconv/v1.13.0/exception.go b/semconv/v1.13.0/exception.go new file mode 100644 index 00000000000..9d343368d75 --- /dev/null +++ b/semconv/v1.13.0/exception.go @@ -0,0 +1,20 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package semconv // import "go.opentelemetry.io/otel/semconv/v1.13.0" + +const ( + // ExceptionEventName is the name of the Span event representing an exception. + ExceptionEventName = "exception" +) diff --git a/semconv/v1.13.0/http.go b/semconv/v1.13.0/http.go new file mode 100644 index 00000000000..dcbc5a59b4d --- /dev/null +++ b/semconv/v1.13.0/http.go @@ -0,0 +1,21 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package semconv // import "go.opentelemetry.io/otel/semconv/v1.13.0" + +// HTTP scheme attributes. +var ( + HTTPSchemeHTTP = HTTPSchemeKey.String("http") + HTTPSchemeHTTPS = HTTPSchemeKey.String("https") +) diff --git a/semconv/v1.13.0/httpconv/http.go b/semconv/v1.13.0/httpconv/http.go new file mode 100644 index 00000000000..f263d4e9bb1 --- /dev/null +++ b/semconv/v1.13.0/httpconv/http.go @@ -0,0 +1,125 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package httpconv provides OpenTelemetry semantic convetions for the net/http +// package from the standard library. +package httpconv // import "go.opentelemetry.io/otel/semconv/v1.13.0/httpconv" + +import ( + "net/http" + + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" + "go.opentelemetry.io/otel/semconv/internal/v2" + semconv "go.opentelemetry.io/otel/semconv/v1.13.0" +) + +var ( + nc = &internal.NetConv{ + NetHostNameKey: semconv.NetHostNameKey, + NetHostPortKey: semconv.NetHostPortKey, + NetPeerNameKey: semconv.NetPeerNameKey, + NetPeerPortKey: semconv.NetPeerPortKey, + NetSockPeerAddrKey: semconv.NetSockPeerAddrKey, + NetSockPeerPortKey: semconv.NetSockPeerPortKey, + NetTransportOther: semconv.NetTransportOther, + NetTransportTCP: semconv.NetTransportTCP, + NetTransportUDP: semconv.NetTransportUDP, + NetTransportInProc: semconv.NetTransportInProc, + } + + hc = &internal.HTTPConv{ + NetConv: nc, + + EnduserIDKey: semconv.EnduserIDKey, + HTTPClientIPKey: semconv.HTTPClientIPKey, + HTTPFlavorKey: semconv.HTTPFlavorKey, + HTTPMethodKey: semconv.HTTPMethodKey, + HTTPRequestContentLengthKey: semconv.HTTPRequestContentLengthKey, + HTTPResponseContentLengthKey: semconv.HTTPResponseContentLengthKey, + HTTPRouteKey: semconv.HTTPRouteKey, + HTTPSchemeHTTP: semconv.HTTPSchemeHTTP, + HTTPSchemeHTTPS: semconv.HTTPSchemeHTTPS, + HTTPStatusCodeKey: semconv.HTTPStatusCodeKey, + HTTPTargetKey: semconv.HTTPTargetKey, + HTTPURLKey: semconv.HTTPURLKey, + HTTPUserAgentKey: semconv.HTTPUserAgentKey, + } +) + +// ClientResponse returns attributes for an HTTP response received by a client +// from a server. +// +// This does not add all OpenTelemetry required attributes for an HTTP event, +// it assumes ClientRequest was used to create the span with a complete set of +// attributes. If a complete set of attributes can be generated using the +// request contained in resp. For example: +// +// append(ClientResponse(resp), ClientRequest(resp.Request)...) +func ClientResponse(resp http.Response) []attribute.KeyValue { + return hc.ClientResponse(resp) +} + +// ClientRequest returns attributes for an HTTP request made by a client. +func ClientRequest(req *http.Request) []attribute.KeyValue { + return hc.ClientRequest(req) +} + +// ClientStatus returns a span status code and message for an HTTP status code +// value received by a client. +func ClientStatus(code int) (codes.Code, string) { + return internal.SpanStatusFromHTTPStatusCode() +} + +// ServerRequest returns attributes for an HTTP request received by a server. +func ServerRequest(req *http.Request) []attribute.KeyValue { + return hc.ServerRequest(req) +} + +// ServerStatus returns a span status code and message for an HTTP status code +// value returned by a server. Status codes in the 400-499 range are not +// returned as errors. +func ServerStatus(code int) (codes.Code, string) { + return hc.ServerStatus(code) +} + +// RequestHeader returns the contents of h as attributes. +// +// Instrumentation should require an explicit configuration of which headers to +// captured and then prune what they pass here. Including all headers can be a +// security risk - explicit configuration helps avoid leaking sensitive +// information. +// +// The User-Agent header is already captured in the http.user_agent attribute +// from ClientRequest and ServerRequest. Instrumentation may provide an option +// to capture that header here even though it is not recommended. Otherwise, +// instrumentation should filter that out of what is passed. +func RequestHeader(h http.Header) []attribute.KeyValue { + return hc.RequestHeader(h) +} + +// ResponseHeader returns the contents of h as attributes. +// +// Instrumentation should require an explicit configuration of which headers to +// captured and then prune what they pass here. Including all headers can be a +// security risk - explicit configuration helps avoid leaking sensitive +// information. +// +// The User-Agent header is already captured in the http.user_agent attribute +// from ClientRequest and ServerRequest. Instrumentation may provide an option +// to capture that header here even though it is not recommended. Otherwise, +// instrumentation should filter that out of what is passed. +func ResponseHeader(h http.Header) []attribute.KeyValue { + return hc.ResponseHeader(h) +} diff --git a/semconv/v1.13.0/netconv/net.go b/semconv/v1.13.0/netconv/net.go new file mode 100644 index 00000000000..c623e4b4008 --- /dev/null +++ b/semconv/v1.13.0/netconv/net.go @@ -0,0 +1,43 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package netconv provides OpenTelemetry semantic convetions for the net +// package from the standard library. +package netconv // import "go.opentelemetry.io/otel/semconv/v1.13.0/netconv" + +import ( + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/semconv/internal/v2" + semconv "go.opentelemetry.io/otel/semconv/v1.13.0" +) + +var nc = &internal.NetConv{ + NetHostNameKey: semconv.NetHostNameKey, + NetHostPortKey: semconv.NetHostPortKey, + NetPeerNameKey: semconv.NetPeerNameKey, + NetPeerPortKey: semconv.NetPeerPortKey, + NetSockPeerAddrKey: semconv.NetSockPeerAddrKey, + NetSockPeerPortKey: semconv.NetSockPeerPortKey, + NetTransportOther: semconv.NetTransportOther, + NetTransportTCP: semconv.NetTransportTCP, + NetTransportUDP: semconv.NetTransportUDP, + NetTransportInProc: semconv.NetTransportInProc, +} + +// Transport returns an attribute describing the transport protocol of the +// passed network. See the net.Dial for information about acceptable network +// values. +func Transport(network string) attribute.KeyValue { + return nc.Transport(network) +} diff --git a/semconv/v1.13.0/resource.go b/semconv/v1.13.0/resource.go new file mode 100644 index 00000000000..9a937fe428d --- /dev/null +++ b/semconv/v1.13.0/resource.go @@ -0,0 +1,1049 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated from semantic convention specification. DO NOT EDIT. + +package semconv // import "go.opentelemetry.io/otel/semconv/v1.13.0" + +import "go.opentelemetry.io/otel/attribute" + +// The web browser in which the application represented by the resource is running. The `browser.*` attributes MUST be used only for resources that represent applications running in a web browser (regardless of whether running on a mobile or desktop device). +const ( + // Array of brand name and version separated by a space + // + // Type: string[] + // RequirementLevel: Optional + // Stability: stable + // Examples: ' Not A;Brand 99', 'Chromium 99', 'Chrome 99' + // Note: This value is intended to be taken from the [UA client hints + // API](https://wicg.github.io/ua-client-hints/#interface) + // (navigator.userAgentData.brands). + BrowserBrandsKey = attribute.Key("browser.brands") + // The platform on which the browser is running + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'Windows', 'macOS', 'Android' + // Note: This value is intended to be taken from the [UA client hints + // API](https://wicg.github.io/ua-client-hints/#interface) + // (navigator.userAgentData.platform). If unavailable, the legacy + // `navigator.platform` API SHOULD NOT be used instead and this attribute SHOULD + // be left unset in order for the values to be consistent. + // The list of possible values is defined in the [W3C User-Agent Client Hints + // specification](https://wicg.github.io/ua-client-hints/#sec-ch-ua-platform). + // Note that some (but not all) of these values can overlap with values in the + // [os.type and os.name attributes](./os.md). However, for consistency, the values + // in the `browser.platform` attribute should capture the exact value that the + // user agent provides. + BrowserPlatformKey = attribute.Key("browser.platform") + // Full user-agent string provided by the browser + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 + // (KHTML, ' + // 'like Gecko) Chrome/95.0.4638.54 Safari/537.36' + // Note: The user-agent value SHOULD be provided only from browsers that do not + // have a mechanism to retrieve brands and platform individually from the User- + // Agent Client Hints API. To retrieve the value, the legacy `navigator.userAgent` + // API can be used. + BrowserUserAgentKey = attribute.Key("browser.user_agent") +) + +// A cloud environment (e.g. GCP, Azure, AWS) +const ( + // Name of the cloud provider. + // + // Type: Enum + // RequirementLevel: Optional + // Stability: stable + CloudProviderKey = attribute.Key("cloud.provider") + // The cloud account ID the resource is assigned to. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: '111111111111', 'opentelemetry' + CloudAccountIDKey = attribute.Key("cloud.account.id") + // The geographical region the resource is running. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'us-central1', 'us-east-1' + // Note: Refer to your provider's docs to see the available regions, for example + // [Alibaba Cloud regions](https://www.alibabacloud.com/help/doc- + // detail/40654.htm), [AWS regions](https://aws.amazon.com/about-aws/global- + // infrastructure/regions_az/), [Azure regions](https://azure.microsoft.com/en- + // us/global-infrastructure/geographies/), [Google Cloud + // regions](https://cloud.google.com/about/locations), or [Tencent Cloud + // regions](https://intl.cloud.tencent.com/document/product/213/6091). + CloudRegionKey = attribute.Key("cloud.region") + // Cloud regions often have multiple, isolated locations known as zones to + // increase availability. Availability zone represents the zone where the resource + // is running. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'us-east-1c' + // Note: Availability zones are called "zones" on Alibaba Cloud and Google Cloud. + CloudAvailabilityZoneKey = attribute.Key("cloud.availability_zone") + // The cloud platform in use. + // + // Type: Enum + // RequirementLevel: Optional + // Stability: stable + // Note: The prefix of the service SHOULD match the one specified in + // `cloud.provider`. + CloudPlatformKey = attribute.Key("cloud.platform") +) + +var ( + // Alibaba Cloud + CloudProviderAlibabaCloud = CloudProviderKey.String("alibaba_cloud") + // Amazon Web Services + CloudProviderAWS = CloudProviderKey.String("aws") + // Microsoft Azure + CloudProviderAzure = CloudProviderKey.String("azure") + // Google Cloud Platform + CloudProviderGCP = CloudProviderKey.String("gcp") + // Tencent Cloud + CloudProviderTencentCloud = CloudProviderKey.String("tencent_cloud") +) + +var ( + // Alibaba Cloud Elastic Compute Service + CloudPlatformAlibabaCloudECS = CloudPlatformKey.String("alibaba_cloud_ecs") + // Alibaba Cloud Function Compute + CloudPlatformAlibabaCloudFc = CloudPlatformKey.String("alibaba_cloud_fc") + // AWS Elastic Compute Cloud + CloudPlatformAWSEC2 = CloudPlatformKey.String("aws_ec2") + // AWS Elastic Container Service + CloudPlatformAWSECS = CloudPlatformKey.String("aws_ecs") + // AWS Elastic Kubernetes Service + CloudPlatformAWSEKS = CloudPlatformKey.String("aws_eks") + // AWS Lambda + CloudPlatformAWSLambda = CloudPlatformKey.String("aws_lambda") + // AWS Elastic Beanstalk + CloudPlatformAWSElasticBeanstalk = CloudPlatformKey.String("aws_elastic_beanstalk") + // AWS App Runner + CloudPlatformAWSAppRunner = CloudPlatformKey.String("aws_app_runner") + // Azure Virtual Machines + CloudPlatformAzureVM = CloudPlatformKey.String("azure_vm") + // Azure Container Instances + CloudPlatformAzureContainerInstances = CloudPlatformKey.String("azure_container_instances") + // Azure Kubernetes Service + CloudPlatformAzureAKS = CloudPlatformKey.String("azure_aks") + // Azure Functions + CloudPlatformAzureFunctions = CloudPlatformKey.String("azure_functions") + // Azure App Service + CloudPlatformAzureAppService = CloudPlatformKey.String("azure_app_service") + // Google Cloud Compute Engine (GCE) + CloudPlatformGCPComputeEngine = CloudPlatformKey.String("gcp_compute_engine") + // Google Cloud Run + CloudPlatformGCPCloudRun = CloudPlatformKey.String("gcp_cloud_run") + // Google Cloud Kubernetes Engine (GKE) + CloudPlatformGCPKubernetesEngine = CloudPlatformKey.String("gcp_kubernetes_engine") + // Google Cloud Functions (GCF) + CloudPlatformGCPCloudFunctions = CloudPlatformKey.String("gcp_cloud_functions") + // Google Cloud App Engine (GAE) + CloudPlatformGCPAppEngine = CloudPlatformKey.String("gcp_app_engine") + // Tencent Cloud Cloud Virtual Machine (CVM) + CloudPlatformTencentCloudCvm = CloudPlatformKey.String("tencent_cloud_cvm") + // Tencent Cloud Elastic Kubernetes Service (EKS) + CloudPlatformTencentCloudEKS = CloudPlatformKey.String("tencent_cloud_eks") + // Tencent Cloud Serverless Cloud Function (SCF) + CloudPlatformTencentCloudScf = CloudPlatformKey.String("tencent_cloud_scf") +) + +// Resources used by AWS Elastic Container Service (ECS). +const ( + // The Amazon Resource Name (ARN) of an [ECS container instance](https://docs.aws. + // amazon.com/AmazonECS/latest/developerguide/ECS_instances.html). + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'arn:aws:ecs:us- + // west-1:123456789123:container/32624152-9086-4f0e-acae-1a75b14fe4d9' + AWSECSContainerARNKey = attribute.Key("aws.ecs.container.arn") + // The ARN of an [ECS cluster](https://docs.aws.amazon.com/AmazonECS/latest/develo + // perguide/clusters.html). + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'arn:aws:ecs:us-west-2:123456789123:cluster/my-cluster' + AWSECSClusterARNKey = attribute.Key("aws.ecs.cluster.arn") + // The [launch type](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/l + // aunch_types.html) for an ECS task. + // + // Type: Enum + // RequirementLevel: Optional + // Stability: stable + AWSECSLaunchtypeKey = attribute.Key("aws.ecs.launchtype") + // The ARN of an [ECS task definition](https://docs.aws.amazon.com/AmazonECS/lates + // t/developerguide/task_definitions.html). + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'arn:aws:ecs:us- + // west-1:123456789123:task/10838bed-421f-43ef-870a-f43feacbbb5b' + AWSECSTaskARNKey = attribute.Key("aws.ecs.task.arn") + // The task definition family this task definition is a member of. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'opentelemetry-family' + AWSECSTaskFamilyKey = attribute.Key("aws.ecs.task.family") + // The revision for this task definition. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: '8', '26' + AWSECSTaskRevisionKey = attribute.Key("aws.ecs.task.revision") +) + +var ( + // ec2 + AWSECSLaunchtypeEC2 = AWSECSLaunchtypeKey.String("ec2") + // fargate + AWSECSLaunchtypeFargate = AWSECSLaunchtypeKey.String("fargate") +) + +// Resources used by AWS Elastic Kubernetes Service (EKS). +const ( + // The ARN of an EKS cluster. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'arn:aws:ecs:us-west-2:123456789123:cluster/my-cluster' + AWSEKSClusterARNKey = attribute.Key("aws.eks.cluster.arn") +) + +// Resources specific to Amazon Web Services. +const ( + // The name(s) of the AWS log group(s) an application is writing to. + // + // Type: string[] + // RequirementLevel: Optional + // Stability: stable + // Examples: '/aws/lambda/my-function', 'opentelemetry-service' + // Note: Multiple log groups must be supported for cases like multi-container + // applications, where a single application has sidecar containers, and each write + // to their own log group. + AWSLogGroupNamesKey = attribute.Key("aws.log.group.names") + // The Amazon Resource Name(s) (ARN) of the AWS log group(s). + // + // Type: string[] + // RequirementLevel: Optional + // Stability: stable + // Examples: 'arn:aws:logs:us-west-1:123456789012:log-group:/aws/my/group:*' + // Note: See the [log group ARN format + // documentation](https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/iam- + // access-control-overview-cwl.html#CWL_ARN_Format). + AWSLogGroupARNsKey = attribute.Key("aws.log.group.arns") + // The name(s) of the AWS log stream(s) an application is writing to. + // + // Type: string[] + // RequirementLevel: Optional + // Stability: stable + // Examples: 'logs/main/10838bed-421f-43ef-870a-f43feacbbb5b' + AWSLogStreamNamesKey = attribute.Key("aws.log.stream.names") + // The ARN(s) of the AWS log stream(s). + // + // Type: string[] + // RequirementLevel: Optional + // Stability: stable + // Examples: 'arn:aws:logs:us-west-1:123456789012:log-group:/aws/my/group:log- + // stream:logs/main/10838bed-421f-43ef-870a-f43feacbbb5b' + // Note: See the [log stream ARN format + // documentation](https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/iam- + // access-control-overview-cwl.html#CWL_ARN_Format). One log group can contain + // several log streams, so these ARNs necessarily identify both a log group and a + // log stream. + AWSLogStreamARNsKey = attribute.Key("aws.log.stream.arns") +) + +// A container instance. +const ( + // Container name used by container runtime. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'opentelemetry-autoconf' + ContainerNameKey = attribute.Key("container.name") + // Container ID. Usually a UUID, as for example used to [identify Docker + // containers](https://docs.docker.com/engine/reference/run/#container- + // identification). The UUID might be abbreviated. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'a3bf90e006b2' + ContainerIDKey = attribute.Key("container.id") + // The container runtime managing this container. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'docker', 'containerd', 'rkt' + ContainerRuntimeKey = attribute.Key("container.runtime") + // Name of the image the container was built on. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'gcr.io/opentelemetry/operator' + ContainerImageNameKey = attribute.Key("container.image.name") + // Container image tag. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: '0.1' + ContainerImageTagKey = attribute.Key("container.image.tag") +) + +// The software deployment. +const ( + // Name of the [deployment + // environment](https://en.wikipedia.org/wiki/Deployment_environment) (aka + // deployment tier). + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'staging', 'production' + DeploymentEnvironmentKey = attribute.Key("deployment.environment") +) + +// The device on which the process represented by this resource is running. +const ( + // A unique identifier representing the device + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: '2ab2916d-a51f-4ac8-80ee-45ac31a28092' + // Note: The device identifier MUST only be defined using the values outlined + // below. This value is not an advertising identifier and MUST NOT be used as + // such. On iOS (Swift or Objective-C), this value MUST be equal to the [vendor id + // entifier](https://developer.apple.com/documentation/uikit/uidevice/1620059-iden + // tifierforvendor). On Android (Java or Kotlin), this value MUST be equal to the + // Firebase Installation ID or a globally unique UUID which is persisted across + // sessions in your application. More information can be found + // [here](https://developer.android.com/training/articles/user-data-ids) on best + // practices and exact implementation details. Caution should be taken when + // storing personal data or anything which can identify a user. GDPR and data + // protection laws may apply, ensure you do your own due diligence. + DeviceIDKey = attribute.Key("device.id") + // The model identifier for the device + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'iPhone3,4', 'SM-G920F' + // Note: It's recommended this value represents a machine readable version of the + // model identifier rather than the market or consumer-friendly name of the + // device. + DeviceModelIdentifierKey = attribute.Key("device.model.identifier") + // The marketing name for the device model + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'iPhone 6s Plus', 'Samsung Galaxy S6' + // Note: It's recommended this value represents a human readable version of the + // device model rather than a machine readable alternative. + DeviceModelNameKey = attribute.Key("device.model.name") + // The name of the device manufacturer + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'Apple', 'Samsung' + // Note: The Android OS provides this field via + // [Build](https://developer.android.com/reference/android/os/Build#MANUFACTURER). + // iOS apps SHOULD hardcode the value `Apple`. + DeviceManufacturerKey = attribute.Key("device.manufacturer") +) + +// A serverless instance. +const ( + // The name of the single function that this runtime instance executes. + // + // Type: string + // RequirementLevel: Required + // Stability: stable + // Examples: 'my-function', 'myazurefunctionapp/some-function-name' + // Note: This is the name of the function as configured/deployed on the FaaS + // platform and is usually different from the name of the callback + // function (which may be stored in the + // [`code.namespace`/`code.function`](../../trace/semantic_conventions/span- + // general.md#source-code-attributes) + // span attributes). + + // For some cloud providers, the above definition is ambiguous. The following + // definition of function name MUST be used for this attribute + // (and consequently the span name) for the listed cloud providers/products: + + // * **Azure:** The full name `/`, i.e., function app name + // followed by a forward slash followed by the function name (this form + // can also be seen in the resource JSON for the function). + // This means that a span attribute MUST be used, as an Azure function + // app can host multiple functions that would usually share + // a TracerProvider (see also the `faas.id` attribute). + FaaSNameKey = attribute.Key("faas.name") + // The unique ID of the single function that this runtime instance executes. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'arn:aws:lambda:us-west-2:123456789012:function:my-function' + // Note: On some cloud providers, it may not be possible to determine the full ID + // at startup, + // so consider setting `faas.id` as a span attribute instead. + + // The exact value to use for `faas.id` depends on the cloud provider: + + // * **AWS Lambda:** The function + // [ARN](https://docs.aws.amazon.com/general/latest/gr/aws-arns-and- + // namespaces.html). + // Take care not to use the "invoked ARN" directly but replace any + // [alias suffix](https://docs.aws.amazon.com/lambda/latest/dg/configuration- + // aliases.html) + // with the resolved function version, as the same runtime instance may be + // invokable with + // multiple different aliases. + // * **GCP:** The [URI of the resource](https://cloud.google.com/iam/docs/full- + // resource-names) + // * **Azure:** The [Fully Qualified Resource ID](https://docs.microsoft.com/en- + // us/rest/api/resources/resources/get-by-id) of the invoked function, + // *not* the function app, having the form + // `/subscriptions//resourceGroups//providers/Microsoft.We + // b/sites//functions/`. + // This means that a span attribute MUST be used, as an Azure function app can + // host multiple functions that would usually share + // a TracerProvider. + FaaSIDKey = attribute.Key("faas.id") + // The immutable version of the function being executed. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: '26', 'pinkfroid-00002' + // Note: Depending on the cloud provider and platform, use: + + // * **AWS Lambda:** The [function + // version](https://docs.aws.amazon.com/lambda/latest/dg/configuration- + // versions.html) + // (an integer represented as a decimal string). + // * **Google Cloud Run:** The + // [revision](https://cloud.google.com/run/docs/managing/revisions) + // (i.e., the function name plus the revision suffix). + // * **Google Cloud Functions:** The value of the + // [`K_REVISION` environment + // variable](https://cloud.google.com/functions/docs/env- + // var#runtime_environment_variables_set_automatically). + // * **Azure Functions:** Not applicable. Do not set this attribute. + FaaSVersionKey = attribute.Key("faas.version") + // The execution environment ID as a string, that will be potentially reused for + // other invocations to the same function/function version. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: '2021/06/28/[$LATEST]2f399eb14537447da05ab2a2e39309de' + // Note: * **AWS Lambda:** Use the (full) log stream name. + FaaSInstanceKey = attribute.Key("faas.instance") + // The amount of memory available to the serverless function in MiB. + // + // Type: int + // RequirementLevel: Optional + // Stability: stable + // Examples: 128 + // Note: It's recommended to set this attribute since e.g. too little memory can + // easily stop a Java AWS Lambda function from working correctly. On AWS Lambda, + // the environment variable `AWS_LAMBDA_FUNCTION_MEMORY_SIZE` provides this + // information. + FaaSMaxMemoryKey = attribute.Key("faas.max_memory") +) + +// A host is defined as a general computing instance. +const ( + // Unique host ID. For Cloud, this must be the instance_id assigned by the cloud + // provider. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'opentelemetry-test' + HostIDKey = attribute.Key("host.id") + // Name of the host. On Unix systems, it may contain what the hostname command + // returns, or the fully qualified hostname, or another name specified by the + // user. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'opentelemetry-test' + HostNameKey = attribute.Key("host.name") + // Type of host. For Cloud, this must be the machine type. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'n1-standard-1' + HostTypeKey = attribute.Key("host.type") + // The CPU architecture the host system is running on. + // + // Type: Enum + // RequirementLevel: Optional + // Stability: stable + HostArchKey = attribute.Key("host.arch") + // Name of the VM image or OS install the host was instantiated from. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'infra-ami-eks-worker-node-7d4ec78312', 'CentOS-8-x86_64-1905' + HostImageNameKey = attribute.Key("host.image.name") + // VM image ID. For Cloud, this value is from the provider. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'ami-07b06b442921831e5' + HostImageIDKey = attribute.Key("host.image.id") + // The version string of the VM image as defined in [Version + // Attributes](README.md#version-attributes). + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: '0.1' + HostImageVersionKey = attribute.Key("host.image.version") +) + +var ( + // AMD64 + HostArchAMD64 = HostArchKey.String("amd64") + // ARM32 + HostArchARM32 = HostArchKey.String("arm32") + // ARM64 + HostArchARM64 = HostArchKey.String("arm64") + // Itanium + HostArchIA64 = HostArchKey.String("ia64") + // 32-bit PowerPC + HostArchPPC32 = HostArchKey.String("ppc32") + // 64-bit PowerPC + HostArchPPC64 = HostArchKey.String("ppc64") + // IBM z/Architecture + HostArchS390x = HostArchKey.String("s390x") + // 32-bit x86 + HostArchX86 = HostArchKey.String("x86") +) + +// A Kubernetes Cluster. +const ( + // The name of the cluster. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'opentelemetry-cluster' + K8SClusterNameKey = attribute.Key("k8s.cluster.name") +) + +// A Kubernetes Node object. +const ( + // The name of the Node. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'node-1' + K8SNodeNameKey = attribute.Key("k8s.node.name") + // The UID of the Node. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: '1eb3a0c6-0477-4080-a9cb-0cb7db65c6a2' + K8SNodeUIDKey = attribute.Key("k8s.node.uid") +) + +// A Kubernetes Namespace. +const ( + // The name of the namespace that the pod is running in. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'default' + K8SNamespaceNameKey = attribute.Key("k8s.namespace.name") +) + +// A Kubernetes Pod object. +const ( + // The UID of the Pod. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: '275ecb36-5aa8-4c2a-9c47-d8bb681b9aff' + K8SPodUIDKey = attribute.Key("k8s.pod.uid") + // The name of the Pod. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'opentelemetry-pod-autoconf' + K8SPodNameKey = attribute.Key("k8s.pod.name") +) + +// A container in a [PodTemplate](https://kubernetes.io/docs/concepts/workloads/pods/#pod-templates). +const ( + // The name of the Container from Pod specification, must be unique within a Pod. + // Container runtime usually uses different globally unique name + // (`container.name`). + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'redis' + K8SContainerNameKey = attribute.Key("k8s.container.name") + // Number of times the container was restarted. This attribute can be used to + // identify a particular container (running or stopped) within a container spec. + // + // Type: int + // RequirementLevel: Optional + // Stability: stable + // Examples: 0, 2 + K8SContainerRestartCountKey = attribute.Key("k8s.container.restart_count") +) + +// A Kubernetes ReplicaSet object. +const ( + // The UID of the ReplicaSet. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: '275ecb36-5aa8-4c2a-9c47-d8bb681b9aff' + K8SReplicaSetUIDKey = attribute.Key("k8s.replicaset.uid") + // The name of the ReplicaSet. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'opentelemetry' + K8SReplicaSetNameKey = attribute.Key("k8s.replicaset.name") +) + +// A Kubernetes Deployment object. +const ( + // The UID of the Deployment. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: '275ecb36-5aa8-4c2a-9c47-d8bb681b9aff' + K8SDeploymentUIDKey = attribute.Key("k8s.deployment.uid") + // The name of the Deployment. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'opentelemetry' + K8SDeploymentNameKey = attribute.Key("k8s.deployment.name") +) + +// A Kubernetes StatefulSet object. +const ( + // The UID of the StatefulSet. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: '275ecb36-5aa8-4c2a-9c47-d8bb681b9aff' + K8SStatefulSetUIDKey = attribute.Key("k8s.statefulset.uid") + // The name of the StatefulSet. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'opentelemetry' + K8SStatefulSetNameKey = attribute.Key("k8s.statefulset.name") +) + +// A Kubernetes DaemonSet object. +const ( + // The UID of the DaemonSet. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: '275ecb36-5aa8-4c2a-9c47-d8bb681b9aff' + K8SDaemonSetUIDKey = attribute.Key("k8s.daemonset.uid") + // The name of the DaemonSet. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'opentelemetry' + K8SDaemonSetNameKey = attribute.Key("k8s.daemonset.name") +) + +// A Kubernetes Job object. +const ( + // The UID of the Job. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: '275ecb36-5aa8-4c2a-9c47-d8bb681b9aff' + K8SJobUIDKey = attribute.Key("k8s.job.uid") + // The name of the Job. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'opentelemetry' + K8SJobNameKey = attribute.Key("k8s.job.name") +) + +// A Kubernetes CronJob object. +const ( + // The UID of the CronJob. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: '275ecb36-5aa8-4c2a-9c47-d8bb681b9aff' + K8SCronJobUIDKey = attribute.Key("k8s.cronjob.uid") + // The name of the CronJob. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'opentelemetry' + K8SCronJobNameKey = attribute.Key("k8s.cronjob.name") +) + +// The operating system (OS) on which the process represented by this resource is running. +const ( + // The operating system type. + // + // Type: Enum + // RequirementLevel: Required + // Stability: stable + OSTypeKey = attribute.Key("os.type") + // Human readable (not intended to be parsed) OS version information, like e.g. + // reported by `ver` or `lsb_release -a` commands. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'Microsoft Windows [Version 10.0.18363.778]', 'Ubuntu 18.04.1 LTS' + OSDescriptionKey = attribute.Key("os.description") + // Human readable operating system name. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'iOS', 'Android', 'Ubuntu' + OSNameKey = attribute.Key("os.name") + // The version string of the operating system as defined in [Version + // Attributes](../../resource/semantic_conventions/README.md#version-attributes). + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: '14.2.1', '18.04.1' + OSVersionKey = attribute.Key("os.version") +) + +var ( + // Microsoft Windows + OSTypeWindows = OSTypeKey.String("windows") + // Linux + OSTypeLinux = OSTypeKey.String("linux") + // Apple Darwin + OSTypeDarwin = OSTypeKey.String("darwin") + // FreeBSD + OSTypeFreeBSD = OSTypeKey.String("freebsd") + // NetBSD + OSTypeNetBSD = OSTypeKey.String("netbsd") + // OpenBSD + OSTypeOpenBSD = OSTypeKey.String("openbsd") + // DragonFly BSD + OSTypeDragonflyBSD = OSTypeKey.String("dragonflybsd") + // HP-UX (Hewlett Packard Unix) + OSTypeHPUX = OSTypeKey.String("hpux") + // AIX (Advanced Interactive eXecutive) + OSTypeAIX = OSTypeKey.String("aix") + // SunOS, Oracle Solaris + OSTypeSolaris = OSTypeKey.String("solaris") + // IBM z/OS + OSTypeZOS = OSTypeKey.String("z_os") +) + +// An operating system process. +const ( + // Process identifier (PID). + // + // Type: int + // RequirementLevel: Optional + // Stability: stable + // Examples: 1234 + ProcessPIDKey = attribute.Key("process.pid") + // Parent Process identifier (PID). + // + // Type: int + // RequirementLevel: Optional + // Stability: stable + // Examples: 111 + ProcessParentPIDKey = attribute.Key("process.parent_pid") + // The name of the process executable. On Linux based systems, can be set to the + // `Name` in `proc/[pid]/status`. On Windows, can be set to the base name of + // `GetProcessImageFileNameW`. + // + // Type: string + // RequirementLevel: ConditionallyRequired (See alternative attributes below.) + // Stability: stable + // Examples: 'otelcol' + ProcessExecutableNameKey = attribute.Key("process.executable.name") + // The full path to the process executable. On Linux based systems, can be set to + // the target of `proc/[pid]/exe`. On Windows, can be set to the result of + // `GetProcessImageFileNameW`. + // + // Type: string + // RequirementLevel: ConditionallyRequired (See alternative attributes below.) + // Stability: stable + // Examples: '/usr/bin/cmd/otelcol' + ProcessExecutablePathKey = attribute.Key("process.executable.path") + // The command used to launch the process (i.e. the command name). On Linux based + // systems, can be set to the zeroth string in `proc/[pid]/cmdline`. On Windows, + // can be set to the first parameter extracted from `GetCommandLineW`. + // + // Type: string + // RequirementLevel: ConditionallyRequired (See alternative attributes below.) + // Stability: stable + // Examples: 'cmd/otelcol' + ProcessCommandKey = attribute.Key("process.command") + // The full command used to launch the process as a single string representing the + // full command. On Windows, can be set to the result of `GetCommandLineW`. Do not + // set this if you have to assemble it just for monitoring; use + // `process.command_args` instead. + // + // Type: string + // RequirementLevel: ConditionallyRequired (See alternative attributes below.) + // Stability: stable + // Examples: 'C:\\cmd\\otecol --config="my directory\\config.yaml"' + ProcessCommandLineKey = attribute.Key("process.command_line") + // All the command arguments (including the command/executable itself) as received + // by the process. On Linux-based systems (and some other Unixoid systems + // supporting procfs), can be set according to the list of null-delimited strings + // extracted from `proc/[pid]/cmdline`. For libc-based executables, this would be + // the full argv vector passed to `main`. + // + // Type: string[] + // RequirementLevel: ConditionallyRequired (See alternative attributes below.) + // Stability: stable + // Examples: 'cmd/otecol', '--config=config.yaml' + ProcessCommandArgsKey = attribute.Key("process.command_args") + // The username of the user that owns the process. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'root' + ProcessOwnerKey = attribute.Key("process.owner") +) + +// The single (language) runtime instance which is monitored. +const ( + // The name of the runtime of this process. For compiled native binaries, this + // SHOULD be the name of the compiler. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'OpenJDK Runtime Environment' + ProcessRuntimeNameKey = attribute.Key("process.runtime.name") + // The version of the runtime of this process, as returned by the runtime without + // modification. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: '14.0.2' + ProcessRuntimeVersionKey = attribute.Key("process.runtime.version") + // An additional description about the runtime of the process, for example a + // specific vendor customization of the runtime environment. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'Eclipse OpenJ9 Eclipse OpenJ9 VM openj9-0.21.0' + ProcessRuntimeDescriptionKey = attribute.Key("process.runtime.description") +) + +// A service instance. +const ( + // Logical name of the service. + // + // Type: string + // RequirementLevel: Required + // Stability: stable + // Examples: 'shoppingcart' + // Note: MUST be the same for all instances of horizontally scaled services. If + // the value was not specified, SDKs MUST fallback to `unknown_service:` + // concatenated with [`process.executable.name`](process.md#process), e.g. + // `unknown_service:bash`. If `process.executable.name` is not available, the + // value MUST be set to `unknown_service`. + ServiceNameKey = attribute.Key("service.name") + // A namespace for `service.name`. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'Shop' + // Note: A string value having a meaning that helps to distinguish a group of + // services, for example the team name that owns a group of services. + // `service.name` is expected to be unique within the same namespace. If + // `service.namespace` is not specified in the Resource then `service.name` is + // expected to be unique for all services that have no explicit namespace defined + // (so the empty/unspecified namespace is simply one more valid namespace). Zero- + // length namespace string is assumed equal to unspecified namespace. + ServiceNamespaceKey = attribute.Key("service.namespace") + // The string ID of the service instance. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: '627cc493-f310-47de-96bd-71410b7dec09' + // Note: MUST be unique for each instance of the same + // `service.namespace,service.name` pair (in other words + // `service.namespace,service.name,service.instance.id` triplet MUST be globally + // unique). The ID helps to distinguish instances of the same service that exist + // at the same time (e.g. instances of a horizontally scaled service). It is + // preferable for the ID to be persistent and stay the same for the lifetime of + // the service instance, however it is acceptable that the ID is ephemeral and + // changes during important lifetime events for the service (e.g. service + // restarts). If the service has no inherent unique ID that can be used as the + // value of this attribute it is recommended to generate a random Version 1 or + // Version 4 RFC 4122 UUID (services aiming for reproducible UUIDs may also use + // Version 5, see RFC 4122 for more recommendations). + ServiceInstanceIDKey = attribute.Key("service.instance.id") + // The version string of the service API or implementation. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: '2.0.0' + ServiceVersionKey = attribute.Key("service.version") +) + +// The telemetry SDK used to capture data recorded by the instrumentation libraries. +const ( + // The name of the telemetry SDK as defined above. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'opentelemetry' + TelemetrySDKNameKey = attribute.Key("telemetry.sdk.name") + // The language of the telemetry SDK. + // + // Type: Enum + // RequirementLevel: Optional + // Stability: stable + TelemetrySDKLanguageKey = attribute.Key("telemetry.sdk.language") + // The version string of the telemetry SDK. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: '1.2.3' + TelemetrySDKVersionKey = attribute.Key("telemetry.sdk.version") + // The version string of the auto instrumentation agent, if used. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: '1.2.3' + TelemetryAutoVersionKey = attribute.Key("telemetry.auto.version") +) + +var ( + // cpp + TelemetrySDKLanguageCPP = TelemetrySDKLanguageKey.String("cpp") + // dotnet + TelemetrySDKLanguageDotnet = TelemetrySDKLanguageKey.String("dotnet") + // erlang + TelemetrySDKLanguageErlang = TelemetrySDKLanguageKey.String("erlang") + // go + TelemetrySDKLanguageGo = TelemetrySDKLanguageKey.String("go") + // java + TelemetrySDKLanguageJava = TelemetrySDKLanguageKey.String("java") + // nodejs + TelemetrySDKLanguageNodejs = TelemetrySDKLanguageKey.String("nodejs") + // php + TelemetrySDKLanguagePHP = TelemetrySDKLanguageKey.String("php") + // python + TelemetrySDKLanguagePython = TelemetrySDKLanguageKey.String("python") + // ruby + TelemetrySDKLanguageRuby = TelemetrySDKLanguageKey.String("ruby") + // webjs + TelemetrySDKLanguageWebjs = TelemetrySDKLanguageKey.String("webjs") + // swift + TelemetrySDKLanguageSwift = TelemetrySDKLanguageKey.String("swift") +) + +// Resource describing the packaged software running the application code. Web engines are typically executed using process.runtime. +const ( + // The name of the web engine. + // + // Type: string + // RequirementLevel: Required + // Stability: stable + // Examples: 'WildFly' + WebEngineNameKey = attribute.Key("webengine.name") + // The version of the web engine. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: '21.0.0' + WebEngineVersionKey = attribute.Key("webengine.version") + // Additional description of the web engine (e.g. detailed version and edition + // information). + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'WildFly Full 21.0.0.Final (WildFly Core 13.0.1.Final) - 2.2.2.Final' + WebEngineDescriptionKey = attribute.Key("webengine.description") +) diff --git a/semconv/v1.13.0/schema.go b/semconv/v1.13.0/schema.go new file mode 100644 index 00000000000..1f4dfb8c65f --- /dev/null +++ b/semconv/v1.13.0/schema.go @@ -0,0 +1,20 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package semconv // import "go.opentelemetry.io/otel/semconv/v1.13.0" + +// SchemaURL is the schema URL that matches the version of the semantic conventions +// that this package defines. Semconv packages starting from v1.4.0 must declare +// non-empty schema URL in the form https://opentelemetry.io/schemas/ +const SchemaURL = "https://opentelemetry.io/schemas/1.13.0" diff --git a/semconv/v1.13.0/trace.go b/semconv/v1.13.0/trace.go new file mode 100644 index 00000000000..7458337627e --- /dev/null +++ b/semconv/v1.13.0/trace.go @@ -0,0 +1,1772 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated from semantic convention specification. DO NOT EDIT. + +package semconv // import "go.opentelemetry.io/otel/semconv/v1.13.0" + +import "go.opentelemetry.io/otel/attribute" + +// Span attributes used by AWS Lambda (in addition to general `faas` attributes). +const ( + // The full invoked ARN as provided on the `Context` passed to the function + // (`Lambda-Runtime-Invoked-Function-ARN` header on the `/runtime/invocation/next` + // applicable). + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'arn:aws:lambda:us-east-1:123456:function:myfunction:myalias' + // Note: This may be different from `faas.id` if an alias is involved. + AWSLambdaInvokedARNKey = attribute.Key("aws.lambda.invoked_arn") +) + +// This document defines attributes for CloudEvents. CloudEvents is a specification on how to define event data in a standard way. These attributes can be attached to spans when performing operations with CloudEvents, regardless of the protocol being used. +const ( + // The [event_id](https://github.com/cloudevents/spec/blob/v1.0.2/cloudevents/spec + // .md#id) uniquely identifies the event. + // + // Type: string + // RequirementLevel: Required + // Stability: stable + // Examples: '123e4567-e89b-12d3-a456-426614174000', '0001' + CloudeventsEventIDKey = attribute.Key("cloudevents.event_id") + // The [source](https://github.com/cloudevents/spec/blob/v1.0.2/cloudevents/spec.m + // d#source-1) identifies the context in which an event happened. + // + // Type: string + // RequirementLevel: Required + // Stability: stable + // Examples: 'https://github.com/cloudevents', '/cloudevents/spec/pull/123', 'my- + // service' + CloudeventsEventSourceKey = attribute.Key("cloudevents.event_source") + // The [version of the CloudEvents specification](https://github.com/cloudevents/s + // pec/blob/v1.0.2/cloudevents/spec.md#specversion) which the event uses. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: '1.0' + CloudeventsEventSpecVersionKey = attribute.Key("cloudevents.event_spec_version") + // The [event_type](https://github.com/cloudevents/spec/blob/v1.0.2/cloudevents/sp + // ec.md#type) contains a value describing the type of event related to the + // originating occurrence. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'com.github.pull_request.opened', 'com.example.object.deleted.v2' + CloudeventsEventTypeKey = attribute.Key("cloudevents.event_type") + // The [subject](https://github.com/cloudevents/spec/blob/v1.0.2/cloudevents/spec. + // md#subject) of the event in the context of the event producer (identified by + // source). + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'mynewfile.jpg' + CloudeventsEventSubjectKey = attribute.Key("cloudevents.event_subject") +) + +// This document defines semantic conventions for the OpenTracing Shim +const ( + // Parent-child Reference type + // + // Type: Enum + // RequirementLevel: Optional + // Stability: stable + // Note: The causal relationship between a child Span and a parent Span. + OpentracingRefTypeKey = attribute.Key("opentracing.ref_type") +) + +var ( + // The parent Span depends on the child Span in some capacity + OpentracingRefTypeChildOf = OpentracingRefTypeKey.String("child_of") + // The parent Span does not depend in any way on the result of the child Span + OpentracingRefTypeFollowsFrom = OpentracingRefTypeKey.String("follows_from") +) + +// This document defines the attributes used to perform database client calls. +const ( + // An identifier for the database management system (DBMS) product being used. See + // below for a list of well-known identifiers. + // + // Type: Enum + // RequirementLevel: Required + // Stability: stable + DBSystemKey = attribute.Key("db.system") + // The connection string used to connect to the database. It is recommended to + // remove embedded credentials. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'Server=(localdb)\\v11.0;Integrated Security=true;' + DBConnectionStringKey = attribute.Key("db.connection_string") + // Username for accessing the database. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'readonly_user', 'reporting_user' + DBUserKey = attribute.Key("db.user") + // The fully-qualified class name of the [Java Database Connectivity + // (JDBC)](https://docs.oracle.com/javase/8/docs/technotes/guides/jdbc/) driver + // used to connect. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'org.postgresql.Driver', + // 'com.microsoft.sqlserver.jdbc.SQLServerDriver' + DBJDBCDriverClassnameKey = attribute.Key("db.jdbc.driver_classname") + // This attribute is used to report the name of the database being accessed. For + // commands that switch the database, this should be set to the target database + // (even if the command fails). + // + // Type: string + // RequirementLevel: ConditionallyRequired (If applicable.) + // Stability: stable + // Examples: 'customers', 'main' + // Note: In some SQL databases, the database name to be used is called "schema + // name". In case there are multiple layers that could be considered for database + // name (e.g. Oracle instance name and schema name), the database name to be used + // is the more specific layer (e.g. Oracle schema name). + DBNameKey = attribute.Key("db.name") + // The database statement being executed. + // + // Type: string + // RequirementLevel: ConditionallyRequired (If applicable and not explicitly + // disabled via instrumentation configuration.) + // Stability: stable + // Examples: 'SELECT * FROM wuser_table', 'SET mykey "WuValue"' + // Note: The value may be sanitized to exclude sensitive information. + DBStatementKey = attribute.Key("db.statement") + // The name of the operation being executed, e.g. the [MongoDB command + // name](https://docs.mongodb.com/manual/reference/command/#database-operations) + // such as `findAndModify`, or the SQL keyword. + // + // Type: string + // RequirementLevel: ConditionallyRequired (If `db.statement` is not applicable.) + // Stability: stable + // Examples: 'findAndModify', 'HMSET', 'SELECT' + // Note: When setting this to an SQL keyword, it is not recommended to attempt any + // client-side parsing of `db.statement` just to get this property, but it should + // be set if the operation name is provided by the library being instrumented. If + // the SQL statement has an ambiguous operation, or performs more than one + // operation, this value may be omitted. + DBOperationKey = attribute.Key("db.operation") +) + +var ( + // Some other SQL database. Fallback only. See notes + DBSystemOtherSQL = DBSystemKey.String("other_sql") + // Microsoft SQL Server + DBSystemMSSQL = DBSystemKey.String("mssql") + // MySQL + DBSystemMySQL = DBSystemKey.String("mysql") + // Oracle Database + DBSystemOracle = DBSystemKey.String("oracle") + // IBM DB2 + DBSystemDB2 = DBSystemKey.String("db2") + // PostgreSQL + DBSystemPostgreSQL = DBSystemKey.String("postgresql") + // Amazon Redshift + DBSystemRedshift = DBSystemKey.String("redshift") + // Apache Hive + DBSystemHive = DBSystemKey.String("hive") + // Cloudscape + DBSystemCloudscape = DBSystemKey.String("cloudscape") + // HyperSQL DataBase + DBSystemHSQLDB = DBSystemKey.String("hsqldb") + // Progress Database + DBSystemProgress = DBSystemKey.String("progress") + // SAP MaxDB + DBSystemMaxDB = DBSystemKey.String("maxdb") + // SAP HANA + DBSystemHanaDB = DBSystemKey.String("hanadb") + // Ingres + DBSystemIngres = DBSystemKey.String("ingres") + // FirstSQL + DBSystemFirstSQL = DBSystemKey.String("firstsql") + // EnterpriseDB + DBSystemEDB = DBSystemKey.String("edb") + // InterSystems Caché + DBSystemCache = DBSystemKey.String("cache") + // Adabas (Adaptable Database System) + DBSystemAdabas = DBSystemKey.String("adabas") + // Firebird + DBSystemFirebird = DBSystemKey.String("firebird") + // Apache Derby + DBSystemDerby = DBSystemKey.String("derby") + // FileMaker + DBSystemFilemaker = DBSystemKey.String("filemaker") + // Informix + DBSystemInformix = DBSystemKey.String("informix") + // InstantDB + DBSystemInstantDB = DBSystemKey.String("instantdb") + // InterBase + DBSystemInterbase = DBSystemKey.String("interbase") + // MariaDB + DBSystemMariaDB = DBSystemKey.String("mariadb") + // Netezza + DBSystemNetezza = DBSystemKey.String("netezza") + // Pervasive PSQL + DBSystemPervasive = DBSystemKey.String("pervasive") + // PointBase + DBSystemPointbase = DBSystemKey.String("pointbase") + // SQLite + DBSystemSqlite = DBSystemKey.String("sqlite") + // Sybase + DBSystemSybase = DBSystemKey.String("sybase") + // Teradata + DBSystemTeradata = DBSystemKey.String("teradata") + // Vertica + DBSystemVertica = DBSystemKey.String("vertica") + // H2 + DBSystemH2 = DBSystemKey.String("h2") + // ColdFusion IMQ + DBSystemColdfusion = DBSystemKey.String("coldfusion") + // Apache Cassandra + DBSystemCassandra = DBSystemKey.String("cassandra") + // Apache HBase + DBSystemHBase = DBSystemKey.String("hbase") + // MongoDB + DBSystemMongoDB = DBSystemKey.String("mongodb") + // Redis + DBSystemRedis = DBSystemKey.String("redis") + // Couchbase + DBSystemCouchbase = DBSystemKey.String("couchbase") + // CouchDB + DBSystemCouchDB = DBSystemKey.String("couchdb") + // Microsoft Azure Cosmos DB + DBSystemCosmosDB = DBSystemKey.String("cosmosdb") + // Amazon DynamoDB + DBSystemDynamoDB = DBSystemKey.String("dynamodb") + // Neo4j + DBSystemNeo4j = DBSystemKey.String("neo4j") + // Apache Geode + DBSystemGeode = DBSystemKey.String("geode") + // Elasticsearch + DBSystemElasticsearch = DBSystemKey.String("elasticsearch") + // Memcached + DBSystemMemcached = DBSystemKey.String("memcached") + // CockroachDB + DBSystemCockroachdb = DBSystemKey.String("cockroachdb") + // OpenSearch + DBSystemOpensearch = DBSystemKey.String("opensearch") +) + +// Connection-level attributes for Microsoft SQL Server +const ( + // The Microsoft SQL Server [instance name](https://docs.microsoft.com/en- + // us/sql/connect/jdbc/building-the-connection-url?view=sql-server-ver15) + // connecting to. This name is used to determine the port of a named instance. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'MSSQLSERVER' + // Note: If setting a `db.mssql.instance_name`, `net.peer.port` is no longer + // required (but still recommended if non-standard). + DBMSSQLInstanceNameKey = attribute.Key("db.mssql.instance_name") +) + +// Call-level attributes for Cassandra +const ( + // The fetch size used for paging, i.e. how many rows will be returned at once. + // + // Type: int + // RequirementLevel: Optional + // Stability: stable + // Examples: 5000 + DBCassandraPageSizeKey = attribute.Key("db.cassandra.page_size") + // The consistency level of the query. Based on consistency values from + // [CQL](https://docs.datastax.com/en/cassandra- + // oss/3.0/cassandra/dml/dmlConfigConsistency.html). + // + // Type: Enum + // RequirementLevel: Optional + // Stability: stable + DBCassandraConsistencyLevelKey = attribute.Key("db.cassandra.consistency_level") + // The name of the primary table that the operation is acting upon, including the + // keyspace name (if applicable). + // + // Type: string + // RequirementLevel: Recommended + // Stability: stable + // Examples: 'mytable' + // Note: This mirrors the db.sql.table attribute but references cassandra rather + // than sql. It is not recommended to attempt any client-side parsing of + // `db.statement` just to get this property, but it should be set if it is + // provided by the library being instrumented. If the operation is acting upon an + // anonymous table, or more than one table, this value MUST NOT be set. + DBCassandraTableKey = attribute.Key("db.cassandra.table") + // Whether or not the query is idempotent. + // + // Type: boolean + // RequirementLevel: Optional + // Stability: stable + DBCassandraIdempotenceKey = attribute.Key("db.cassandra.idempotence") + // The number of times a query was speculatively executed. Not set or `0` if the + // query was not executed speculatively. + // + // Type: int + // RequirementLevel: Optional + // Stability: stable + // Examples: 0, 2 + DBCassandraSpeculativeExecutionCountKey = attribute.Key("db.cassandra.speculative_execution_count") + // The ID of the coordinating node for a query. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'be13faa2-8574-4d71-926d-27f16cf8a7af' + DBCassandraCoordinatorIDKey = attribute.Key("db.cassandra.coordinator.id") + // The data center of the coordinating node for a query. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'us-west-2' + DBCassandraCoordinatorDCKey = attribute.Key("db.cassandra.coordinator.dc") +) + +var ( + // all + DBCassandraConsistencyLevelAll = DBCassandraConsistencyLevelKey.String("all") + // each_quorum + DBCassandraConsistencyLevelEachQuorum = DBCassandraConsistencyLevelKey.String("each_quorum") + // quorum + DBCassandraConsistencyLevelQuorum = DBCassandraConsistencyLevelKey.String("quorum") + // local_quorum + DBCassandraConsistencyLevelLocalQuorum = DBCassandraConsistencyLevelKey.String("local_quorum") + // one + DBCassandraConsistencyLevelOne = DBCassandraConsistencyLevelKey.String("one") + // two + DBCassandraConsistencyLevelTwo = DBCassandraConsistencyLevelKey.String("two") + // three + DBCassandraConsistencyLevelThree = DBCassandraConsistencyLevelKey.String("three") + // local_one + DBCassandraConsistencyLevelLocalOne = DBCassandraConsistencyLevelKey.String("local_one") + // any + DBCassandraConsistencyLevelAny = DBCassandraConsistencyLevelKey.String("any") + // serial + DBCassandraConsistencyLevelSerial = DBCassandraConsistencyLevelKey.String("serial") + // local_serial + DBCassandraConsistencyLevelLocalSerial = DBCassandraConsistencyLevelKey.String("local_serial") +) + +// Call-level attributes for Redis +const ( + // The index of the database being accessed as used in the [`SELECT` + // command](https://redis.io/commands/select), provided as an integer. To be used + // instead of the generic `db.name` attribute. + // + // Type: int + // RequirementLevel: ConditionallyRequired (If other than the default database + // (`0`).) + // Stability: stable + // Examples: 0, 1, 15 + DBRedisDBIndexKey = attribute.Key("db.redis.database_index") +) + +// Call-level attributes for MongoDB +const ( + // The collection being accessed within the database stated in `db.name`. + // + // Type: string + // RequirementLevel: Required + // Stability: stable + // Examples: 'customers', 'products' + DBMongoDBCollectionKey = attribute.Key("db.mongodb.collection") +) + +// Call-level attributes for SQL databases +const ( + // The name of the primary table that the operation is acting upon, including the + // database name (if applicable). + // + // Type: string + // RequirementLevel: Recommended + // Stability: stable + // Examples: 'public.users', 'customers' + // Note: It is not recommended to attempt any client-side parsing of + // `db.statement` just to get this property, but it should be set if it is + // provided by the library being instrumented. If the operation is acting upon an + // anonymous table, or more than one table, this value MUST NOT be set. + DBSQLTableKey = attribute.Key("db.sql.table") +) + +// This document defines the attributes used to report a single exception associated with a span. +const ( + // The type of the exception (its fully-qualified class name, if applicable). The + // dynamic type of the exception should be preferred over the static type in + // languages that support it. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'java.net.ConnectException', 'OSError' + ExceptionTypeKey = attribute.Key("exception.type") + // The exception message. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'Division by zero', "Can't convert 'int' object to str implicitly" + ExceptionMessageKey = attribute.Key("exception.message") + // A stacktrace as a string in the natural representation for the language + // runtime. The representation is to be determined and documented by each language + // SIG. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'Exception in thread "main" java.lang.RuntimeException: Test + // exception\\n at ' + // 'com.example.GenerateTrace.methodB(GenerateTrace.java:13)\\n at ' + // 'com.example.GenerateTrace.methodA(GenerateTrace.java:9)\\n at ' + // 'com.example.GenerateTrace.main(GenerateTrace.java:5)' + ExceptionStacktraceKey = attribute.Key("exception.stacktrace") + // SHOULD be set to true if the exception event is recorded at a point where it is + // known that the exception is escaping the scope of the span. + // + // Type: boolean + // RequirementLevel: Optional + // Stability: stable + // Note: An exception is considered to have escaped (or left) the scope of a span, + // if that span is ended while the exception is still logically "in flight". + // This may be actually "in flight" in some languages (e.g. if the exception + // is passed to a Context manager's `__exit__` method in Python) but will + // usually be caught at the point of recording the exception in most languages. + + // It is usually not possible to determine at the point where an exception is + // thrown + // whether it will escape the scope of a span. + // However, it is trivial to know that an exception + // will escape, if one checks for an active exception just before ending the span, + // as done in the [example above](#recording-an-exception). + + // It follows that an exception may still escape the scope of the span + // even if the `exception.escaped` attribute was not set or set to false, + // since the event might have been recorded at a time where it was not + // clear whether the exception will escape. + ExceptionEscapedKey = attribute.Key("exception.escaped") +) + +// This semantic convention describes an instance of a function that runs without provisioning or managing of servers (also known as serverless functions or Function as a Service (FaaS)) with spans. +const ( + // Type of the trigger which caused this function execution. + // + // Type: Enum + // RequirementLevel: Optional + // Stability: stable + // Note: For the server/consumer span on the incoming side, + // `faas.trigger` MUST be set. + + // Clients invoking FaaS instances usually cannot set `faas.trigger`, + // since they would typically need to look in the payload to determine + // the event type. If clients set it, it should be the same as the + // trigger that corresponding incoming would have (i.e., this has + // nothing to do with the underlying transport used to make the API + // call to invoke the lambda, which is often HTTP). + FaaSTriggerKey = attribute.Key("faas.trigger") + // The execution ID of the current function execution. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'af9d5aa4-a685-4c5f-a22b-444f80b3cc28' + FaaSExecutionKey = attribute.Key("faas.execution") +) + +var ( + // A response to some data source operation such as a database or filesystem read/write + FaaSTriggerDatasource = FaaSTriggerKey.String("datasource") + // To provide an answer to an inbound HTTP request + FaaSTriggerHTTP = FaaSTriggerKey.String("http") + // A function is set to be executed when messages are sent to a messaging system + FaaSTriggerPubsub = FaaSTriggerKey.String("pubsub") + // A function is scheduled to be executed regularly + FaaSTriggerTimer = FaaSTriggerKey.String("timer") + // If none of the others apply + FaaSTriggerOther = FaaSTriggerKey.String("other") +) + +// Semantic Convention for FaaS triggered as a response to some data source operation such as a database or filesystem read/write. +const ( + // The name of the source on which the triggering operation was performed. For + // example, in Cloud Storage or S3 corresponds to the bucket name, and in Cosmos + // DB to the database name. + // + // Type: string + // RequirementLevel: Required + // Stability: stable + // Examples: 'myBucketName', 'myDBName' + FaaSDocumentCollectionKey = attribute.Key("faas.document.collection") + // Describes the type of the operation that was performed on the data. + // + // Type: Enum + // RequirementLevel: Required + // Stability: stable + FaaSDocumentOperationKey = attribute.Key("faas.document.operation") + // A string containing the time when the data was accessed in the [ISO + // 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format expressed + // in [UTC](https://www.w3.org/TR/NOTE-datetime). + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: '2020-01-23T13:47:06Z' + FaaSDocumentTimeKey = attribute.Key("faas.document.time") + // The document name/table subjected to the operation. For example, in Cloud + // Storage or S3 is the name of the file, and in Cosmos DB the table name. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'myFile.txt', 'myTableName' + FaaSDocumentNameKey = attribute.Key("faas.document.name") +) + +var ( + // When a new object is created + FaaSDocumentOperationInsert = FaaSDocumentOperationKey.String("insert") + // When an object is modified + FaaSDocumentOperationEdit = FaaSDocumentOperationKey.String("edit") + // When an object is deleted + FaaSDocumentOperationDelete = FaaSDocumentOperationKey.String("delete") +) + +// Semantic Convention for FaaS scheduled to be executed regularly. +const ( + // A string containing the function invocation time in the [ISO + // 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format expressed + // in [UTC](https://www.w3.org/TR/NOTE-datetime). + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: '2020-01-23T13:47:06Z' + FaaSTimeKey = attribute.Key("faas.time") + // A string containing the schedule period as [Cron Expression](https://docs.oracl + // e.com/cd/E12058_01/doc/doc.1014/e12030/cron_expressions.htm). + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: '0/5 * * * ? *' + FaaSCronKey = attribute.Key("faas.cron") +) + +// Contains additional attributes for incoming FaaS spans. +const ( + // A boolean that is true if the serverless function is executed for the first + // time (aka cold-start). + // + // Type: boolean + // RequirementLevel: Optional + // Stability: stable + FaaSColdstartKey = attribute.Key("faas.coldstart") +) + +// Contains additional attributes for outgoing FaaS spans. +const ( + // The name of the invoked function. + // + // Type: string + // RequirementLevel: Required + // Stability: stable + // Examples: 'my-function' + // Note: SHOULD be equal to the `faas.name` resource attribute of the invoked + // function. + FaaSInvokedNameKey = attribute.Key("faas.invoked_name") + // The cloud provider of the invoked function. + // + // Type: Enum + // RequirementLevel: Required + // Stability: stable + // Note: SHOULD be equal to the `cloud.provider` resource attribute of the invoked + // function. + FaaSInvokedProviderKey = attribute.Key("faas.invoked_provider") + // The cloud region of the invoked function. + // + // Type: string + // RequirementLevel: ConditionallyRequired (For some cloud providers, like AWS or + // GCP, the region in which a function is hosted is essential to uniquely identify + // the function and also part of its endpoint. Since it's part of the endpoint + // being called, the region is always known to clients. In these cases, + // `faas.invoked_region` MUST be set accordingly. If the region is unknown to the + // client or not required for identifying the invoked function, setting + // `faas.invoked_region` is optional.) + // Stability: stable + // Examples: 'eu-central-1' + // Note: SHOULD be equal to the `cloud.region` resource attribute of the invoked + // function. + FaaSInvokedRegionKey = attribute.Key("faas.invoked_region") +) + +var ( + // Alibaba Cloud + FaaSInvokedProviderAlibabaCloud = FaaSInvokedProviderKey.String("alibaba_cloud") + // Amazon Web Services + FaaSInvokedProviderAWS = FaaSInvokedProviderKey.String("aws") + // Microsoft Azure + FaaSInvokedProviderAzure = FaaSInvokedProviderKey.String("azure") + // Google Cloud Platform + FaaSInvokedProviderGCP = FaaSInvokedProviderKey.String("gcp") + // Tencent Cloud + FaaSInvokedProviderTencentCloud = FaaSInvokedProviderKey.String("tencent_cloud") +) + +// These attributes may be used for any network related operation. +const ( + // Transport protocol used. See note below. + // + // Type: Enum + // RequirementLevel: Optional + // Stability: stable + NetTransportKey = attribute.Key("net.transport") + // Application layer protocol used. The value SHOULD be normalized to lowercase. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'amqp', 'http', 'mqtt' + NetAppProtocolNameKey = attribute.Key("net.app.protocol.name") + // Version of the application layer protocol used. See note below. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: '3.1.1' + // Note: `net.app.protocol.version` refers to the version of the protocol used and + // might be different from the protocol client's version. If the HTTP client used + // has a version of `0.27.2`, but sends HTTP version `1.1`, this attribute should + // be set to `1.1`. + NetAppProtocolVersionKey = attribute.Key("net.app.protocol.version") + // Remote socket peer name. + // + // Type: string + // RequirementLevel: Recommended (If available and different from `net.peer.name` + // and if `net.sock.peer.addr` is set.) + // Stability: stable + // Examples: 'proxy.example.com' + NetSockPeerNameKey = attribute.Key("net.sock.peer.name") + // Remote socket peer address: IPv4 or IPv6 for internet protocols, path for local + // communication, [etc](https://man7.org/linux/man- + // pages/man7/address_families.7.html). + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: '127.0.0.1', '/tmp/mysql.sock' + NetSockPeerAddrKey = attribute.Key("net.sock.peer.addr") + // Remote socket peer port. + // + // Type: int + // RequirementLevel: Recommended (If defined for the address family and if + // different than `net.peer.port` and if `net.sock.peer.addr` is set.) + // Stability: stable + // Examples: 16456 + NetSockPeerPortKey = attribute.Key("net.sock.peer.port") + // Protocol [address family](https://man7.org/linux/man- + // pages/man7/address_families.7.html) which is used for communication. + // + // Type: Enum + // RequirementLevel: ConditionallyRequired (If different than `inet` and if any of + // `net.sock.peer.addr` or `net.sock.host.addr` are set. Consumers of telemetry + // SHOULD accept both IPv4 and IPv6 formats for the address in + // `net.sock.peer.addr` if `net.sock.family` is not set. This is to support + // instrumentations that follow previous versions of this document.) + // Stability: stable + // Examples: 'inet6', 'bluetooth' + NetSockFamilyKey = attribute.Key("net.sock.family") + // Logical remote hostname, see note below. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'example.com' + // Note: `net.peer.name` SHOULD NOT be set if capturing it would require an extra + // DNS lookup. + NetPeerNameKey = attribute.Key("net.peer.name") + // Logical remote port number + // + // Type: int + // RequirementLevel: Optional + // Stability: stable + // Examples: 80, 8080, 443 + NetPeerPortKey = attribute.Key("net.peer.port") + // Logical local hostname or similar, see note below. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'localhost' + NetHostNameKey = attribute.Key("net.host.name") + // Logical local port number, preferably the one that the peer used to connect + // + // Type: int + // RequirementLevel: Optional + // Stability: stable + // Examples: 8080 + NetHostPortKey = attribute.Key("net.host.port") + // Local socket address. Useful in case of a multi-IP host. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: '192.168.0.1' + NetSockHostAddrKey = attribute.Key("net.sock.host.addr") + // Local socket port number. + // + // Type: int + // RequirementLevel: Recommended (If defined for the address family and if + // different than `net.host.port` and if `net.sock.host.addr` is set.) + // Stability: stable + // Examples: 35555 + NetSockHostPortKey = attribute.Key("net.sock.host.port") + // The internet connection type currently being used by the host. + // + // Type: Enum + // RequirementLevel: Optional + // Stability: stable + // Examples: 'wifi' + NetHostConnectionTypeKey = attribute.Key("net.host.connection.type") + // This describes more details regarding the connection.type. It may be the type + // of cell technology connection, but it could be used for describing details + // about a wifi connection. + // + // Type: Enum + // RequirementLevel: Optional + // Stability: stable + // Examples: 'LTE' + NetHostConnectionSubtypeKey = attribute.Key("net.host.connection.subtype") + // The name of the mobile carrier. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'sprint' + NetHostCarrierNameKey = attribute.Key("net.host.carrier.name") + // The mobile carrier country code. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: '310' + NetHostCarrierMccKey = attribute.Key("net.host.carrier.mcc") + // The mobile carrier network code. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: '001' + NetHostCarrierMncKey = attribute.Key("net.host.carrier.mnc") + // The ISO 3166-1 alpha-2 2-character country code associated with the mobile + // carrier network. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'DE' + NetHostCarrierIccKey = attribute.Key("net.host.carrier.icc") +) + +var ( + // ip_tcp + NetTransportTCP = NetTransportKey.String("ip_tcp") + // ip_udp + NetTransportUDP = NetTransportKey.String("ip_udp") + // Named or anonymous pipe. See note below + NetTransportPipe = NetTransportKey.String("pipe") + // In-process communication + NetTransportInProc = NetTransportKey.String("inproc") + // Something else (non IP-based) + NetTransportOther = NetTransportKey.String("other") +) + +var ( + // IPv4 address + NetSockFamilyInet = NetSockFamilyKey.String("inet") + // IPv6 address + NetSockFamilyInet6 = NetSockFamilyKey.String("inet6") + // Unix domain socket path + NetSockFamilyUnix = NetSockFamilyKey.String("unix") +) + +var ( + // wifi + NetHostConnectionTypeWifi = NetHostConnectionTypeKey.String("wifi") + // wired + NetHostConnectionTypeWired = NetHostConnectionTypeKey.String("wired") + // cell + NetHostConnectionTypeCell = NetHostConnectionTypeKey.String("cell") + // unavailable + NetHostConnectionTypeUnavailable = NetHostConnectionTypeKey.String("unavailable") + // unknown + NetHostConnectionTypeUnknown = NetHostConnectionTypeKey.String("unknown") +) + +var ( + // GPRS + NetHostConnectionSubtypeGprs = NetHostConnectionSubtypeKey.String("gprs") + // EDGE + NetHostConnectionSubtypeEdge = NetHostConnectionSubtypeKey.String("edge") + // UMTS + NetHostConnectionSubtypeUmts = NetHostConnectionSubtypeKey.String("umts") + // CDMA + NetHostConnectionSubtypeCdma = NetHostConnectionSubtypeKey.String("cdma") + // EVDO Rel. 0 + NetHostConnectionSubtypeEvdo0 = NetHostConnectionSubtypeKey.String("evdo_0") + // EVDO Rev. A + NetHostConnectionSubtypeEvdoA = NetHostConnectionSubtypeKey.String("evdo_a") + // CDMA2000 1XRTT + NetHostConnectionSubtypeCdma20001xrtt = NetHostConnectionSubtypeKey.String("cdma2000_1xrtt") + // HSDPA + NetHostConnectionSubtypeHsdpa = NetHostConnectionSubtypeKey.String("hsdpa") + // HSUPA + NetHostConnectionSubtypeHsupa = NetHostConnectionSubtypeKey.String("hsupa") + // HSPA + NetHostConnectionSubtypeHspa = NetHostConnectionSubtypeKey.String("hspa") + // IDEN + NetHostConnectionSubtypeIden = NetHostConnectionSubtypeKey.String("iden") + // EVDO Rev. B + NetHostConnectionSubtypeEvdoB = NetHostConnectionSubtypeKey.String("evdo_b") + // LTE + NetHostConnectionSubtypeLte = NetHostConnectionSubtypeKey.String("lte") + // EHRPD + NetHostConnectionSubtypeEhrpd = NetHostConnectionSubtypeKey.String("ehrpd") + // HSPAP + NetHostConnectionSubtypeHspap = NetHostConnectionSubtypeKey.String("hspap") + // GSM + NetHostConnectionSubtypeGsm = NetHostConnectionSubtypeKey.String("gsm") + // TD-SCDMA + NetHostConnectionSubtypeTdScdma = NetHostConnectionSubtypeKey.String("td_scdma") + // IWLAN + NetHostConnectionSubtypeIwlan = NetHostConnectionSubtypeKey.String("iwlan") + // 5G NR (New Radio) + NetHostConnectionSubtypeNr = NetHostConnectionSubtypeKey.String("nr") + // 5G NRNSA (New Radio Non-Standalone) + NetHostConnectionSubtypeNrnsa = NetHostConnectionSubtypeKey.String("nrnsa") + // LTE CA + NetHostConnectionSubtypeLteCa = NetHostConnectionSubtypeKey.String("lte_ca") +) + +// Operations that access some remote service. +const ( + // The [`service.name`](../../resource/semantic_conventions/README.md#service) of + // the remote service. SHOULD be equal to the actual `service.name` resource + // attribute of the remote service if any. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'AuthTokenCache' + PeerServiceKey = attribute.Key("peer.service") +) + +// These attributes may be used for any operation with an authenticated and/or authorized enduser. +const ( + // Username or client_id extracted from the access token or + // [Authorization](https://tools.ietf.org/html/rfc7235#section-4.2) header in the + // inbound request from outside the system. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'username' + EnduserIDKey = attribute.Key("enduser.id") + // Actual/assumed role the client is making the request under extracted from token + // or application security context. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'admin' + EnduserRoleKey = attribute.Key("enduser.role") + // Scopes or granted authorities the client currently possesses extracted from + // token or application security context. The value would come from the scope + // associated with an [OAuth 2.0 Access + // Token](https://tools.ietf.org/html/rfc6749#section-3.3) or an attribute value + // in a [SAML 2.0 Assertion](http://docs.oasis- + // open.org/security/saml/Post2.0/sstc-saml-tech-overview-2.0.html). + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'read:message, write:files' + EnduserScopeKey = attribute.Key("enduser.scope") +) + +// These attributes may be used for any operation to store information about a thread that started a span. +const ( + // Current "managed" thread ID (as opposed to OS thread ID). + // + // Type: int + // RequirementLevel: Optional + // Stability: stable + // Examples: 42 + ThreadIDKey = attribute.Key("thread.id") + // Current thread name. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'main' + ThreadNameKey = attribute.Key("thread.name") +) + +// These attributes allow to report this unit of code and therefore to provide more context about the span. +const ( + // The method or function name, or equivalent (usually rightmost part of the code + // unit's name). + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'serveRequest' + CodeFunctionKey = attribute.Key("code.function") + // The "namespace" within which `code.function` is defined. Usually the qualified + // class or module name, such that `code.namespace` + some separator + + // `code.function` form a unique identifier for the code unit. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'com.example.MyHTTPService' + CodeNamespaceKey = attribute.Key("code.namespace") + // The source code file name that identifies the code unit as uniquely as possible + // (preferably an absolute file path). + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: '/usr/local/MyApplication/content_root/app/index.php' + CodeFilepathKey = attribute.Key("code.filepath") + // The line number in `code.filepath` best representing the operation. It SHOULD + // point within the code unit named in `code.function`. + // + // Type: int + // RequirementLevel: Optional + // Stability: stable + // Examples: 42 + CodeLineNumberKey = attribute.Key("code.lineno") +) + +// This document defines semantic conventions for HTTP client and server Spans. +const ( + // HTTP request method. + // + // Type: string + // RequirementLevel: Required + // Stability: stable + // Examples: 'GET', 'POST', 'HEAD' + HTTPMethodKey = attribute.Key("http.method") + // [HTTP response status code](https://tools.ietf.org/html/rfc7231#section-6). + // + // Type: int + // RequirementLevel: ConditionallyRequired (If and only if one was received/sent.) + // Stability: stable + // Examples: 200 + HTTPStatusCodeKey = attribute.Key("http.status_code") + // Kind of HTTP protocol used. + // + // Type: Enum + // RequirementLevel: Optional + // Stability: stable + // Note: If `net.transport` is not specified, it can be assumed to be `IP.TCP` + // except if `http.flavor` is `QUIC`, in which case `IP.UDP` is assumed. + HTTPFlavorKey = attribute.Key("http.flavor") + // Value of the [HTTP User-Agent](https://www.rfc- + // editor.org/rfc/rfc9110.html#field.user-agent) header sent by the client. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'CERN-LineMode/2.15 libwww/2.17b3' + HTTPUserAgentKey = attribute.Key("http.user_agent") + // The size of the request payload body in bytes. This is the number of bytes + // transferred excluding headers and is often, but not always, present as the + // [Content-Length](https://www.rfc-editor.org/rfc/rfc9110.html#field.content- + // length) header. For requests using transport encoding, this should be the + // compressed size. + // + // Type: int + // RequirementLevel: Optional + // Stability: stable + // Examples: 3495 + HTTPRequestContentLengthKey = attribute.Key("http.request_content_length") + // The size of the response payload body in bytes. This is the number of bytes + // transferred excluding headers and is often, but not always, present as the + // [Content-Length](https://www.rfc-editor.org/rfc/rfc9110.html#field.content- + // length) header. For requests using transport encoding, this should be the + // compressed size. + // + // Type: int + // RequirementLevel: Optional + // Stability: stable + // Examples: 3495 + HTTPResponseContentLengthKey = attribute.Key("http.response_content_length") +) + +var ( + // HTTP/1.0 + HTTPFlavorHTTP10 = HTTPFlavorKey.String("1.0") + // HTTP/1.1 + HTTPFlavorHTTP11 = HTTPFlavorKey.String("1.1") + // HTTP/2 + HTTPFlavorHTTP20 = HTTPFlavorKey.String("2.0") + // HTTP/3 + HTTPFlavorHTTP30 = HTTPFlavorKey.String("3.0") + // SPDY protocol + HTTPFlavorSPDY = HTTPFlavorKey.String("SPDY") + // QUIC protocol + HTTPFlavorQUIC = HTTPFlavorKey.String("QUIC") +) + +// Semantic Convention for HTTP Client +const ( + // Full HTTP request URL in the form `scheme://host[:port]/path?query[#fragment]`. + // Usually the fragment is not transmitted over HTTP, but if it is known, it + // should be included nevertheless. + // + // Type: string + // RequirementLevel: Required + // Stability: stable + // Examples: 'https://www.foo.bar/search?q=OpenTelemetry#SemConv' + // Note: `http.url` MUST NOT contain credentials passed via URL in form of + // `https://username:password@www.example.com/`. In such case the attribute's + // value should be `https://www.example.com/`. + HTTPURLKey = attribute.Key("http.url") + // The ordinal number of request re-sending attempt. + // + // Type: int + // RequirementLevel: Recommended (if and only if request was retried.) + // Stability: stable + // Examples: 3 + HTTPRetryCountKey = attribute.Key("http.retry_count") +) + +// Semantic Convention for HTTP Server +const ( + // The URI scheme identifying the used protocol. + // + // Type: string + // RequirementLevel: Required + // Stability: stable + // Examples: 'http', 'https' + HTTPSchemeKey = attribute.Key("http.scheme") + // The full request target as passed in a HTTP request line or equivalent. + // + // Type: string + // RequirementLevel: Required + // Stability: stable + // Examples: '/path/12314/?q=ddds' + HTTPTargetKey = attribute.Key("http.target") + // The matched route (path template in the format used by the respective server + // framework). See note below + // + // Type: string + // RequirementLevel: ConditionallyRequired (If and only if it's available) + // Stability: stable + // Examples: '/users/:userID?', '{controller}/{action}/{id?}' + // Note: 'http.route' MUST NOT be populated when this is not supported by the HTTP + // server framework as the route attribute should have low-cardinality and the URI + // path can NOT substitute it. + HTTPRouteKey = attribute.Key("http.route") + // The IP address of the original client behind all proxies, if known (e.g. from + // [X-Forwarded-For](https://developer.mozilla.org/en- + // US/docs/Web/HTTP/Headers/X-Forwarded-For)). + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: '83.164.160.102' + // Note: This is not necessarily the same as `net.sock.peer.addr`, which would + // identify the network-level peer, which may be a proxy. + + // This attribute should be set when a source of information different + // from the one used for `net.sock.peer.addr`, is available even if that other + // source just confirms the same value as `net.sock.peer.addr`. + // Rationale: For `net.sock.peer.addr`, one typically does not know if it + // comes from a proxy, reverse proxy, or the actual client. Setting + // `http.client_ip` when it's the same as `net.sock.peer.addr` means that + // one is at least somewhat confident that the address is not that of + // the closest proxy. + HTTPClientIPKey = attribute.Key("http.client_ip") +) + +// Attributes that exist for multiple DynamoDB request types. +const ( + // The keys in the `RequestItems` object field. + // + // Type: string[] + // RequirementLevel: Optional + // Stability: stable + // Examples: 'Users', 'Cats' + AWSDynamoDBTableNamesKey = attribute.Key("aws.dynamodb.table_names") + // The JSON-serialized value of each item in the `ConsumedCapacity` response + // field. + // + // Type: string[] + // RequirementLevel: Optional + // Stability: stable + // Examples: '{ "CapacityUnits": number, "GlobalSecondaryIndexes": { "string" : { + // "CapacityUnits": number, "ReadCapacityUnits": number, "WriteCapacityUnits": + // number } }, "LocalSecondaryIndexes": { "string" : { "CapacityUnits": number, + // "ReadCapacityUnits": number, "WriteCapacityUnits": number } }, + // "ReadCapacityUnits": number, "Table": { "CapacityUnits": number, + // "ReadCapacityUnits": number, "WriteCapacityUnits": number }, "TableName": + // "string", "WriteCapacityUnits": number }' + AWSDynamoDBConsumedCapacityKey = attribute.Key("aws.dynamodb.consumed_capacity") + // The JSON-serialized value of the `ItemCollectionMetrics` response field. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: '{ "string" : [ { "ItemCollectionKey": { "string" : { "B": blob, + // "BOOL": boolean, "BS": [ blob ], "L": [ "AttributeValue" ], "M": { "string" : + // "AttributeValue" }, "N": "string", "NS": [ "string" ], "NULL": boolean, "S": + // "string", "SS": [ "string" ] } }, "SizeEstimateRangeGB": [ number ] } ] }' + AWSDynamoDBItemCollectionMetricsKey = attribute.Key("aws.dynamodb.item_collection_metrics") + // The value of the `ProvisionedThroughput.ReadCapacityUnits` request parameter. + // + // Type: double + // RequirementLevel: Optional + // Stability: stable + // Examples: 1.0, 2.0 + AWSDynamoDBProvisionedReadCapacityKey = attribute.Key("aws.dynamodb.provisioned_read_capacity") + // The value of the `ProvisionedThroughput.WriteCapacityUnits` request parameter. + // + // Type: double + // RequirementLevel: Optional + // Stability: stable + // Examples: 1.0, 2.0 + AWSDynamoDBProvisionedWriteCapacityKey = attribute.Key("aws.dynamodb.provisioned_write_capacity") + // The value of the `ConsistentRead` request parameter. + // + // Type: boolean + // RequirementLevel: Optional + // Stability: stable + AWSDynamoDBConsistentReadKey = attribute.Key("aws.dynamodb.consistent_read") + // The value of the `ProjectionExpression` request parameter. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'Title', 'Title, Price, Color', 'Title, Description, RelatedItems, + // ProductReviews' + AWSDynamoDBProjectionKey = attribute.Key("aws.dynamodb.projection") + // The value of the `Limit` request parameter. + // + // Type: int + // RequirementLevel: Optional + // Stability: stable + // Examples: 10 + AWSDynamoDBLimitKey = attribute.Key("aws.dynamodb.limit") + // The value of the `AttributesToGet` request parameter. + // + // Type: string[] + // RequirementLevel: Optional + // Stability: stable + // Examples: 'lives', 'id' + AWSDynamoDBAttributesToGetKey = attribute.Key("aws.dynamodb.attributes_to_get") + // The value of the `IndexName` request parameter. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'name_to_group' + AWSDynamoDBIndexNameKey = attribute.Key("aws.dynamodb.index_name") + // The value of the `Select` request parameter. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'ALL_ATTRIBUTES', 'COUNT' + AWSDynamoDBSelectKey = attribute.Key("aws.dynamodb.select") +) + +// DynamoDB.CreateTable +const ( + // The JSON-serialized value of each item of the `GlobalSecondaryIndexes` request + // field + // + // Type: string[] + // RequirementLevel: Optional + // Stability: stable + // Examples: '{ "IndexName": "string", "KeySchema": [ { "AttributeName": "string", + // "KeyType": "string" } ], "Projection": { "NonKeyAttributes": [ "string" ], + // "ProjectionType": "string" }, "ProvisionedThroughput": { "ReadCapacityUnits": + // number, "WriteCapacityUnits": number } }' + AWSDynamoDBGlobalSecondaryIndexesKey = attribute.Key("aws.dynamodb.global_secondary_indexes") + // The JSON-serialized value of each item of the `LocalSecondaryIndexes` request + // field. + // + // Type: string[] + // RequirementLevel: Optional + // Stability: stable + // Examples: '{ "IndexARN": "string", "IndexName": "string", "IndexSizeBytes": + // number, "ItemCount": number, "KeySchema": [ { "AttributeName": "string", + // "KeyType": "string" } ], "Projection": { "NonKeyAttributes": [ "string" ], + // "ProjectionType": "string" } }' + AWSDynamoDBLocalSecondaryIndexesKey = attribute.Key("aws.dynamodb.local_secondary_indexes") +) + +// DynamoDB.ListTables +const ( + // The value of the `ExclusiveStartTableName` request parameter. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'Users', 'CatsTable' + AWSDynamoDBExclusiveStartTableKey = attribute.Key("aws.dynamodb.exclusive_start_table") + // The the number of items in the `TableNames` response parameter. + // + // Type: int + // RequirementLevel: Optional + // Stability: stable + // Examples: 20 + AWSDynamoDBTableCountKey = attribute.Key("aws.dynamodb.table_count") +) + +// DynamoDB.Query +const ( + // The value of the `ScanIndexForward` request parameter. + // + // Type: boolean + // RequirementLevel: Optional + // Stability: stable + AWSDynamoDBScanForwardKey = attribute.Key("aws.dynamodb.scan_forward") +) + +// DynamoDB.Scan +const ( + // The value of the `Segment` request parameter. + // + // Type: int + // RequirementLevel: Optional + // Stability: stable + // Examples: 10 + AWSDynamoDBSegmentKey = attribute.Key("aws.dynamodb.segment") + // The value of the `TotalSegments` request parameter. + // + // Type: int + // RequirementLevel: Optional + // Stability: stable + // Examples: 100 + AWSDynamoDBTotalSegmentsKey = attribute.Key("aws.dynamodb.total_segments") + // The value of the `Count` response parameter. + // + // Type: int + // RequirementLevel: Optional + // Stability: stable + // Examples: 10 + AWSDynamoDBCountKey = attribute.Key("aws.dynamodb.count") + // The value of the `ScannedCount` response parameter. + // + // Type: int + // RequirementLevel: Optional + // Stability: stable + // Examples: 50 + AWSDynamoDBScannedCountKey = attribute.Key("aws.dynamodb.scanned_count") +) + +// DynamoDB.UpdateTable +const ( + // The JSON-serialized value of each item in the `AttributeDefinitions` request + // field. + // + // Type: string[] + // RequirementLevel: Optional + // Stability: stable + // Examples: '{ "AttributeName": "string", "AttributeType": "string" }' + AWSDynamoDBAttributeDefinitionsKey = attribute.Key("aws.dynamodb.attribute_definitions") + // The JSON-serialized value of each item in the the `GlobalSecondaryIndexUpdates` + // request field. + // + // Type: string[] + // RequirementLevel: Optional + // Stability: stable + // Examples: '{ "Create": { "IndexName": "string", "KeySchema": [ { + // "AttributeName": "string", "KeyType": "string" } ], "Projection": { + // "NonKeyAttributes": [ "string" ], "ProjectionType": "string" }, + // "ProvisionedThroughput": { "ReadCapacityUnits": number, "WriteCapacityUnits": + // number } }' + AWSDynamoDBGlobalSecondaryIndexUpdatesKey = attribute.Key("aws.dynamodb.global_secondary_index_updates") +) + +// This document defines semantic conventions to apply when instrumenting the GraphQL implementation. They map GraphQL operations to attributes on a Span. +const ( + // The name of the operation being executed. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'findBookByID' + GraphqlOperationNameKey = attribute.Key("graphql.operation.name") + // The type of the operation being executed. + // + // Type: Enum + // RequirementLevel: Optional + // Stability: stable + // Examples: 'query', 'mutation', 'subscription' + GraphqlOperationTypeKey = attribute.Key("graphql.operation.type") + // The GraphQL document being executed. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'query findBookByID { bookByID(id: ?) { name } }' + // Note: The value may be sanitized to exclude sensitive information. + GraphqlDocumentKey = attribute.Key("graphql.document") +) + +var ( + // GraphQL query + GraphqlOperationTypeQuery = GraphqlOperationTypeKey.String("query") + // GraphQL mutation + GraphqlOperationTypeMutation = GraphqlOperationTypeKey.String("mutation") + // GraphQL subscription + GraphqlOperationTypeSubscription = GraphqlOperationTypeKey.String("subscription") +) + +// This document defines the attributes used in messaging systems. +const ( + // A string identifying the messaging system. + // + // Type: string + // RequirementLevel: Required + // Stability: stable + // Examples: 'kafka', 'rabbitmq', 'rocketmq', 'activemq', 'AmazonSQS' + MessagingSystemKey = attribute.Key("messaging.system") + // The message destination name. This might be equal to the span name but is + // required nevertheless. + // + // Type: string + // RequirementLevel: Required + // Stability: stable + // Examples: 'MyQueue', 'MyTopic' + MessagingDestinationKey = attribute.Key("messaging.destination") + // The kind of message destination + // + // Type: Enum + // RequirementLevel: ConditionallyRequired (If the message destination is either a + // `queue` or `topic`.) + // Stability: stable + MessagingDestinationKindKey = attribute.Key("messaging.destination_kind") + // A boolean that is true if the message destination is temporary. + // + // Type: boolean + // RequirementLevel: ConditionallyRequired (If value is `true`. When missing, the + // value is assumed to be `false`.) + // Stability: stable + MessagingTempDestinationKey = attribute.Key("messaging.temp_destination") + // The name of the transport protocol. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'AMQP', 'MQTT' + MessagingProtocolKey = attribute.Key("messaging.protocol") + // The version of the transport protocol. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: '0.9.1' + MessagingProtocolVersionKey = attribute.Key("messaging.protocol_version") + // Connection string. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'tibjmsnaming://localhost:7222', + // 'https://queue.amazonaws.com/80398EXAMPLE/MyQueue' + MessagingURLKey = attribute.Key("messaging.url") + // A value used by the messaging system as an identifier for the message, + // represented as a string. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: '452a7c7c7c7048c2f887f61572b18fc2' + MessagingMessageIDKey = attribute.Key("messaging.message_id") + // The [conversation ID](#conversations) identifying the conversation to which the + // message belongs, represented as a string. Sometimes called "Correlation ID". + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'MyConversationID' + MessagingConversationIDKey = attribute.Key("messaging.conversation_id") + // The (uncompressed) size of the message payload in bytes. Also use this + // attribute if it is unknown whether the compressed or uncompressed payload size + // is reported. + // + // Type: int + // RequirementLevel: Optional + // Stability: stable + // Examples: 2738 + MessagingMessagePayloadSizeBytesKey = attribute.Key("messaging.message_payload_size_bytes") + // The compressed size of the message payload in bytes. + // + // Type: int + // RequirementLevel: Optional + // Stability: stable + // Examples: 2048 + MessagingMessagePayloadCompressedSizeBytesKey = attribute.Key("messaging.message_payload_compressed_size_bytes") +) + +var ( + // A message sent to a queue + MessagingDestinationKindQueue = MessagingDestinationKindKey.String("queue") + // A message sent to a topic + MessagingDestinationKindTopic = MessagingDestinationKindKey.String("topic") +) + +// Semantic convention for a consumer of messages received from a messaging system +const ( + // A string identifying the kind of message consumption as defined in the + // [Operation names](#operation-names) section above. If the operation is "send", + // this attribute MUST NOT be set, since the operation can be inferred from the + // span kind in that case. + // + // Type: Enum + // RequirementLevel: Optional + // Stability: stable + MessagingOperationKey = attribute.Key("messaging.operation") + // The identifier for the consumer receiving a message. For Kafka, set it to + // `{messaging.kafka.consumer_group} - {messaging.kafka.client_id}`, if both are + // present, or only `messaging.kafka.consumer_group`. For brokers, such as + // RabbitMQ and Artemis, set it to the `client_id` of the client consuming the + // message. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'mygroup - client-6' + MessagingConsumerIDKey = attribute.Key("messaging.consumer_id") +) + +var ( + // receive + MessagingOperationReceive = MessagingOperationKey.String("receive") + // process + MessagingOperationProcess = MessagingOperationKey.String("process") +) + +// Attributes for RabbitMQ +const ( + // RabbitMQ message routing key. + // + // Type: string + // RequirementLevel: ConditionallyRequired (If not empty.) + // Stability: stable + // Examples: 'myKey' + MessagingRabbitmqRoutingKeyKey = attribute.Key("messaging.rabbitmq.routing_key") +) + +// Attributes for Apache Kafka +const ( + // Message keys in Kafka are used for grouping alike messages to ensure they're + // processed on the same partition. They differ from `messaging.message_id` in + // that they're not unique. If the key is `null`, the attribute MUST NOT be set. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'myKey' + // Note: If the key type is not string, it's string representation has to be + // supplied for the attribute. If the key has no unambiguous, canonical string + // form, don't include its value. + MessagingKafkaMessageKeyKey = attribute.Key("messaging.kafka.message_key") + // Name of the Kafka Consumer Group that is handling the message. Only applies to + // consumers, not producers. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'my-group' + MessagingKafkaConsumerGroupKey = attribute.Key("messaging.kafka.consumer_group") + // Client ID for the Consumer or Producer that is handling the message. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'client-5' + MessagingKafkaClientIDKey = attribute.Key("messaging.kafka.client_id") + // Partition the message is sent to. + // + // Type: int + // RequirementLevel: Optional + // Stability: stable + // Examples: 2 + MessagingKafkaPartitionKey = attribute.Key("messaging.kafka.partition") + // A boolean that is true if the message is a tombstone. + // + // Type: boolean + // RequirementLevel: ConditionallyRequired (If value is `true`. When missing, the + // value is assumed to be `false`.) + // Stability: stable + MessagingKafkaTombstoneKey = attribute.Key("messaging.kafka.tombstone") +) + +// Attributes for Apache RocketMQ +const ( + // Namespace of RocketMQ resources, resources in different namespaces are + // individual. + // + // Type: string + // RequirementLevel: Required + // Stability: stable + // Examples: 'myNamespace' + MessagingRocketmqNamespaceKey = attribute.Key("messaging.rocketmq.namespace") + // Name of the RocketMQ producer/consumer group that is handling the message. The + // client type is identified by the SpanKind. + // + // Type: string + // RequirementLevel: Required + // Stability: stable + // Examples: 'myConsumerGroup' + MessagingRocketmqClientGroupKey = attribute.Key("messaging.rocketmq.client_group") + // The unique identifier for each client. + // + // Type: string + // RequirementLevel: Required + // Stability: stable + // Examples: 'myhost@8742@s8083jm' + MessagingRocketmqClientIDKey = attribute.Key("messaging.rocketmq.client_id") + // Type of message. + // + // Type: Enum + // RequirementLevel: Optional + // Stability: stable + MessagingRocketmqMessageTypeKey = attribute.Key("messaging.rocketmq.message_type") + // The secondary classifier of message besides topic. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'tagA' + MessagingRocketmqMessageTagKey = attribute.Key("messaging.rocketmq.message_tag") + // Key(s) of message, another way to mark message besides message id. + // + // Type: string[] + // RequirementLevel: Optional + // Stability: stable + // Examples: 'keyA', 'keyB' + MessagingRocketmqMessageKeysKey = attribute.Key("messaging.rocketmq.message_keys") + // Model of message consumption. This only applies to consumer spans. + // + // Type: Enum + // RequirementLevel: Optional + // Stability: stable + MessagingRocketmqConsumptionModelKey = attribute.Key("messaging.rocketmq.consumption_model") +) + +var ( + // Normal message + MessagingRocketmqMessageTypeNormal = MessagingRocketmqMessageTypeKey.String("normal") + // FIFO message + MessagingRocketmqMessageTypeFifo = MessagingRocketmqMessageTypeKey.String("fifo") + // Delay message + MessagingRocketmqMessageTypeDelay = MessagingRocketmqMessageTypeKey.String("delay") + // Transaction message + MessagingRocketmqMessageTypeTransaction = MessagingRocketmqMessageTypeKey.String("transaction") +) + +var ( + // Clustering consumption model + MessagingRocketmqConsumptionModelClustering = MessagingRocketmqConsumptionModelKey.String("clustering") + // Broadcasting consumption model + MessagingRocketmqConsumptionModelBroadcasting = MessagingRocketmqConsumptionModelKey.String("broadcasting") +) + +// This document defines semantic conventions for remote procedure calls. +const ( + // A string identifying the remoting system. See below for a list of well-known + // identifiers. + // + // Type: Enum + // RequirementLevel: Required + // Stability: stable + RPCSystemKey = attribute.Key("rpc.system") + // The full (logical) name of the service being called, including its package + // name, if applicable. + // + // Type: string + // RequirementLevel: Recommended + // Stability: stable + // Examples: 'myservice.EchoService' + // Note: This is the logical name of the service from the RPC interface + // perspective, which can be different from the name of any implementing class. + // The `code.namespace` attribute may be used to store the latter (despite the + // attribute name, it may include a class name; e.g., class with method actually + // executing the call on the server side, RPC client stub class on the client + // side). + RPCServiceKey = attribute.Key("rpc.service") + // The name of the (logical) method being called, must be equal to the $method + // part in the span name. + // + // Type: string + // RequirementLevel: Recommended + // Stability: stable + // Examples: 'exampleMethod' + // Note: This is the logical name of the method from the RPC interface + // perspective, which can be different from the name of any implementing + // method/function. The `code.function` attribute may be used to store the latter + // (e.g., method actually executing the call on the server side, RPC client stub + // method on the client side). + RPCMethodKey = attribute.Key("rpc.method") +) + +var ( + // gRPC + RPCSystemGRPC = RPCSystemKey.String("grpc") + // Java RMI + RPCSystemJavaRmi = RPCSystemKey.String("java_rmi") + // .NET WCF + RPCSystemDotnetWcf = RPCSystemKey.String("dotnet_wcf") + // Apache Dubbo + RPCSystemApacheDubbo = RPCSystemKey.String("apache_dubbo") +) + +// Tech-specific attributes for gRPC. +const ( + // The [numeric status + // code](https://github.com/grpc/grpc/blob/v1.33.2/doc/statuscodes.md) of the gRPC + // request. + // + // Type: Enum + // RequirementLevel: Required + // Stability: stable + RPCGRPCStatusCodeKey = attribute.Key("rpc.grpc.status_code") +) + +var ( + // OK + RPCGRPCStatusCodeOk = RPCGRPCStatusCodeKey.Int(0) + // CANCELLED + RPCGRPCStatusCodeCancelled = RPCGRPCStatusCodeKey.Int(1) + // UNKNOWN + RPCGRPCStatusCodeUnknown = RPCGRPCStatusCodeKey.Int(2) + // INVALID_ARGUMENT + RPCGRPCStatusCodeInvalidArgument = RPCGRPCStatusCodeKey.Int(3) + // DEADLINE_EXCEEDED + RPCGRPCStatusCodeDeadlineExceeded = RPCGRPCStatusCodeKey.Int(4) + // NOT_FOUND + RPCGRPCStatusCodeNotFound = RPCGRPCStatusCodeKey.Int(5) + // ALREADY_EXISTS + RPCGRPCStatusCodeAlreadyExists = RPCGRPCStatusCodeKey.Int(6) + // PERMISSION_DENIED + RPCGRPCStatusCodePermissionDenied = RPCGRPCStatusCodeKey.Int(7) + // RESOURCE_EXHAUSTED + RPCGRPCStatusCodeResourceExhausted = RPCGRPCStatusCodeKey.Int(8) + // FAILED_PRECONDITION + RPCGRPCStatusCodeFailedPrecondition = RPCGRPCStatusCodeKey.Int(9) + // ABORTED + RPCGRPCStatusCodeAborted = RPCGRPCStatusCodeKey.Int(10) + // OUT_OF_RANGE + RPCGRPCStatusCodeOutOfRange = RPCGRPCStatusCodeKey.Int(11) + // UNIMPLEMENTED + RPCGRPCStatusCodeUnimplemented = RPCGRPCStatusCodeKey.Int(12) + // INTERNAL + RPCGRPCStatusCodeInternal = RPCGRPCStatusCodeKey.Int(13) + // UNAVAILABLE + RPCGRPCStatusCodeUnavailable = RPCGRPCStatusCodeKey.Int(14) + // DATA_LOSS + RPCGRPCStatusCodeDataLoss = RPCGRPCStatusCodeKey.Int(15) + // UNAUTHENTICATED + RPCGRPCStatusCodeUnauthenticated = RPCGRPCStatusCodeKey.Int(16) +) + +// Tech-specific attributes for [JSON RPC](https://www.jsonrpc.org/). +const ( + // Protocol version as in `jsonrpc` property of request/response. Since JSON-RPC + // 1.0 does not specify this, the value can be omitted. + // + // Type: string + // RequirementLevel: ConditionallyRequired (If other than the default version + // (`1.0`)) + // Stability: stable + // Examples: '2.0', '1.0' + RPCJsonrpcVersionKey = attribute.Key("rpc.jsonrpc.version") + // `id` property of request or response. Since protocol allows id to be int, + // string, `null` or missing (for notifications), value is expected to be cast to + // string for simplicity. Use empty string in case of `null` value. Omit entirely + // if this is a notification. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: '10', 'request-7', '' + RPCJsonrpcRequestIDKey = attribute.Key("rpc.jsonrpc.request_id") + // `error.code` property of response if it is an error response. + // + // Type: int + // RequirementLevel: ConditionallyRequired (If response is not successful.) + // Stability: stable + // Examples: -32700, 100 + RPCJsonrpcErrorCodeKey = attribute.Key("rpc.jsonrpc.error_code") + // `error.message` property of response if it is an error response. + // + // Type: string + // RequirementLevel: Optional + // Stability: stable + // Examples: 'Parse error', 'User already exists' + RPCJsonrpcErrorMessageKey = attribute.Key("rpc.jsonrpc.error_message") +) + +// RPC received/sent message. +const ( + // Whether this is a received or sent message. + // + // Type: Enum + // RequirementLevel: Optional + // Stability: stable + MessageTypeKey = attribute.Key("message.type") + // MUST be calculated as two different counters starting from `1` one for sent + // messages and one for received message. + // + // Type: int + // RequirementLevel: Optional + // Stability: stable + // Note: This way we guarantee that the values will be consistent between + // different implementations. + MessageIDKey = attribute.Key("message.id") + // Compressed size of the message in bytes. + // + // Type: int + // RequirementLevel: Optional + // Stability: stable + MessageCompressedSizeKey = attribute.Key("message.compressed_size") + // Uncompressed size of the message in bytes. + // + // Type: int + // RequirementLevel: Optional + // Stability: stable + MessageUncompressedSizeKey = attribute.Key("message.uncompressed_size") +) + +var ( + // sent + MessageTypeSent = MessageTypeKey.String("SENT") + // received + MessageTypeReceived = MessageTypeKey.String("RECEIVED") +) From 23856fcf2692eb331f6c6cfb1fc11a915819982a Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Sat, 26 Nov 2022 08:51:41 -0800 Subject: [PATCH 02/15] Add NetConv unit tests --- semconv/internal/v2/net_test.go | 91 +++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 semconv/internal/v2/net_test.go diff --git a/semconv/internal/v2/net_test.go b/semconv/internal/v2/net_test.go new file mode 100644 index 00000000000..ed8ba607751 --- /dev/null +++ b/semconv/internal/v2/net_test.go @@ -0,0 +1,91 @@ +// Copyright The OpenTelemetry Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package internal + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "go.opentelemetry.io/otel/attribute" +) + +const ( + addr = "127.0.0.1" + port = 1834 +) + +var nc = &NetConv{ + NetHostNameKey: attribute.Key("net.host.name"), + NetHostPortKey: attribute.Key("net.host.port"), + NetPeerNameKey: attribute.Key("net.peer.name"), + NetPeerPortKey: attribute.Key("net.peer.port"), + NetSockPeerAddrKey: attribute.Key("net.sock.peer.addr"), + NetSockPeerPortKey: attribute.Key("net.sock.peer.port"), + NetTransportOther: attribute.String("net.transport", "other"), + NetTransportTCP: attribute.String("net.transport", "ip_tcp"), + NetTransportUDP: attribute.String("net.transport", "ip_udp"), + NetTransportInProc: attribute.String("net.transport", "inproc"), +} + +func TestNetTransport(t *testing.T) { + transports := map[string]attribute.KeyValue{ + "tcp": attribute.String("net.transport", "ip_tcp"), + "tcp4": attribute.String("net.transport", "ip_tcp"), + "tcp6": attribute.String("net.transport", "ip_tcp"), + "udp": attribute.String("net.transport", "ip_udp"), + "udp4": attribute.String("net.transport", "ip_udp"), + "udp6": attribute.String("net.transport", "ip_udp"), + "unix": attribute.String("net.transport", "inproc"), + "unixgram": attribute.String("net.transport", "inproc"), + "unixpacket": attribute.String("net.transport", "inproc"), + "ip:1": attribute.String("net.transport", "other"), + "ip:icmp": attribute.String("net.transport", "other"), + "ip4:proto": attribute.String("net.transport", "other"), + "ip6:proto": attribute.String("net.transport", "other"), + } + + for net, want := range transports { + assert.Equal(t, want, nc.Transport(net)) + } +} + +func TestNetHostName(t *testing.T) { + expected := attribute.Key("net.host.name").String(addr) + assert.Equal(t, expected, nc.HostName(addr)) +} + +func TestNetHostPort(t *testing.T) { + expected := attribute.Key("net.host.port").Int(port) + assert.Equal(t, expected, nc.HostPort(port)) +} + +func TestNetPeerName(t *testing.T) { + expected := attribute.Key("net.peer.name").String(addr) + assert.Equal(t, expected, nc.PeerName(addr)) +} + +func TestNetPeerPort(t *testing.T) { + expected := attribute.Key("net.peer.port").Int(port) + assert.Equal(t, expected, nc.PeerPort(port)) +} + +func TestNetSockPeerName(t *testing.T) { + expected := attribute.Key("net.sock.peer.addr").String(addr) + assert.Equal(t, expected, nc.SockPeerAddr(addr)) +} + +func TestNetSockPeerPort(t *testing.T) { + expected := attribute.Key("net.sock.peer.port").Int(port) + assert.Equal(t, expected, nc.SockPeerPort(port)) +} From 214d975ab4e59ea88afe39c01684c51ed3b4840d Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Sat, 26 Nov 2022 08:52:17 -0800 Subject: [PATCH 03/15] Add ServerRequest unit tests --- semconv/internal/v2/http.go | 9 +- semconv/internal/v2/http_test.go | 1116 +++++------------------------- 2 files changed, 191 insertions(+), 934 deletions(-) diff --git a/semconv/internal/v2/http.go b/semconv/internal/v2/http.go index efc4c0ec08b..470d14e7071 100644 --- a/semconv/internal/v2/http.go +++ b/semconv/internal/v2/http.go @@ -154,10 +154,17 @@ func (c *HTTPConv) ServerRequest(req *http.Request) []attribute.KeyValue { attrs = append(attrs, c.method(req.Method)) attrs = append(attrs, c.scheme(req.TLS != nil)) - attrs = append(attrs, c.HTTPTargetKey.String(req.URL.RequestURI())) attrs = append(attrs, c.proto(req.Proto)) attrs = append(attrs, c.NetConv.HostName(host)) + if req.URL != nil { + attrs = append(attrs, c.HTTPTargetKey.String(req.URL.RequestURI())) + } else { + // This should never occur if the request was generated by the net/http + // package. Fail gracefully, if it does though. + attrs = append(attrs, c.HTTPTargetKey.String(req.RequestURI)) + } + if hostPort > 0 { attrs = append(attrs, c.NetConv.HostPort(hostPort)) } diff --git a/semconv/internal/v2/http_test.go b/semconv/internal/v2/http_test.go index d478b32c094..2429978d6c5 100644 --- a/semconv/internal/v2/http_test.go +++ b/semconv/internal/v2/http_test.go @@ -14,896 +14,202 @@ package internal import ( - "crypto/tls" "net/http" + "net/http/httptest" "net/url" - "strings" + "strconv" "testing" - "go.opentelemetry.io/otel/trace" - - "github.com/google/go-cmp/cmp" - "github.com/stretchr/testify/assert" - - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/codes" -) - -type tlsOption int - -const ( - noTLS tlsOption = iota - withTLS -) - -var sc = &HTTPConv{ - EnduserIDKey: attribute.Key("enduser.id"), - HTTPClientIPKey: attribute.Key("http.client_ip"), - HTTPFlavorKey: attribute.Key("http.flavor"), - HTTPHostKey: attribute.Key("http.host"), - HTTPMethodKey: attribute.Key("http.method"), - HTTPRequestContentLengthKey: attribute.Key("http.request_content_length"), - HTTPRouteKey: attribute.Key("http.route"), - HTTPSchemeHTTP: attribute.String("http.scheme", "http"), - HTTPSchemeHTTPS: attribute.String("http.scheme", "https"), - HTTPServerNameKey: attribute.Key("http.server_name"), - HTTPStatusCodeKey: attribute.Key("http.status_code"), - HTTPTargetKey: attribute.Key("http.target"), - HTTPURLKey: attribute.Key("http.url"), - HTTPUserAgentKey: attribute.Key("http.user_agent"), - NetHostIPKey: attribute.Key("net.host.ip"), - NetHostNameKey: attribute.Key("net.host.name"), - NetHostPortKey: attribute.Key("net.host.port"), - NetPeerIPKey: attribute.Key("net.peer.ip"), - NetPeerNameKey: attribute.Key("net.peer.name"), - NetPeerPortKey: attribute.Key("net.peer.port"), - NetTransportIP: attribute.String("net.transport", "ip"), - NetTransportOther: attribute.String("net.transport", "other"), - NetTransportTCP: attribute.String("net.transport", "ip_tcp"), - NetTransportUDP: attribute.String("net.transport", "ip_udp"), - NetTransportUnix: attribute.String("net.transport", "unix"), -} - -func TestNetAttributesFromHTTPRequest(t *testing.T) { - type testcase struct { - name string - - network string - - method string - requestURI string - proto string - remoteAddr string - host string - url *url.URL - header http.Header - - expected []attribute.KeyValue - } - testcases := []testcase{ - { - name: "stripped, tcp", - network: "tcp", - method: "GET", - requestURI: "/user/123", - proto: "HTTP/1.0", - remoteAddr: "", - host: "", - url: &url.URL{ - Path: "/user/123", - }, - header: nil, - expected: []attribute.KeyValue{ - attribute.String("net.transport", "ip_tcp"), - }, - }, - { - name: "stripped, udp", - network: "udp", - method: "GET", - requestURI: "/user/123", - proto: "HTTP/1.0", - remoteAddr: "", - host: "", - url: &url.URL{ - Path: "/user/123", - }, - header: nil, - expected: []attribute.KeyValue{ - attribute.String("net.transport", "ip_udp"), - }, - }, - { - name: "stripped, ip", - network: "ip", - method: "GET", - requestURI: "/user/123", - proto: "HTTP/1.0", - remoteAddr: "", - host: "", - url: &url.URL{ - Path: "/user/123", - }, - header: nil, - expected: []attribute.KeyValue{ - attribute.String("net.transport", "ip"), - }, - }, - { - name: "stripped, unix", - network: "unix", - method: "GET", - requestURI: "/user/123", - proto: "HTTP/1.0", - remoteAddr: "", - host: "", - url: &url.URL{ - Path: "/user/123", - }, - header: nil, - expected: []attribute.KeyValue{ - attribute.String("net.transport", "unix"), - }, - }, - { - name: "stripped, other", - network: "nih", - method: "GET", - requestURI: "/user/123", - proto: "HTTP/1.0", - remoteAddr: "", - host: "", - url: &url.URL{ - Path: "/user/123", - }, - header: nil, - expected: []attribute.KeyValue{ - attribute.String("net.transport", "other"), - }, - }, - { - name: "with remote ipv4 and port", - network: "tcp", - method: "GET", - requestURI: "/user/123", - proto: "HTTP/1.0", - remoteAddr: "1.2.3.4:56", - host: "", - url: &url.URL{ - Path: "/user/123", - }, - header: nil, - expected: []attribute.KeyValue{ - attribute.String("net.transport", "ip_tcp"), - attribute.String("net.peer.ip", "1.2.3.4"), - attribute.Int("net.peer.port", 56), - }, - }, - { - name: "with remote ipv6 and port", - network: "tcp", - method: "GET", - requestURI: "/user/123", - proto: "HTTP/1.0", - remoteAddr: "[fe80::0202:b3ff:fe1e:8329]:56", - host: "", - url: &url.URL{ - Path: "/user/123", - }, - header: nil, - expected: []attribute.KeyValue{ - attribute.String("net.transport", "ip_tcp"), - attribute.String("net.peer.ip", "fe80::202:b3ff:fe1e:8329"), - attribute.Int("net.peer.port", 56), - }, - }, - { - name: "with remote ipv4-in-v6 and port", - network: "tcp", - method: "GET", - requestURI: "/user/123", - proto: "HTTP/1.0", - remoteAddr: "[::ffff:192.168.0.1]:56", - host: "", - url: &url.URL{ - Path: "/user/123", - }, - header: nil, - expected: []attribute.KeyValue{ - attribute.String("net.transport", "ip_tcp"), - attribute.String("net.peer.ip", "192.168.0.1"), - attribute.Int("net.peer.port", 56), - }, - }, - { - name: "with remote name and port", - network: "tcp", - method: "GET", - requestURI: "/user/123", - proto: "HTTP/1.0", - remoteAddr: "example.com:56", - host: "", - url: &url.URL{ - Path: "/user/123", - }, - header: nil, - expected: []attribute.KeyValue{ - attribute.String("net.transport", "ip_tcp"), - attribute.String("net.peer.name", "example.com"), - attribute.Int("net.peer.port", 56), - }, - }, - { - name: "with remote ipv4 only", - network: "tcp", - method: "GET", - requestURI: "/user/123", - proto: "HTTP/1.0", - remoteAddr: "1.2.3.4", - host: "", - url: &url.URL{ - Path: "/user/123", - }, - header: nil, - expected: []attribute.KeyValue{ - attribute.String("net.transport", "ip_tcp"), - attribute.String("net.peer.ip", "1.2.3.4"), - }, - }, - { - name: "with remote ipv6 only", - network: "tcp", - method: "GET", - requestURI: "/user/123", - proto: "HTTP/1.0", - remoteAddr: "fe80::0202:b3ff:fe1e:8329", - host: "", - url: &url.URL{ - Path: "/user/123", - }, - header: nil, - expected: []attribute.KeyValue{ - attribute.String("net.transport", "ip_tcp"), - attribute.String("net.peer.ip", "fe80::202:b3ff:fe1e:8329"), - }, - }, - { - name: "with remote ipv4_in_v6 only", - network: "tcp", - method: "GET", - requestURI: "/user/123", - proto: "HTTP/1.0", - remoteAddr: "::ffff:192.168.0.1", // section 2.5.5.2 of RFC4291 - host: "", - url: &url.URL{ - Path: "/user/123", - }, - header: nil, - expected: []attribute.KeyValue{ - attribute.String("net.transport", "ip_tcp"), - attribute.String("net.peer.ip", "192.168.0.1"), - }, - }, - { - name: "with remote name only", - network: "tcp", - method: "GET", - requestURI: "/user/123", - proto: "HTTP/1.0", - remoteAddr: "example.com", - host: "", - url: &url.URL{ - Path: "/user/123", - }, - header: nil, - expected: []attribute.KeyValue{ - attribute.String("net.transport", "ip_tcp"), - attribute.String("net.peer.name", "example.com"), - }, - }, - { - name: "with remote port only", - network: "tcp", - method: "GET", - requestURI: "/user/123", - proto: "HTTP/1.0", - remoteAddr: ":56", - host: "", - url: &url.URL{ - Path: "/user/123", - }, - header: nil, - expected: []attribute.KeyValue{ - attribute.String("net.transport", "ip_tcp"), - attribute.Int("net.peer.port", 56), - }, - }, - { - name: "with host name only", - network: "tcp", - method: "GET", - requestURI: "/user/123", - proto: "HTTP/1.0", - remoteAddr: "1.2.3.4:56", - host: "example.com", - url: &url.URL{ - Path: "/user/123", - }, - header: nil, - expected: []attribute.KeyValue{ - attribute.String("net.transport", "ip_tcp"), - attribute.String("net.peer.ip", "1.2.3.4"), - attribute.Int("net.peer.port", 56), - attribute.String("net.host.name", "example.com"), - }, - }, - { - name: "with host ipv4 only", - network: "tcp", - method: "GET", - requestURI: "/user/123", - proto: "HTTP/1.0", - remoteAddr: "1.2.3.4:56", - host: "4.3.2.1", - url: &url.URL{ - Path: "/user/123", - }, - header: nil, - expected: []attribute.KeyValue{ - attribute.String("net.transport", "ip_tcp"), - attribute.String("net.peer.ip", "1.2.3.4"), - attribute.Int("net.peer.port", 56), - attribute.String("net.host.ip", "4.3.2.1"), - }, - }, - { - name: "with host ipv6 only", - network: "tcp", - method: "GET", - requestURI: "/user/123", - proto: "HTTP/1.0", - remoteAddr: "1.2.3.4:56", - host: "fe80::0202:b3ff:fe1e:8329", - url: &url.URL{ - Path: "/user/123", - }, - header: nil, - expected: []attribute.KeyValue{ - attribute.String("net.transport", "ip_tcp"), - attribute.String("net.peer.ip", "1.2.3.4"), - attribute.Int("net.peer.port", 56), - attribute.String("net.host.ip", "fe80::202:b3ff:fe1e:8329"), - }, - }, - { - name: "with host name and port", - network: "tcp", - method: "GET", - requestURI: "/user/123", - proto: "HTTP/1.0", - remoteAddr: "1.2.3.4:56", - host: "example.com:78", - url: &url.URL{ - Path: "/user/123", - }, - header: nil, - expected: []attribute.KeyValue{ - attribute.String("net.transport", "ip_tcp"), - attribute.String("net.peer.ip", "1.2.3.4"), - attribute.Int("net.peer.port", 56), - attribute.String("net.host.name", "example.com"), - attribute.Int("net.host.port", 78), - }, - }, - { - name: "with host ipv4 and port", - network: "tcp", - method: "GET", - requestURI: "/user/123", - proto: "HTTP/1.0", - remoteAddr: "1.2.3.4:56", - host: "4.3.2.1:78", - url: &url.URL{ - Path: "/user/123", - }, - header: nil, - expected: []attribute.KeyValue{ - attribute.String("net.transport", "ip_tcp"), - attribute.String("net.peer.ip", "1.2.3.4"), - attribute.Int("net.peer.port", 56), - attribute.String("net.host.ip", "4.3.2.1"), - attribute.Int("net.host.port", 78), - }, - }, - { - name: "with host ipv6 and port", - network: "tcp", - method: "GET", - requestURI: "/user/123", - proto: "HTTP/1.0", - remoteAddr: "1.2.3.4:56", - host: "[fe80::202:b3ff:fe1e:8329]:78", - url: &url.URL{ - Path: "/user/123", - }, - header: nil, - expected: []attribute.KeyValue{ - attribute.String("net.transport", "ip_tcp"), - attribute.String("net.peer.ip", "1.2.3.4"), - attribute.Int("net.peer.port", 56), - attribute.String("net.host.ip", "fe80::202:b3ff:fe1e:8329"), - attribute.Int("net.host.port", 78), - }, - }, - { - name: "with host name and bogus port", - network: "tcp", - method: "GET", - requestURI: "/user/123", - proto: "HTTP/1.0", - remoteAddr: "1.2.3.4:56", - host: "example.com:qwerty", - url: &url.URL{ - Path: "/user/123", - }, - header: nil, - expected: []attribute.KeyValue{ - attribute.String("net.transport", "ip_tcp"), - attribute.String("net.peer.ip", "1.2.3.4"), - attribute.Int("net.peer.port", 56), - attribute.String("net.host.name", "example.com"), - }, - }, - { - name: "with host ipv4 and bogus port", - network: "tcp", - method: "GET", - requestURI: "/user/123", - proto: "HTTP/1.0", - remoteAddr: "1.2.3.4:56", - host: "4.3.2.1:qwerty", - url: &url.URL{ - Path: "/user/123", - }, - header: nil, - expected: []attribute.KeyValue{ - attribute.String("net.transport", "ip_tcp"), - attribute.String("net.peer.ip", "1.2.3.4"), - attribute.Int("net.peer.port", 56), - attribute.String("net.host.ip", "4.3.2.1"), - }, - }, - { - name: "with host ipv6 and bogus port", - network: "tcp", - method: "GET", - requestURI: "/user/123", - proto: "HTTP/1.0", - remoteAddr: "1.2.3.4:56", - host: "[fe80::202:b3ff:fe1e:8329]:qwerty", - url: &url.URL{ - Path: "/user/123", - }, - header: nil, - expected: []attribute.KeyValue{ - attribute.String("net.transport", "ip_tcp"), - attribute.String("net.peer.ip", "1.2.3.4"), - attribute.Int("net.peer.port", 56), - attribute.String("net.host.ip", "fe80::202:b3ff:fe1e:8329"), - }, - }, - { - name: "with empty host and port", - network: "tcp", - method: "GET", - requestURI: "/user/123", - proto: "HTTP/1.0", - remoteAddr: "1.2.3.4:56", - host: ":80", - url: &url.URL{ - Path: "/user/123", - }, - header: nil, - expected: []attribute.KeyValue{ - attribute.String("net.transport", "ip_tcp"), - attribute.String("net.peer.ip", "1.2.3.4"), - attribute.Int("net.peer.port", 56), - attribute.Int("net.host.port", 80), - }, - }, - { - name: "with host ip and port in headers", - network: "tcp", - method: "GET", - requestURI: "/user/123", - proto: "HTTP/1.0", - remoteAddr: "1.2.3.4:56", - host: "", - url: &url.URL{ - Path: "/user/123", - }, - header: http.Header{ - "Host": []string{"4.3.2.1:78"}, - }, - expected: []attribute.KeyValue{ - attribute.String("net.transport", "ip_tcp"), - attribute.String("net.peer.ip", "1.2.3.4"), - attribute.Int("net.peer.port", 56), - attribute.String("net.host.ip", "4.3.2.1"), - attribute.Int("net.host.port", 78), - }, - }, - { - name: "with host ipv4 and port in url", - network: "tcp", - method: "GET", - requestURI: "http://4.3.2.1:78/user/123", - proto: "HTTP/1.0", - remoteAddr: "1.2.3.4:56", - host: "", - url: &url.URL{ - Host: "4.3.2.1:78", - Path: "/user/123", - }, - header: nil, - expected: []attribute.KeyValue{ - attribute.String("net.transport", "ip_tcp"), - attribute.String("net.peer.ip", "1.2.3.4"), - attribute.Int("net.peer.port", 56), - attribute.String("net.host.ip", "4.3.2.1"), - attribute.Int("net.host.port", 78), - }, - }, - { - name: "with host ipv6 and port in url", - network: "tcp", - method: "GET", - requestURI: "http://4.3.2.1:78/user/123", - proto: "HTTP/1.0", - remoteAddr: "1.2.3.4:56", - host: "", - url: &url.URL{ - Host: "[fe80::202:b3ff:fe1e:8329]:78", - Path: "/user/123", - }, - header: nil, - expected: []attribute.KeyValue{ - attribute.String("net.transport", "ip_tcp"), - attribute.String("net.peer.ip", "1.2.3.4"), - attribute.Int("net.peer.port", 56), - attribute.String("net.host.ip", "fe80::202:b3ff:fe1e:8329"), - attribute.Int("net.host.port", 78), - }, - }, - } - 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, noTLS) - got := sc.NetAttributesFromHTTPRequest(tc.network, r) - if diff := cmp.Diff( - tc.expected, - got, - cmp.AllowUnexported(attribute.Value{})); diff != "" { - t.Fatalf("attributes differ: diff %+v,", diff) - } - }) - } -} - -func TestEndUserAttributesFromHTTPRequest(t *testing.T) { - r := testRequest("GET", "/user/123", "HTTP/1.1", "", "", nil, http.Header{}, withTLS) - var expected []attribute.KeyValue - got := sc.EndUserAttributesFromHTTPRequest(r) - assert.ElementsMatch(t, expected, got) - r.SetBasicAuth("admin", "password") - expected = []attribute.KeyValue{attribute.String("enduser.id", "admin")} - got = sc.EndUserAttributesFromHTTPRequest(r) - assert.ElementsMatch(t, expected, got) -} - -func TestHTTPServerAttributesFromHTTPRequest(t *testing.T) { - type testcase struct { - name string - - serverName string - route 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: "", - route: "", - 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.target", "/user/123"), - attribute.String("http.scheme", "http"), - attribute.String("http.flavor", "1.0"), - }, - }, - { - name: "with server name", - serverName: "my-server-name", - route: "", - 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.target", "/user/123"), - 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", - route: "", - 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.target", "/user/123"), - 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", - route: "/user/:id", - 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.target", "/user/123"), - attribute.String("http.scheme", "https"), - attribute.String("http.flavor", "1.0"), - attribute.String("http.server_name", "my-server-name"), - attribute.String("http.route", "/user/:id"), - }, - }, - { - name: "with host", - serverName: "my-server-name", - route: "/user/:id", - 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.target", "/user/123"), - attribute.String("http.scheme", "https"), - attribute.String("http.flavor", "1.0"), - attribute.String("http.server_name", "my-server-name"), - attribute.String("http.route", "/user/:id"), - attribute.String("http.host", "example.com"), - }, - }, - { - name: "with host fallback", - serverName: "my-server-name", - route: "/user/:id", - 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.target", "/user/123"), - attribute.String("http.scheme", "https"), - attribute.String("http.flavor", "1.0"), - attribute.String("http.server_name", "my-server-name"), - attribute.String("http.route", "/user/:id"), - attribute.String("http.host", "example.com"), - }, - }, - { - name: "with user agent", - serverName: "my-server-name", - route: "/user/:id", - 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.target", "/user/123"), - attribute.String("http.scheme", "https"), - attribute.String("http.flavor", "1.0"), - attribute.String("http.server_name", "my-server-name"), - attribute.String("http.route", "/user/:id"), - attribute.String("http.host", "example.com"), - attribute.String("http.user_agent", "foodownloader"), - }, - }, - { - name: "with proxy info", - serverName: "my-server-name", - route: "/user/:id", - 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.target", "/user/123"), - attribute.String("http.scheme", "https"), - attribute.String("http.flavor", "1.0"), - attribute.String("http.server_name", "my-server-name"), - attribute.String("http.route", "/user/:id"), - attribute.String("http.host", "example.com"), - attribute.String("http.user_agent", "foodownloader"), - attribute.String("http.client_ip", "203.0.113.195"), - }, - }, - { - name: "with http 1.1", - serverName: "my-server-name", - route: "/user/:id", - 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.target", "/user/123"), - attribute.String("http.scheme", "https"), - attribute.String("http.flavor", "1.1"), - attribute.String("http.server_name", "my-server-name"), - attribute.String("http.route", "/user/:id"), - attribute.String("http.host", "example.com"), - attribute.String("http.user_agent", "foodownloader"), - attribute.String("http.client_ip", "1.2.3.4"), - }, - }, - { - name: "with http 2", - serverName: "my-server-name", - route: "/user/:id", - 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.target", "/user/123"), - attribute.String("http.scheme", "https"), - attribute.String("http.flavor", "2"), - attribute.String("http.server_name", "my-server-name"), - attribute.String("http.route", "/user/:id"), - attribute.String("http.host", "example.com"), - attribute.String("http.user_agent", "foodownloader"), - attribute.String("http.client_ip", "1.2.3.4"), - }, - }, - { - name: "with content length", - method: "GET", - requestURI: "/user/123", - contentLength: 100, - expected: []attribute.KeyValue{ - attribute.String("http.method", "GET"), - attribute.String("http.target", "/user/123"), - attribute.String("http.scheme", "http"), - attribute.Int64("http.request_content_length", 100), - }, - }, + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/otel/attribute" +) + +type tlsOption int + +const ( + noTLS tlsOption = iota + withTLS +) + +var hc = &HTTPConv{ + NetConv: nc, + + EnduserIDKey: attribute.Key("enduser.id"), + HTTPClientIPKey: attribute.Key("http.client_ip"), + HTTPFlavorKey: attribute.Key("http.flavor"), + HTTPMethodKey: attribute.Key("http.method"), + HTTPRequestContentLengthKey: attribute.Key("http.request_content_length"), + HTTPResponseContentLengthKey: attribute.Key("http.response_content_length"), + HTTPRouteKey: attribute.Key("http.route"), + HTTPSchemeHTTP: attribute.String("http.scheme", "http"), + HTTPSchemeHTTPS: attribute.String("http.scheme", "https"), + HTTPStatusCodeKey: attribute.Key("http.status_code"), + HTTPTargetKey: attribute.Key("http.target"), + HTTPURLKey: attribute.Key("http.url"), + HTTPUserAgentKey: attribute.Key("http.user_agent"), +} + +func TestHTTPClientResponse(t *testing.T) { + const stat, n = 201, 397 + resp := http.Response{ + StatusCode: stat, + ContentLength: n, } - 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.HTTPServerAttributesFromHTTPRequest(tc.serverName, tc.route, r) - assertElementsMatch(t, tc.expected, got, "testcase %d - %s", idx, tc.name) + got := hc.ClientResponse(resp) + assert.Equal(t, 2, cap(got), "slice capacity") + assert.ElementsMatch(t, []attribute.KeyValue{ + attribute.Key("http.status_code").Int(stat), + attribute.Key("http.response_content_length").Int(n), + }, got) +} + +func srvAttr(method, scheme, target, proto, host string, opt ...attribute.KeyValue) []attribute.KeyValue { + attrs := []attribute.KeyValue{ + attribute.String("http.method", method), + attribute.String("http.target", target), + attribute.String("http.scheme", scheme), + attribute.String("http.flavor", proto), + attribute.String("net.host.name", host), + } + + seen := map[attribute.Key]int{ + attribute.Key("http.method"): 0, + attribute.Key("http.target"): 1, + attribute.Key("http.scheme"): 2, + attribute.Key("http.flavor"): 3, + attribute.Key("net.host.name"): 4, + } + for _, o := range opt { + idx, ok := seen[o.Key] + if ok { + attrs[idx] = o + continue + } + attrs = append(attrs, o) + seen[o.Key] = len(attrs) - 1 + } + + return attrs +} + +func TestHTTPServerRequest(t *testing.T) { + got := make(chan *http.Request, 1) + handler := func(w http.ResponseWriter, r *http.Request) { + got <- r + w.WriteHeader(http.StatusOK) + } + + srv := httptest.NewServer(http.HandlerFunc(handler)) + defer srv.Close() + + srvURL, err := url.Parse(srv.URL) + require.NoError(t, err) + srvPort, err := strconv.ParseInt(srvURL.Port(), 10, 32) + require.NoError(t, err) + + resp, err := srv.Client().Get(srv.URL) + require.NoError(t, err) + require.NoError(t, resp.Body.Close()) + + req := <-got + peer, peerPort := splitHostPort(req.RemoteAddr) + + const user = "alice" + req.SetBasicAuth(user, "pswrd") + + const clientIP = "127.0.0.5" + req.Header.Add("X-Forwarded-For", clientIP) + + assert.ElementsMatch(t, + srvAttr("GET", "http", "/", "1.1", srvURL.Hostname(), + attribute.Int("net.host.port", int(srvPort)), + attribute.String("net.sock.peer.addr", peer), + attribute.Int("net.sock.peer.port", peerPort), + attribute.String("http.user_agent", "Go-http-client/1.1"), + attribute.String("enduser.id", user), + attribute.String("http.client_ip", clientIP), + ), + hc.ServerRequest(req)) +} + +func TestHTTPServerRequestFailsGracefully(t *testing.T) { + req := new(http.Request) + var got []attribute.KeyValue + assert.NotPanics(t, func() { got = hc.ServerRequest(req) }) + assert.ElementsMatch(t, srvAttr("GET", "http", "", "", ""), got) +} + +/* +func testRequest(method, requestURI, proto, remoteAddr, host string, u *url.URL, header http.Header, tlsopt tlsOption) *http.Request { + major, minor := protoToInts(proto) + var tlsConn *tls.ConnectionState + switch tlsopt { + case noTLS: + case withTLS: + tlsConn = &tls.ConnectionState{} + } + return &http.Request{ + Method: method, + URL: u, + Proto: proto, + ProtoMajor: major, + ProtoMinor: minor, + Header: header, + Host: host, + RemoteAddr: remoteAddr, + RequestURI: requestURI, + TLS: tlsConn, + } +} + +func assertElementsMatch(t *testing.T, expected, got []attribute.KeyValue, format string, args ...interface{}) { + if !assert.ElementsMatchf(t, expected, got, format, args...) { + t.Log("expected:", kvStr(expected)) + t.Log("got:", kvStr(got)) + } +} + +func protoToInts(proto string) (int, int) { + switch proto { + case "HTTP/1.0": + return 1, 0 + case "HTTP/1.1": + return 1, 1 + case "HTTP/2.0": + return 2, 0 + } + // invalid proto + return 13, 42 +} + +func kvStr(kvs []attribute.KeyValue) string { + sb := strings.Builder{} + _, _ = sb.WriteRune('[') + for idx, attr := range kvs { + if idx > 0 { + _, _ = sb.WriteString(", ") + } + _, _ = sb.WriteString((string)(attr.Key)) + _, _ = sb.WriteString(": ") + _, _ = sb.WriteString(attr.Value.Emit()) } + _, _ = sb.WriteRune(']') + return sb.String() } func TestHTTPAttributesFromHTTPStatusCode(t *testing.T) { expected := []attribute.KeyValue{ attribute.Int("http.status_code", 404), } - got := sc.HTTPAttributesFromHTTPStatusCode(http.StatusNotFound) + got := hc.HTTPAttributesFromHTTPStatusCode(http.StatusNotFound) assertElementsMatch(t, expected, got, "with valid HTTP status code") assert.ElementsMatch(t, expected, got) expected = []attribute.KeyValue{ attribute.Int("http.status_code", 499), } - got = sc.HTTPAttributesFromHTTPStatusCode(499) + got = hc.HTTPAttributesFromHTTPStatusCode(499) assertElementsMatch(t, expected, got, "with invalid HTTP status code") } @@ -964,63 +270,6 @@ func getExpectedCodeForHTTPCode(code int, spanKind trace.SpanKind) codes.Code { return codes.Error } -func assertElementsMatch(t *testing.T, expected, got []attribute.KeyValue, format string, args ...interface{}) { - if !assert.ElementsMatchf(t, expected, got, format, args...) { - t.Log("expected:", kvStr(expected)) - t.Log("got:", kvStr(got)) - } -} - -func testRequest(method, requestURI, proto, remoteAddr, host string, u *url.URL, header http.Header, tlsopt tlsOption) *http.Request { - major, minor := protoToInts(proto) - var tlsConn *tls.ConnectionState - switch tlsopt { - case noTLS: - case withTLS: - tlsConn = &tls.ConnectionState{} - } - return &http.Request{ - Method: method, - URL: u, - Proto: proto, - ProtoMajor: major, - ProtoMinor: minor, - Header: header, - Host: host, - RemoteAddr: remoteAddr, - RequestURI: requestURI, - TLS: tlsConn, - } -} - -func protoToInts(proto string) (int, int) { - switch proto { - case "HTTP/1.0": - return 1, 0 - case "HTTP/1.1": - return 1, 1 - case "HTTP/2.0": - return 2, 0 - } - // invalid proto - return 13, 42 -} - -func kvStr(kvs []attribute.KeyValue) string { - sb := strings.Builder{} - _, _ = sb.WriteRune('[') - for idx, attr := range kvs { - if idx > 0 { - _, _ = sb.WriteString(", ") - } - _, _ = sb.WriteString((string)(attr.Key)) - _, _ = sb.WriteString(": ") - _, _ = sb.WriteString(attr.Value.Emit()) - } - _, _ = sb.WriteRune(']') - return sb.String() -} - func TestHTTPClientAttributesFromHTTPRequest(t *testing.T) { testCases := []struct { name string @@ -1231,7 +480,7 @@ func TestHTTPClientAttributesFromHTTPRequest(t *testing.T) { 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 := sc.HTTPClientAttributesFromHTTPRequest(r) + got := hc.HTTPClientAttributesFromHTTPRequest(r) assert.ElementsMatch(t, tc.expected, got) }) } @@ -1474,7 +723,7 @@ func TestHTTPServerMetricAttributesFromHTTPRequest(t *testing.T) { 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) + got := hc.HTTPServerMetricAttributesFromHTTPRequest(tc.serverName, r) assertElementsMatch(t, tc.expected, got, "testcase %d - %s", idx, tc.name) } } @@ -1517,7 +766,8 @@ func TestHttpBasicAttributesFromHTTPRequest(t *testing.T) { 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) + got := hc.httpBasicAttributesFromHTTPRequest(r) assertElementsMatch(t, tc.expected, got, "testcase %d - %s", idx, tc.name) } } +*/ From 0afd72363f2094837d0cba7fd5ada733339019b8 Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Sat, 26 Nov 2022 11:29:27 -0800 Subject: [PATCH 04/15] Unit test ClientRequest --- semconv/internal/v2/http.go | 22 +++++++++----- semconv/internal/v2/http_test.go | 52 ++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 7 deletions(-) diff --git a/semconv/internal/v2/http.go b/semconv/internal/v2/http.go index 470d14e7071..6c60f601dc8 100644 --- a/semconv/internal/v2/http.go +++ b/semconv/internal/v2/http.go @@ -76,7 +76,11 @@ func (c *HTTPConv) ClientResponse(resp http.Response) []attribute.KeyValue { // by a client. func (c *HTTPConv) ClientRequest(req *http.Request) []attribute.KeyValue { n := 3 // URL, peer name, proto, and method. - peer, p := firstHostPort(req.URL.Host, req.Header.Get("Host")) + var h string + if req.URL != nil { + h = req.URL.Host + } + peer, p := firstHostPort(h, req.Header.Get("Host")) port := requiredHTTPPort(req.TLS != nil, p) if port > 0 { n++ @@ -97,12 +101,16 @@ func (c *HTTPConv) ClientRequest(req *http.Request) []attribute.KeyValue { attrs = append(attrs, c.method(req.Method)) attrs = append(attrs, c.proto(req.Proto)) - // Remove any username/password info that may be in the URL. - userinfo := req.URL.User - req.URL.User = nil - attrs = append(attrs, c.HTTPURLKey.String(req.URL.String())) - // Restore any username/password info that was removed. - req.URL.User = userinfo + var u string + if req.URL != nil { + // Remove any username/password info that may be in the URL. + userinfo := req.URL.User + req.URL.User = nil + u = req.URL.String() + // Restore any username/password info that was removed. + req.URL.User = userinfo + } + attrs = append(attrs, c.HTTPURLKey.String(u)) attrs = append(attrs, c.NetConv.PeerName(peer)) if port > 0 { diff --git a/semconv/internal/v2/http_test.go b/semconv/internal/v2/http_test.go index 2429978d6c5..a596562191a 100644 --- a/semconv/internal/v2/http_test.go +++ b/semconv/internal/v2/http_test.go @@ -65,6 +65,58 @@ func TestHTTPClientResponse(t *testing.T) { }, got) } +func TestHTTPClientRequest(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.Equal( + t, + []attribute.KeyValue{ + attribute.String("http.method", "GET"), + attribute.String("http.flavor", "1.0"), + attribute.String("http.url", "http://127.0.0.1:8080/resource"), + attribute.String("net.peer.name", "127.0.0.1"), + attribute.Int("net.peer.port", 8080), + attribute.String("http.user_agent", agent), + attribute.Int("http.request_content_length", n), + attribute.String("enduser.id", user), + }, + hc.ClientRequest(req), + ) +} + +func TestHTTPClientRequestRequired(t *testing.T) { + req := new(http.Request) + var got []attribute.KeyValue + assert.NotPanics(t, func() { got = hc.ClientRequest(req) }) + want := []attribute.KeyValue{ + attribute.String("http.method", "GET"), + attribute.String("http.flavor", ""), + attribute.String("http.url", ""), + attribute.String("net.peer.name", ""), + } + assert.Equal(t, want, got) +} + func srvAttr(method, scheme, target, proto, host string, opt ...attribute.KeyValue) []attribute.KeyValue { attrs := []attribute.KeyValue{ attribute.String("http.method", method), From d5bb8a37bf8036fe52bb12da94f9651f0b9d622d Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Sat, 26 Nov 2022 11:32:34 -0800 Subject: [PATCH 05/15] Remove unneeded --- semconv/internal/v2/http_test.go | 104 +++++-------------------------- 1 file changed, 15 insertions(+), 89 deletions(-) diff --git a/semconv/internal/v2/http_test.go b/semconv/internal/v2/http_test.go index a596562191a..f3a67566cf3 100644 --- a/semconv/internal/v2/http_test.go +++ b/semconv/internal/v2/http_test.go @@ -117,35 +117,6 @@ func TestHTTPClientRequestRequired(t *testing.T) { assert.Equal(t, want, got) } -func srvAttr(method, scheme, target, proto, host string, opt ...attribute.KeyValue) []attribute.KeyValue { - attrs := []attribute.KeyValue{ - attribute.String("http.method", method), - attribute.String("http.target", target), - attribute.String("http.scheme", scheme), - attribute.String("http.flavor", proto), - attribute.String("net.host.name", host), - } - - seen := map[attribute.Key]int{ - attribute.Key("http.method"): 0, - attribute.Key("http.target"): 1, - attribute.Key("http.scheme"): 2, - attribute.Key("http.flavor"): 3, - attribute.Key("net.host.name"): 4, - } - for _, o := range opt { - idx, ok := seen[o.Key] - if ok { - attrs[idx] = o - continue - } - attrs = append(attrs, o) - seen[o.Key] = len(attrs) - 1 - } - - return attrs -} - func TestHTTPServerRequest(t *testing.T) { got := make(chan *http.Request, 1) handler := func(w http.ResponseWriter, r *http.Request) { @@ -175,14 +146,19 @@ func TestHTTPServerRequest(t *testing.T) { req.Header.Add("X-Forwarded-For", clientIP) assert.ElementsMatch(t, - srvAttr("GET", "http", "/", "1.1", srvURL.Hostname(), + []attribute.KeyValue{ + attribute.String("http.method", "GET"), + attribute.String("http.target", "/"), + attribute.String("http.scheme", "http"), + attribute.String("http.flavor", "1.1"), + attribute.String("net.host.name", srvURL.Hostname()), attribute.Int("net.host.port", int(srvPort)), attribute.String("net.sock.peer.addr", peer), attribute.Int("net.sock.peer.port", peerPort), attribute.String("http.user_agent", "Go-http-client/1.1"), attribute.String("enduser.id", user), attribute.String("http.client_ip", clientIP), - ), + }, hc.ServerRequest(req)) } @@ -190,67 +166,17 @@ func TestHTTPServerRequestFailsGracefully(t *testing.T) { req := new(http.Request) var got []attribute.KeyValue assert.NotPanics(t, func() { got = hc.ServerRequest(req) }) - assert.ElementsMatch(t, srvAttr("GET", "http", "", "", ""), got) -} - -/* -func testRequest(method, requestURI, proto, remoteAddr, host string, u *url.URL, header http.Header, tlsopt tlsOption) *http.Request { - major, minor := protoToInts(proto) - var tlsConn *tls.ConnectionState - switch tlsopt { - case noTLS: - case withTLS: - tlsConn = &tls.ConnectionState{} - } - return &http.Request{ - Method: method, - URL: u, - Proto: proto, - ProtoMajor: major, - ProtoMinor: minor, - Header: header, - Host: host, - RemoteAddr: remoteAddr, - RequestURI: requestURI, - TLS: tlsConn, - } -} - -func assertElementsMatch(t *testing.T, expected, got []attribute.KeyValue, format string, args ...interface{}) { - if !assert.ElementsMatchf(t, expected, got, format, args...) { - t.Log("expected:", kvStr(expected)) - t.Log("got:", kvStr(got)) - } -} - -func protoToInts(proto string) (int, int) { - switch proto { - case "HTTP/1.0": - return 1, 0 - case "HTTP/1.1": - return 1, 1 - case "HTTP/2.0": - return 2, 0 - } - // invalid proto - return 13, 42 -} - -func kvStr(kvs []attribute.KeyValue) string { - sb := strings.Builder{} - _, _ = sb.WriteRune('[') - for idx, attr := range kvs { - if idx > 0 { - _, _ = sb.WriteString(", ") - } - _, _ = sb.WriteString((string)(attr.Key)) - _, _ = sb.WriteString(": ") - _, _ = sb.WriteString(attr.Value.Emit()) + want := []attribute.KeyValue{ + attribute.String("http.method", "GET"), + attribute.String("http.target", ""), + attribute.String("http.scheme", "http"), + attribute.String("http.flavor", ""), + attribute.String("net.host.name", ""), } - _, _ = sb.WriteRune(']') - return sb.String() + assert.ElementsMatch(t, want, got) } +/* func TestHTTPAttributesFromHTTPStatusCode(t *testing.T) { expected := []attribute.KeyValue{ attribute.Int("http.status_code", 404), From b46910d1c63a6fa069d733569880c1827a321c0b Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Sat, 26 Nov 2022 11:45:16 -0800 Subject: [PATCH 06/15] Unit test helper funcs --- semconv/internal/v2/http_test.go | 59 ++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/semconv/internal/v2/http_test.go b/semconv/internal/v2/http_test.go index f3a67566cf3..c32082e438b 100644 --- a/semconv/internal/v2/http_test.go +++ b/semconv/internal/v2/http_test.go @@ -176,6 +176,65 @@ func TestHTTPServerRequestFailsGracefully(t *testing.T) { assert.ElementsMatch(t, want, got) } +func TestMethod(t *testing.T) { + assert.Equal(t, attribute.String("http.method", "POST"), hc.method("POST")) + assert.Equal(t, attribute.String("http.method", "GET"), hc.method("")) + assert.Equal(t, attribute.String("http.method", "garbage"), hc.method("garbage")) +} + +func TestScheme(t *testing.T) { + assert.Equal(t, attribute.String("http.scheme", "http"), hc.scheme(false)) + assert.Equal(t, attribute.String("http.scheme", "https"), hc.scheme(true)) +} + +func TestProto(t *testing.T) { + tests := map[string]string{ + "HTTP/1.0": "1.0", + "HTTP/1.1": "1.1", + "HTTP/2": "2.0", + "HTTP/3": "3.0", + "SPDY": "SPDY", + "QUIC": "QUIC", + "other": "other", + } + + for proto, want := range tests { + assert.Equal(t, attribute.String("http.flavor", want), hc.proto(proto)) + } +} + +func TestServerClientIP(t *testing.T) { + tests := []struct { + xForwardedFor string + want string + }{ + {"", ""}, + {"127.0.0.1", "127.0.0.1"}, + {"127.0.0.1,127.0.0.5", "127.0.0.1"}, + } + for _, test := range tests { + assert.Equal(t, test.want, serverClientIP(test.xForwardedFor)) + } +} + +func TestRequiredHTTPPort(t *testing.T) { + tests := []struct { + https bool + port int + want int + }{ + {true, 443, -1}, + {true, 80, 80}, + {true, 8081, 8081}, + {false, 443, 443}, + {false, 80, -1}, + {false, 8080, 8080}, + } + for _, test := range tests { + assert.Equal(t, test.want, requiredHTTPPort(test.https, test.port)) + } +} + /* func TestHTTPAttributesFromHTTPStatusCode(t *testing.T) { expected := []attribute.KeyValue{ From d31c58cb2ca11355a250bb7449597d2c3e647f25 Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Sun, 27 Nov 2022 09:50:19 -0800 Subject: [PATCH 07/15] Add unit tests for remaining funcs --- semconv/internal/v2/http_test.go | 773 +++++++++---------------------- 1 file changed, 218 insertions(+), 555 deletions(-) diff --git a/semconv/internal/v2/http_test.go b/semconv/internal/v2/http_test.go index c32082e438b..b71a5cddbfe 100644 --- a/semconv/internal/v2/http_test.go +++ b/semconv/internal/v2/http_test.go @@ -24,6 +24,7 @@ import ( "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" ) type tlsOption int @@ -199,7 +200,8 @@ func TestProto(t *testing.T) { } for proto, want := range tests { - assert.Equal(t, attribute.String("http.flavor", want), hc.proto(proto)) + expect := attribute.String("http.flavor", want) + assert.Equal(t, expect, hc.proto(proto), proto) } } @@ -213,7 +215,8 @@ func TestServerClientIP(t *testing.T) { {"127.0.0.1,127.0.0.5", "127.0.0.1"}, } for _, test := range tests { - assert.Equal(t, test.want, serverClientIP(test.xForwardedFor)) + got := serverClientIP(test.xForwardedFor) + assert.Equal(t, test.want, got, test.xForwardedFor) } } @@ -231,580 +234,240 @@ func TestRequiredHTTPPort(t *testing.T) { {false, 8080, 8080}, } for _, test := range tests { - assert.Equal(t, test.want, requiredHTTPPort(test.https, test.port)) + got := requiredHTTPPort(test.https, test.port) + assert.Equal(t, test.want, got, test.https, test.port) } } -/* -func TestHTTPAttributesFromHTTPStatusCode(t *testing.T) { - expected := []attribute.KeyValue{ - attribute.Int("http.status_code", 404), +func TestFirstHostPort(t *testing.T) { + host, port := "127.0.0.1", 8080 + hostport := "127.0.0.1:8080" + sources := [][]string{ + {hostport}, + {"", hostport}, + {"", "", hostport}, + {"", "", hostport, ""}, + {"", "", hostport, "127.0.0.3:80"}, } - got := hc.HTTPAttributesFromHTTPStatusCode(http.StatusNotFound) - assertElementsMatch(t, expected, got, "with valid HTTP status code") - assert.ElementsMatch(t, expected, got) - expected = []attribute.KeyValue{ - attribute.Int("http.status_code", 499), + + for _, src := range sources { + h, p := firstHostPort(src...) + assert.Equal(t, host, h, src) + assert.Equal(t, port, p, src) } - got = hc.HTTPAttributesFromHTTPStatusCode(499) - assertElementsMatch(t, expected, got, "with invalid HTTP status code") } -func TestSpanStatusFromHTTPStatusCode(t *testing.T) { - for code := 0; code < 1000; code++ { - expected := getExpectedCodeForHTTPCode(code, trace.SpanKindClient) - got, msg := SpanStatusFromHTTPStatusCode(code) - assert.Equalf(t, expected, got, "%s vs %s", expected, got) - - _, valid := validateHTTPStatusCode(code) - if !valid { - assert.NotEmpty(t, msg, "message should be set if error cannot be inferred from code") - } else { - assert.Empty(t, msg, "message should not be set if error can be inferred from code") - } +func TestSplitHostPort(t *testing.T) { + tests := []struct { + hostport string + host string + port int + }{ + {"", "", -1}, + {":8080", "", 8080}, + {"127.0.0.1", "", -1}, + {"127.0.0.1:", "127.0.0.1", -1}, + {"127.0.0.1:port", "127.0.0.1", -1}, + {"127.0.0.1:8080", "127.0.0.1", 8080}, } -} -func TestSpanStatusFromHTTPStatusCodeAndSpanKind(t *testing.T) { - for code := 0; code < 1000; code++ { - expected := getExpectedCodeForHTTPCode(code, trace.SpanKindClient) - got, msg := SpanStatusFromHTTPStatusCodeAndSpanKind(code, trace.SpanKindClient) - assert.Equalf(t, expected, got, "%s vs %s", expected, got) - - _, valid := validateHTTPStatusCode(code) - if !valid { - assert.NotEmpty(t, msg, "message should be set if error cannot be inferred from code") - } else { - assert.Empty(t, msg, "message should not be set if error can be inferred from code") - } + for _, test := range tests { + h, p := splitHostPort(test.hostport) + assert.Equal(t, test.host, h, test.hostport) + assert.Equal(t, test.port, p, test.hostport) } - code, _ := SpanStatusFromHTTPStatusCodeAndSpanKind(400, trace.SpanKindServer) - assert.Equalf(t, codes.Unset, code, "message should be set if error cannot be inferred from code") } -func getExpectedCodeForHTTPCode(code int, spanKind trace.SpanKind) codes.Code { - if http.StatusText(code) == "" { - return codes.Error - } - switch code { - case - http.StatusUnauthorized, - http.StatusForbidden, - http.StatusNotFound, - http.StatusTooManyRequests, - http.StatusNotImplemented, - http.StatusServiceUnavailable, - http.StatusGatewayTimeout: - return codes.Error - } - category := code / 100 - if category > 0 && category < 4 { - return codes.Unset - } - if spanKind == trace.SpanKindServer && category == 4 { - return codes.Unset - } - return codes.Error +func TestRequestHeader(t *testing.T) { + ips := []string{"127.0.0.5", "127.0.0.9"} + user := []string{"alice"} + h := http.Header{"ips": ips, "user": user} + + got := hc.RequestHeader(h) + assert.Equal(t, 2, cap(got), "slice capacity") + assert.ElementsMatch(t, []attribute.KeyValue{ + attribute.StringSlice("http.request.header.ips", ips), + attribute.StringSlice("http.request.header.user", user), + }, got) } -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 []attribute.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: []attribute.KeyValue{ - attribute.String("http.method", "GET"), - attribute.String("http.url", "/user/123"), - attribute.String("http.scheme", "http"), - attribute.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: []attribute.KeyValue{ - attribute.String("http.method", "GET"), - attribute.String("http.url", "/user/123"), - attribute.String("http.scheme", "https"), - attribute.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: []attribute.KeyValue{ - attribute.String("http.method", "GET"), - attribute.String("http.url", "/user/123"), - attribute.String("http.scheme", "https"), - attribute.String("http.flavor", "1.0"), - attribute.String("http.host", "example.com"), - }, - }, - { - name: "with host fallback", - method: "GET", - requestURI: "/user/123", - proto: "HTTP/1.0", - remoteAddr: "", - host: "", - url: &url.URL{ - Scheme: "https", - Host: "example.com", - Path: "/user/123", - }, - header: nil, - tls: withTLS, - expected: []attribute.KeyValue{ - attribute.String("http.method", "GET"), - attribute.String("http.url", "https://example.com/user/123"), - attribute.String("http.scheme", "https"), - attribute.String("http.flavor", "1.0"), - attribute.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: []attribute.KeyValue{ - attribute.String("http.method", "GET"), - attribute.String("http.url", "/user/123"), - attribute.String("http.scheme", "https"), - attribute.String("http.flavor", "1.0"), - attribute.String("http.host", "example.com"), - attribute.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: []attribute.KeyValue{ - attribute.String("http.method", "GET"), - attribute.String("http.url", "/user/123"), - attribute.String("http.scheme", "https"), - attribute.String("http.flavor", "1.1"), - attribute.String("http.host", "example.com"), - attribute.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: []attribute.KeyValue{ - attribute.String("http.method", "GET"), - attribute.String("http.url", "/user/123"), - attribute.String("http.scheme", "https"), - attribute.String("http.flavor", "2"), - attribute.String("http.host", "example.com"), - attribute.String("http.user_agent", "foodownloader"), - }, - }, - { - name: "with content length", - method: "GET", - url: &url.URL{ - Path: "/user/123", - }, - contentLength: 100, - expected: []attribute.KeyValue{ - attribute.String("http.method", "GET"), - attribute.String("http.url", "/user/123"), - attribute.String("http.scheme", "http"), - attribute.Int64("http.request_content_length", 100), - }, - }, - { - name: "with empty method (fallback to GET)", - method: "", - url: &url.URL{ - Path: "/user/123", - }, - expected: []attribute.KeyValue{ - attribute.String("http.method", "GET"), - attribute.String("http.url", "/user/123"), - attribute.String("http.scheme", "http"), - }, - }, - { - name: "authentication information is stripped", - method: "", - url: &url.URL{ - Path: "/user/123", - User: url.UserPassword("foo", "bar"), - }, - expected: []attribute.KeyValue{ - attribute.String("http.method", "GET"), - attribute.String("http.url", "/user/123"), - attribute.String("http.scheme", "http"), - }, - }, - } +func TestReponseHeader(t *testing.T) { + ips := []string{"127.0.0.5", "127.0.0.9"} + user := []string{"alice"} + h := http.Header{"ips": ips, "user": user} - 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 := hc.HTTPClientAttributesFromHTTPRequest(r) - assert.ElementsMatch(t, tc.expected, got) - }) - } + got := hc.ResponseHeader(h) + assert.Equal(t, 2, cap(got), "slice capacity") + assert.ElementsMatch(t, []attribute.KeyValue{ + attribute.StringSlice("http.response.header.ips", ips), + attribute.StringSlice("http.response.header.user", user), + }, got) } -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"), - }, - }, +func TestClientStatus(t *testing.T) { + tests := []struct { + code int + stat codes.Code + msg bool + }{ + {0, codes.Error, true}, + {http.StatusContinue, codes.Unset, false}, + {http.StatusSwitchingProtocols, codes.Unset, false}, + {http.StatusProcessing, codes.Unset, false}, + {http.StatusEarlyHints, codes.Unset, false}, + {http.StatusOK, codes.Unset, false}, + {http.StatusCreated, codes.Unset, false}, + {http.StatusAccepted, codes.Unset, false}, + {http.StatusNonAuthoritativeInfo, codes.Unset, false}, + {http.StatusNoContent, codes.Unset, false}, + {http.StatusResetContent, codes.Unset, false}, + {http.StatusPartialContent, codes.Unset, false}, + {http.StatusMultiStatus, codes.Unset, false}, + {http.StatusAlreadyReported, codes.Unset, false}, + {http.StatusIMUsed, codes.Unset, false}, + {http.StatusMultipleChoices, codes.Unset, false}, + {http.StatusMovedPermanently, codes.Unset, false}, + {http.StatusFound, codes.Unset, false}, + {http.StatusSeeOther, codes.Unset, false}, + {http.StatusNotModified, codes.Unset, false}, + {http.StatusUseProxy, codes.Unset, false}, + {306, codes.Error, true}, + {http.StatusTemporaryRedirect, codes.Unset, false}, + {http.StatusPermanentRedirect, codes.Unset, false}, + {http.StatusBadRequest, codes.Error, false}, + {http.StatusUnauthorized, codes.Error, false}, + {http.StatusPaymentRequired, codes.Error, false}, + {http.StatusForbidden, codes.Error, false}, + {http.StatusNotFound, codes.Error, false}, + {http.StatusMethodNotAllowed, codes.Error, false}, + {http.StatusNotAcceptable, codes.Error, false}, + {http.StatusProxyAuthRequired, codes.Error, false}, + {http.StatusRequestTimeout, codes.Error, false}, + {http.StatusConflict, codes.Error, false}, + {http.StatusGone, codes.Error, false}, + {http.StatusLengthRequired, codes.Error, false}, + {http.StatusPreconditionFailed, codes.Error, false}, + {http.StatusRequestEntityTooLarge, codes.Error, false}, + {http.StatusRequestURITooLong, codes.Error, false}, + {http.StatusUnsupportedMediaType, codes.Error, false}, + {http.StatusRequestedRangeNotSatisfiable, codes.Error, false}, + {http.StatusExpectationFailed, codes.Error, false}, + {http.StatusTeapot, codes.Error, false}, + {http.StatusMisdirectedRequest, codes.Error, false}, + {http.StatusUnprocessableEntity, codes.Error, false}, + {http.StatusLocked, codes.Error, false}, + {http.StatusFailedDependency, codes.Error, false}, + {http.StatusTooEarly, codes.Error, false}, + {http.StatusUpgradeRequired, codes.Error, false}, + {http.StatusPreconditionRequired, codes.Error, false}, + {http.StatusTooManyRequests, codes.Error, false}, + {http.StatusRequestHeaderFieldsTooLarge, codes.Error, false}, + {http.StatusUnavailableForLegalReasons, codes.Error, false}, + {http.StatusInternalServerError, codes.Error, false}, + {http.StatusNotImplemented, codes.Error, false}, + {http.StatusBadGateway, codes.Error, false}, + {http.StatusServiceUnavailable, codes.Error, false}, + {http.StatusGatewayTimeout, codes.Error, false}, + {http.StatusHTTPVersionNotSupported, codes.Error, false}, + {http.StatusVariantAlsoNegotiates, codes.Error, false}, + {http.StatusInsufficientStorage, codes.Error, false}, + {http.StatusLoopDetected, codes.Error, false}, + {http.StatusNotExtended, codes.Error, false}, + {http.StatusNetworkAuthenticationRequired, codes.Error, false}, + {600, codes.Error, true}, } - 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 := hc.HTTPServerMetricAttributesFromHTTPRequest(tc.serverName, r) - assertElementsMatch(t, tc.expected, got, "testcase %d - %s", idx, tc.name) + + for _, test := range tests { + c, msg := hc.ClientStatus(test.code) + assert.Equal(t, test.stat, c) + if test.msg && msg == "" { + t.Errorf("expected non-empty message for %d", test.code) + } else if !test.msg && msg != "" { + t.Errorf("expected empty message for %d, got: %s", test.code, msg) + } } } -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"), - }, - }, +func TestServerStatus(t *testing.T) { + tests := []struct { + code int + stat codes.Code + msg bool + }{ + {0, codes.Error, true}, + {http.StatusContinue, codes.Unset, false}, + {http.StatusSwitchingProtocols, codes.Unset, false}, + {http.StatusProcessing, codes.Unset, false}, + {http.StatusEarlyHints, codes.Unset, false}, + {http.StatusOK, codes.Unset, false}, + {http.StatusCreated, codes.Unset, false}, + {http.StatusAccepted, codes.Unset, false}, + {http.StatusNonAuthoritativeInfo, codes.Unset, false}, + {http.StatusNoContent, codes.Unset, false}, + {http.StatusResetContent, codes.Unset, false}, + {http.StatusPartialContent, codes.Unset, false}, + {http.StatusMultiStatus, codes.Unset, false}, + {http.StatusAlreadyReported, codes.Unset, false}, + {http.StatusIMUsed, codes.Unset, false}, + {http.StatusMultipleChoices, codes.Unset, false}, + {http.StatusMovedPermanently, codes.Unset, false}, + {http.StatusFound, codes.Unset, false}, + {http.StatusSeeOther, codes.Unset, false}, + {http.StatusNotModified, codes.Unset, false}, + {http.StatusUseProxy, codes.Unset, false}, + {306, codes.Error, true}, + {http.StatusTemporaryRedirect, codes.Unset, false}, + {http.StatusPermanentRedirect, codes.Unset, false}, + {http.StatusBadRequest, codes.Unset, false}, + {http.StatusUnauthorized, codes.Unset, false}, + {http.StatusPaymentRequired, codes.Unset, false}, + {http.StatusForbidden, codes.Unset, false}, + {http.StatusNotFound, codes.Unset, false}, + {http.StatusMethodNotAllowed, codes.Unset, false}, + {http.StatusNotAcceptable, codes.Unset, false}, + {http.StatusProxyAuthRequired, codes.Unset, false}, + {http.StatusRequestTimeout, codes.Unset, false}, + {http.StatusConflict, codes.Unset, false}, + {http.StatusGone, codes.Unset, false}, + {http.StatusLengthRequired, codes.Unset, false}, + {http.StatusPreconditionFailed, codes.Unset, false}, + {http.StatusRequestEntityTooLarge, codes.Unset, false}, + {http.StatusRequestURITooLong, codes.Unset, false}, + {http.StatusUnsupportedMediaType, codes.Unset, false}, + {http.StatusRequestedRangeNotSatisfiable, codes.Unset, false}, + {http.StatusExpectationFailed, codes.Unset, false}, + {http.StatusTeapot, codes.Unset, false}, + {http.StatusMisdirectedRequest, codes.Unset, false}, + {http.StatusUnprocessableEntity, codes.Unset, false}, + {http.StatusLocked, codes.Unset, false}, + {http.StatusFailedDependency, codes.Unset, false}, + {http.StatusTooEarly, codes.Unset, false}, + {http.StatusUpgradeRequired, codes.Unset, false}, + {http.StatusPreconditionRequired, codes.Unset, false}, + {http.StatusTooManyRequests, codes.Unset, false}, + {http.StatusRequestHeaderFieldsTooLarge, codes.Unset, false}, + {http.StatusUnavailableForLegalReasons, codes.Unset, false}, + {http.StatusInternalServerError, codes.Error, false}, + {http.StatusNotImplemented, codes.Error, false}, + {http.StatusBadGateway, codes.Error, false}, + {http.StatusServiceUnavailable, codes.Error, false}, + {http.StatusGatewayTimeout, codes.Error, false}, + {http.StatusHTTPVersionNotSupported, codes.Error, false}, + {http.StatusVariantAlsoNegotiates, codes.Error, false}, + {http.StatusInsufficientStorage, codes.Error, false}, + {http.StatusLoopDetected, codes.Error, false}, + {http.StatusNotExtended, codes.Error, false}, + {http.StatusNetworkAuthenticationRequired, codes.Error, false}, + {600, codes.Error, true}, } - 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 := hc.httpBasicAttributesFromHTTPRequest(r) - assertElementsMatch(t, tc.expected, got, "testcase %d - %s", idx, tc.name) + + for _, test := range tests { + c, msg := hc.ServerStatus(test.code) + assert.Equal(t, test.stat, c) + if test.msg && msg == "" { + t.Errorf("expected non-empty message for %d", test.code) + } else if !test.msg && msg != "" { + t.Errorf("expected empty message for %d, got: %s", test.code, msg) + } } } -*/ From fb9bb6ebf5aae4ee79ffbf074259c5d2ad12523c Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Tue, 29 Nov 2022 10:40:28 -0800 Subject: [PATCH 08/15] Update exported docs --- .../templates/httpconv/http.go.tmpl | 14 +++++++-- semconv/internal/v2/http.go | 29 +++++++++++++------ semconv/v1.13.0/httpconv/http.go | 14 +++++++-- 3 files changed, 44 insertions(+), 13 deletions(-) diff --git a/internal/tools/semconvkit/templates/httpconv/http.go.tmpl b/internal/tools/semconvkit/templates/httpconv/http.go.tmpl index ff9e1a008a8..f98a15fb22e 100644 --- a/internal/tools/semconvkit/templates/httpconv/http.go.tmpl +++ b/internal/tools/semconvkit/templates/httpconv/http.go.tmpl @@ -59,7 +59,8 @@ var ( ) // ClientResponse returns attributes for an HTTP response received by a client -// from a server. +// from a server. It will return the following attributes if the related values +// are defined in resp: "http.status.code", "http.response_content_length". // // This does not add all OpenTelemetry required attributes for an HTTP event, // it assumes ClientRequest was used to create the span with a complete set of @@ -71,7 +72,11 @@ func ClientResponse(resp http.Response) []attribute.KeyValue { return hc.ClientResponse(resp) } -// ClientRequest returns attributes for an HTTP request made by a client. +// ClientRequest returns attributes for an HTTP request made by a client. The +// following attributes are always returned: "http.url", "http.flavor", +// "http.method", "net.peer.name". The following attributes are returned if the +// related values are defined in req: "net.peer.port", "http.user_agent", +// "http.request_content_length", "enduser.id". func ClientRequest(req *http.Request) []attribute.KeyValue { return hc.ClientRequest(req) } @@ -83,6 +88,11 @@ func ClientStatus(code int) (codes.Code, string) { } // ServerRequest returns attributes for an HTTP request received by a server. +// The following attributes are always returned: "http.method", "http.scheme", +// "http.flavor", "http.target", "net.host.name". The following attributes are +// returned if they related values are defined in req: "net.host.port", +// "net.sock.peer.addr", "net.sock.peer.port", "http.user_agent", "enduser.id", +// "http.client_ip". func ServerRequest(req *http.Request) []attribute.KeyValue { return hc.ServerRequest(req) } diff --git a/semconv/internal/v2/http.go b/semconv/internal/v2/http.go index 6c60f601dc8..9b75d53be36 100644 --- a/semconv/internal/v2/http.go +++ b/semconv/internal/v2/http.go @@ -45,14 +45,16 @@ type HTTPConv struct { HTTPUserAgentKey attribute.Key } -// ClientResponse returns OpenTelemetry attributes for an HTTP response -// received by a client from a server. This does not add all OpenTelemetry -// required attributes for an HTTP event, it assumes HTTPClientRequest was used -// to create the span with a complete set of attributes. If a complete set of -// attributes can be generated using the request contained in resp. For -// example: +// ClientResponse returns attributes for an HTTP response received by a client +// from a server. The following attributes are returned if the related values +// are defined in resp: "http.status.code", "http.response_content_length". // -// append(ClientResponse(resp), HTTPClientRequest(resp.Request)...) +// This does not add all OpenTelemetry required attributes for an HTTP event, +// it assumes ClientRequest was used to create the span with a complete set of +// attributes. If a complete set of attributes can be generated using the +// request contained in resp. For example: +// +// append(ClientResponse(resp), ClientRequest(resp.Request)...) func (c *HTTPConv) ClientResponse(resp http.Response) []attribute.KeyValue { var n int if resp.StatusCode > 0 { @@ -72,8 +74,11 @@ func (c *HTTPConv) ClientResponse(resp http.Response) []attribute.KeyValue { return attrs } -// ClientRequest returns OpenTelemetry attributes for an HTTP request made -// by a client. +// ClientRequest returns attributes for an HTTP request made by a client. The +// following attributes are always returned: "http.url", "http.flavor", +// "http.method", "net.peer.name". The following attributes are returned if the +// related values are defined in req: "net.peer.port", "http.user_agent", +// "http.request_content_length", "enduser.id". func (c *HTTPConv) ClientRequest(req *http.Request) []attribute.KeyValue { n := 3 // URL, peer name, proto, and method. var h string @@ -132,6 +137,12 @@ func (c *HTTPConv) ClientRequest(req *http.Request) []attribute.KeyValue { return attrs } +// ServerRequest returns attributes for an HTTP request received by a server. +// The following attributes are always returned: "http.method", "http.scheme", +// "http.flavor", "http.target", "net.host.name". The following attributes are +// returned if they related values are defined in req: "net.host.port", +// "net.sock.peer.addr", "net.sock.peer.port", "http.user_agent", "enduser.id", +// "http.client_ip". func (c *HTTPConv) ServerRequest(req *http.Request) []attribute.KeyValue { n := 5 // Method, scheme, target, proto, and host name. host, p := splitHostPort(req.Host) diff --git a/semconv/v1.13.0/httpconv/http.go b/semconv/v1.13.0/httpconv/http.go index f263d4e9bb1..fd1134e15e2 100644 --- a/semconv/v1.13.0/httpconv/http.go +++ b/semconv/v1.13.0/httpconv/http.go @@ -59,7 +59,8 @@ var ( ) // ClientResponse returns attributes for an HTTP response received by a client -// from a server. +// from a server. It will return the following attributes if the related values +// are defined in resp: "http.status.code", "http.response_content_length". // // This does not add all OpenTelemetry required attributes for an HTTP event, // it assumes ClientRequest was used to create the span with a complete set of @@ -71,7 +72,11 @@ func ClientResponse(resp http.Response) []attribute.KeyValue { return hc.ClientResponse(resp) } -// ClientRequest returns attributes for an HTTP request made by a client. +// ClientRequest returns attributes for an HTTP request made by a client. The +// following attributes are always returned: "http.url", "http.flavor", +// "http.method", "net.peer.name". The following attributes are returned if the +// related values are defined in req: "net.peer.port", "http.user_agent", +// "http.request_content_length", "enduser.id". func ClientRequest(req *http.Request) []attribute.KeyValue { return hc.ClientRequest(req) } @@ -83,6 +88,11 @@ func ClientStatus(code int) (codes.Code, string) { } // ServerRequest returns attributes for an HTTP request received by a server. +// The following attributes are always returned: "http.method", "http.scheme", +// "http.flavor", "http.target", "net.host.name". The following attributes are +// returned if they related values are defined in req: "net.host.port", +// "net.sock.peer.addr", "net.sock.peer.port", "http.user_agent", "enduser.id", +// "http.client_ip". func ServerRequest(req *http.Request) []attribute.KeyValue { return hc.ServerRequest(req) } From 02c9c7e8cdea87a981e8d55a9ac95f2ef8b49e14 Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Tue, 29 Nov 2022 12:14:00 -0800 Subject: [PATCH 09/15] Fix lint --- internal/tools/semconvkit/templates/httpconv/http.go.tmpl | 2 +- semconv/internal/v2/http.go | 4 ++-- semconv/internal/v2/http_test.go | 7 ------- semconv/internal/v2/net_test.go | 1 + semconv/v1.13.0/httpconv/http.go | 2 +- 5 files changed, 5 insertions(+), 11 deletions(-) diff --git a/internal/tools/semconvkit/templates/httpconv/http.go.tmpl b/internal/tools/semconvkit/templates/httpconv/http.go.tmpl index f98a15fb22e..4d9244aa02d 100644 --- a/internal/tools/semconvkit/templates/httpconv/http.go.tmpl +++ b/internal/tools/semconvkit/templates/httpconv/http.go.tmpl @@ -84,7 +84,7 @@ func ClientRequest(req *http.Request) []attribute.KeyValue { // ClientStatus returns a span status code and message for an HTTP status code // value received by a client. func ClientStatus(code int) (codes.Code, string) { - return internal.SpanStatusFromHTTPStatusCode() + return hc.ClientStatus(code) } // ServerRequest returns attributes for an HTTP request received by a server. diff --git a/semconv/internal/v2/http.go b/semconv/internal/v2/http.go index 9b75d53be36..3d2791eb9cf 100644 --- a/semconv/internal/v2/http.go +++ b/semconv/internal/v2/http.go @@ -219,7 +219,7 @@ func (c *HTTPConv) method(method string) attribute.KeyValue { return c.HTTPMethodKey.String(method) } -func (c *HTTPConv) scheme(https bool) attribute.KeyValue { +func (c *HTTPConv) scheme(https bool) attribute.KeyValue { // nolint:revive if https { return c.HTTPSchemeHTTPS } @@ -248,7 +248,7 @@ func serverClientIP(xForwardedFor string) string { return xForwardedFor } -func requiredHTTPPort(https bool, port int) int { +func requiredHTTPPort(https bool, port int) int { // nolint:revive if https { if port > 0 && port != 443 { return port diff --git a/semconv/internal/v2/http_test.go b/semconv/internal/v2/http_test.go index b71a5cddbfe..c53a3e2c147 100644 --- a/semconv/internal/v2/http_test.go +++ b/semconv/internal/v2/http_test.go @@ -27,13 +27,6 @@ import ( "go.opentelemetry.io/otel/codes" ) -type tlsOption int - -const ( - noTLS tlsOption = iota - withTLS -) - var hc = &HTTPConv{ NetConv: nc, diff --git a/semconv/internal/v2/net_test.go b/semconv/internal/v2/net_test.go index ed8ba607751..4882603fb30 100644 --- a/semconv/internal/v2/net_test.go +++ b/semconv/internal/v2/net_test.go @@ -17,6 +17,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "go.opentelemetry.io/otel/attribute" ) diff --git a/semconv/v1.13.0/httpconv/http.go b/semconv/v1.13.0/httpconv/http.go index fd1134e15e2..9ba94c9e1e1 100644 --- a/semconv/v1.13.0/httpconv/http.go +++ b/semconv/v1.13.0/httpconv/http.go @@ -84,7 +84,7 @@ func ClientRequest(req *http.Request) []attribute.KeyValue { // ClientStatus returns a span status code and message for an HTTP status code // value received by a client. func ClientStatus(code int) (codes.Code, string) { - return internal.SpanStatusFromHTTPStatusCode() + return hc.ClientStatus(code) } // ServerRequest returns attributes for an HTTP request received by a server. From 8df8d23e59a2806cd7cbcefe138203aca1018e55 Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Tue, 29 Nov 2022 12:24:07 -0800 Subject: [PATCH 10/15] Add changelog entry --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c2703e18bc5..5cba25a749b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,16 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - The `Instrument` and `InstrumentKind` type are added to `go.opentelemetry.io/otel/sdk/metric`. These additions are replacements for the `Instrument` and `InstrumentKind` types from `go.opentelemetry.io/otel/sdk/metric/view`. (#3459) - The `Stream` type is added to `go.opentelemetry.io/otel/sdk/metric` to define a metric data stream a view will produce. (#3459) +- Add the `go.opentelemetry.io/otel/semconv/v1.13.0` package. + The package contains semantic conventions from the `v1.13.0` version of the OpenTelemetry specification. (#3499) + - The `EndUserAttributesFromHTTPRequest` function in `go.opentelemetry.io/otel/semconv/v1.12.0` is merged into `ClientRequest` and `ServerRequest` in `go.opentelemetry.io/otel/semconv/v1.13.0/httpconv`. + - The `HTTPAttributesFromHTTPStatusCode` function in `go.opentelemetry.io/otel/semconv/v1.12.0` is merged into `ClientResponse` in `go.opentelemetry.io/otel/semconv/v1.13.0/httpconv`. + - The `HTTPClientAttributesFromHTTPRequest` function in `go.opentelemetry.io/otel/semconv/v1.12.0` is replaced by `ClientRequest` in `go.opentelemetry.io/otel/semconv/v1.13.0/httpconv`. + - The `HTTPServerAttributesFromHTTPRequest` function in `go.opentelemetry.io/otel/semconv/v1.12.0` is replaced by `ServerRequest` in `go.opentelemetry.io/otel/semconv/v1.13.0/httpconv`. + - The `HTTPServerMetricAttributesFromHTTPRequest` function in `go.opentelemetry.io/otel/semconv/v1.12.0` is replaced by `ServerRequest` in `go.opentelemetry.io/otel/semconv/v1.13.0/httpconv`. + - The `NetAttributesFromHTTPRequest` function in `go.opentelemetry.io/otel/semconv/v1.12.0` is split into `Transport` in `go.opentelemetry.io/otel/semconv/v1.13.0/netconv` and `ClientRequest` or `ServerRequest` in `go.opentelemetry.io/otel/semconv/v1.13.0/httpconv`. + - The `SpanStatusFromHTTPStatusCode` function in `go.opentelemetry.io/otel/semconv/v1.12.0` is replaced by `ClientStatus` in `go.opentelemetry.io/otel/semconv/v1.13.0/httpconv`. + - The `SpanStatusFromHTTPStatusCodeAndSpanKind` function in `go.opentelemetry.io/otel/semconv/v1.12.0` is split into `ClientStatus` and `ServerStatus` in `go.opentelemetry.io/otel/semconv/v1.13.0/httpconv`. ### Changed From a58929a4fedcaef84205026eebc0a57599bcb832 Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Thu, 1 Dec 2022 12:04:51 -0800 Subject: [PATCH 11/15] Add Client/Server func to semconv/internal/v2 --- semconv/internal/v2/http.go | 19 --- semconv/internal/v2/http_test.go | 21 --- semconv/internal/v2/net.go | 252 ++++++++++++++++++++++++++++++- semconv/internal/v2/net_test.go | 252 +++++++++++++++++++++++++++++++ 4 files changed, 503 insertions(+), 41 deletions(-) diff --git a/semconv/internal/v2/http.go b/semconv/internal/v2/http.go index 3d2791eb9cf..6989391cfbd 100644 --- a/semconv/internal/v2/http.go +++ b/semconv/internal/v2/http.go @@ -16,9 +16,7 @@ package internal // import "go.opentelemetry.io/otel/semconv/internal/v2" import ( "fmt" - "net" "net/http" - "strconv" "strings" "go.opentelemetry.io/otel/attribute" @@ -272,23 +270,6 @@ func firstHostPort(source ...string) (host string, port int) { return } -// splitHostPort splits a network address of the form "host:port", -// "host%zone:port", "[host]:port" or "[host%zone]:port" into host or -// host%zone and port. -// -// A negative port is returned if no parsable port is found. -func splitHostPort(hostport string) (host string, port int) { - host, portStr, err := net.SplitHostPort(hostport) - if err != nil { - return host, -1 - } - p, err := strconv.ParseUint(portStr, 10, 16) - if err != nil { - return host, -1 - } - return host, int(p) -} - // RequestHeader returns the contents of h as OpenTelemetry attributes. func (c *HTTPConv) RequestHeader(h http.Header) []attribute.KeyValue { return c.header("http.request.header", h) diff --git a/semconv/internal/v2/http_test.go b/semconv/internal/v2/http_test.go index c53a3e2c147..edfc7b50f27 100644 --- a/semconv/internal/v2/http_test.go +++ b/semconv/internal/v2/http_test.go @@ -250,27 +250,6 @@ func TestFirstHostPort(t *testing.T) { } } -func TestSplitHostPort(t *testing.T) { - tests := []struct { - hostport string - host string - port int - }{ - {"", "", -1}, - {":8080", "", 8080}, - {"127.0.0.1", "", -1}, - {"127.0.0.1:", "127.0.0.1", -1}, - {"127.0.0.1:port", "127.0.0.1", -1}, - {"127.0.0.1:8080", "127.0.0.1", 8080}, - } - - for _, test := range tests { - h, p := splitHostPort(test.hostport) - assert.Equal(t, test.host, h, test.hostport) - assert.Equal(t, test.port, p, test.hostport) - } -} - func TestRequestHeader(t *testing.T) { ips := []string{"127.0.0.5", "127.0.0.9"} user := []string{"alice"} diff --git a/semconv/internal/v2/net.go b/semconv/internal/v2/net.go index d4b33fd24f8..fb761b6a127 100644 --- a/semconv/internal/v2/net.go +++ b/semconv/internal/v2/net.go @@ -14,7 +14,13 @@ package internal // import "go.opentelemetry.io/otel/semconv/internal/v2" -import "go.opentelemetry.io/otel/attribute" +import ( + "net" + "strconv" + "strings" + + "go.opentelemetry.io/otel/attribute" +) // NetConv are the network semantic convention attributes defined for a version // of the OpenTelemetry specification. @@ -23,8 +29,11 @@ type NetConv struct { NetHostPortKey attribute.Key NetPeerNameKey attribute.Key NetPeerPortKey attribute.Key + NetSockFamilyKey attribute.Key NetSockPeerAddrKey attribute.Key NetSockPeerPortKey attribute.Key + NetSockHostAddrKey attribute.Key + NetSockHostPortKey attribute.Key NetTransportOther attribute.KeyValue NetTransportTCP attribute.KeyValue NetTransportUDP attribute.KeyValue @@ -45,6 +54,73 @@ func (c *NetConv) Transport(network string) attribute.KeyValue { } } +// Host returns attributes for a network host address. +func (c *NetConv) Host(address string) []attribute.KeyValue { + h, p := splitHostPort(address) + var n int + if h != "" { + n++ + if p > 0 { + n++ + } + } + + if n == 0 { + return nil + } + + attrs := make([]attribute.KeyValue, 0, n) + attrs = append(attrs, c.HostName(h)) + if p > 0 { + attrs = append(attrs, c.HostPort(int(p))) + } + return attrs +} + +// Server returns attributes for a server network listener at address. See +// net.Listen for information about acceptable address values, address should +// be the same as the one used to create l. +func (c *NetConv) Server(address string, l net.Listener) []attribute.KeyValue { + if l == nil { + return c.Host(address) + } + + lAddr := l.Addr() + if lAddr == nil { + return c.Host(address) + } + + hostName, hostPort := splitHostPort(address) + sockHostAddr, sockHostPort := splitHostPort(lAddr.String()) + network := lAddr.Network() + sockFamily := family(network, sockHostAddr) + + n := nonZeroStr(hostName, network, sockHostAddr, sockFamily) + n += positiveInt(hostPort, sockHostPort) + attr := make([]attribute.KeyValue, 0, n) + if hostName != "" { + attr = append(attr, c.HostName(hostName)) + if hostPort > 0 { + // Only if net.host.name is set should net.host.port be. + attr = append(attr, c.HostPort(hostPort)) + } + } + if network != "" { + attr = append(attr, c.Transport(network)) + } + if sockFamily != "" { + attr = append(attr, c.NetSockFamilyKey.String(sockFamily)) + } + if sockHostAddr != "" { + attr = append(attr, c.NetSockHostAddrKey.String(sockHostAddr)) + if sockHostPort > 0 { + // Only if net.sock.host.addr is set should net.sock.host.port be. + attr = append(attr, c.NetSockHostPortKey.Int(sockHostPort)) + } + } + return attr +} + func (c *NetConv) HostName(name string) attribute.KeyValue { return c.NetHostNameKey.String(name) } @@ -53,6 +129,141 @@ func (c *NetConv) HostPort(port int) attribute.KeyValue { return c.NetHostPortKey.Int(port) } +// Client returns attributes for a client network connection to address. See +// net.Dial for information about acceptable address values, address should be +// the same as the one used to create conn. +func (c *NetConv) Client(address string, conn net.Conn) []attribute.KeyValue { + if conn == nil { + return c.Peer(address) + } + + lAddr, rAddr := conn.LocalAddr(), conn.RemoteAddr() + + var network string + switch { + case lAddr != nil: + network = lAddr.Network() + case rAddr != nil: + network = rAddr.Network() + default: + return c.Peer(address) + } + + peerName, peerPort := splitHostPort(address) + var ( + sockFamily string + sockPeerAddr string + sockPeerPort int + sockHostAddr string + sockHostPort int + ) + + if lAddr != nil { + sockHostAddr, sockHostPort = splitHostPort(lAddr.String()) + } + + if rAddr != nil { + sockPeerAddr, sockPeerPort = splitHostPort(rAddr.String()) + } + + switch { + case sockHostAddr != "": + sockFamily = family(network, sockHostAddr) + case sockPeerAddr != "": + sockFamily = family(network, sockPeerAddr) + } + + n := nonZeroStr(peerName, network, sockPeerAddr, sockHostAddr, sockFamily) + n += positiveInt(peerPort, sockPeerPort, sockHostPort) + attr := make([]attribute.KeyValue, 0, n) + if peerName != "" { + attr = append(attr, c.PeerName(peerName)) + if peerPort > 0 { + // Only if net.peer.name is set should net.peer.port be. + attr = append(attr, c.PeerPort(peerPort)) + } + } + if network != "" { + attr = append(attr, c.Transport(network)) + } + if sockFamily != "" { + attr = append(attr, c.NetSockFamilyKey.String(sockFamily)) + } + if sockPeerAddr != "" { + attr = append(attr, c.NetSockPeerAddrKey.String(sockPeerAddr)) + if sockPeerPort > 0 { + // Only if net.sock.peer.addr is set should net.sock.peer.port be. + attr = append(attr, c.NetSockPeerPortKey.Int(sockPeerPort)) + } + } + if sockHostAddr != "" { + attr = append(attr, c.NetSockHostAddrKey.String(sockHostAddr)) + if sockHostPort > 0 { + // Only if net.sock.host.addr is set should net.sock.host.port be. + attr = append(attr, c.NetSockHostPortKey.Int(sockHostPort)) + } + } + return attr +} + +func family(network, address string) string { + switch network { + case "unix", "unixgram", "unixpacket": + return "unix" + default: + if ip := net.ParseIP(address); ip != nil { + if ip.To4() == nil { + return "inet6" + } + return "inet" + } + } + return "" +} + +func nonZeroStr(strs ...string) int { + var n int + for _, str := range strs { + if str != "" { + n++ + } + } + return n +} + +func positiveInt(ints ...int) int { + var n int + for _, i := range ints { + if i > 0 { + n++ + } + } + return n +} + +// Peer returns attributes for a network peer address. +func (c *NetConv) Peer(address string) []attribute.KeyValue { + h, p := splitHostPort(address) + var n int + if h != "" { + n++ + if p > 0 { + n++ + } + } + + if n == 0 { + return nil + } + + attrs := make([]attribute.KeyValue, 0, n) + attrs = append(attrs, c.PeerName(h)) + if p > 0 { + attrs = append(attrs, c.PeerPort(int(p))) + } + return attrs +} + func (c *NetConv) PeerName(name string) attribute.KeyValue { return c.NetPeerNameKey.String(name) } @@ -68,3 +279,42 @@ func (c *NetConv) SockPeerAddr(addr string) attribute.KeyValue { func (c *NetConv) SockPeerPort(port int) attribute.KeyValue { return c.NetSockPeerPortKey.Int(port) } + +// splitHostPort splits a network address hostport of the form "host", +// "host%zone", "[host]", "[host%zone], "host:port", "host%zone:port", +// "[host]:port", "[host%zone]:port", or ":port" into host or host%zone and +// port. +// +// An empty host is returned if it is not provided or unparsable. A negative +// port is returned if it is not provided or unparsable. +func splitHostPort(hostport string) (host string, port int) { + port = -1 + + if strings.HasPrefix(hostport, "[") { + addrEnd := strings.LastIndex(hostport, "]") + if addrEnd < 0 { + // Invalid hostport. + return + } + if i := strings.LastIndex(hostport[addrEnd:], ":"); i < 0 { + host = hostport[1:addrEnd] + return + } + } else { + if i := strings.LastIndex(hostport, ":"); i < 0 { + host = hostport + return + } + } + + host, pStr, err := net.SplitHostPort(hostport) + if err != nil { + return + } + + p, err := strconv.ParseUint(pStr, 10, 16) + if err != nil { + return + } + return host, int(p) +} diff --git a/semconv/internal/v2/net_test.go b/semconv/internal/v2/net_test.go index 4882603fb30..a3d03daf927 100644 --- a/semconv/internal/v2/net_test.go +++ b/semconv/internal/v2/net_test.go @@ -14,9 +14,12 @@ package internal import ( + "net" + "strconv" "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/attribute" ) @@ -61,6 +64,66 @@ func TestNetTransport(t *testing.T) { } } +func TestNetServerNilListener(t *testing.T) { + const addr = "127.0.0.1:8080" + got := nc.Server(addr, nil) + expected := nc.Host(addr) + assert.Equal(t, cap(expected), cap(got), "slice capacity") + assert.ElementsMatch(t, expected, got) +} + +type listener struct{ net.Listener } + +func (listener) Addr() net.Addr { return nil } + +func TestNetServerNilAddr(t *testing.T) { + const addr = "127.0.0.1:8080" + got := nc.Server(addr, listener{}) + expected := nc.Host(addr) + assert.Equal(t, cap(expected), cap(got), "slice capacity") + assert.ElementsMatch(t, expected, got) +} + +func newTCPListener() (net.Listener, error) { + return net.Listen("tcp4", "127.0.0.1:0") +} + +func TestNetServerTCP(t *testing.T) { + ln, err := newTCPListener() + require.NoError(t, err) + defer func() { require.NoError(t, ln.Close()) }() + + host, pStr, err := net.SplitHostPort(ln.Addr().String()) + require.NoError(t, err) + port, err := strconv.Atoi(pStr) + require.NoError(t, err) + + got := nc.Server("example.com:8080", ln) + expected := []attribute.KeyValue{ + nc.HostName("example.com"), + nc.HostPort(8080), + nc.NetTransportTCP, + nc.NetSockFamilyKey.String("inet"), + nc.NetSockHostAddrKey.String(host), + nc.NetSockHostPortKey.Int(port), + } + assert.Equal(t, cap(expected), cap(got), "slice capacity") + assert.ElementsMatch(t, expected, got) +} + +func TestNetHost(t *testing.T) { + testAddrs(t, []addrTest{ + {address: "", expected: nil}, + {address: "192.0.0.1", expected: []attribute.KeyValue{ + nc.HostName("192.0.0.1"), + }}, + {address: "192.0.0.1:9090", expected: []attribute.KeyValue{ + nc.HostName("192.0.0.1"), + nc.HostPort(9090), + }}, + }, nc.Host) +} + func TestNetHostName(t *testing.T) { expected := attribute.Key("net.host.name").String(addr) assert.Equal(t, expected, nc.HostName(addr)) @@ -71,6 +134,123 @@ func TestNetHostPort(t *testing.T) { assert.Equal(t, expected, nc.HostPort(port)) } +func TestNetClientNilConn(t *testing.T) { + const addr = "127.0.0.1:8080" + got := nc.Client(addr, nil) + expected := nc.Peer(addr) + assert.Equal(t, cap(expected), cap(got), "slice capacity") + assert.ElementsMatch(t, expected, got) +} + +type conn struct{ net.Conn } + +func (conn) LocalAddr() net.Addr { return nil } +func (conn) RemoteAddr() net.Addr { return nil } + +func TestNetClientNilAddr(t *testing.T) { + const addr = "127.0.0.1:8080" + got := nc.Client(addr, conn{}) + expected := nc.Peer(addr) + assert.Equal(t, cap(expected), cap(got), "slice capacity") + assert.ElementsMatch(t, expected, got) +} + +func newTCPConn() (net.Conn, net.Listener, error) { + ln, err := newTCPListener() + if err != nil { + return nil, nil, err + } + + conn, err := net.Dial("tcp4", ln.Addr().String()) + if err != nil { + ln.Close() + return nil, nil, err + } + + return conn, ln, nil +} + +func TestNetClientTCP(t *testing.T) { + conn, ln, err := newTCPConn() + require.NoError(t, err) + defer func() { require.NoError(t, ln.Close()) }() + defer func() { require.NoError(t, conn.Close()) }() + + lHost, pStr, err := net.SplitHostPort(conn.LocalAddr().String()) + require.NoError(t, err) + lPort, err := strconv.Atoi(pStr) + require.NoError(t, err) + + rHost, pStr, err := net.SplitHostPort(conn.RemoteAddr().String()) + require.NoError(t, err) + rPort, err := strconv.Atoi(pStr) + require.NoError(t, err) + + got := nc.Client("example.com:8080", conn) + expected := []attribute.KeyValue{ + nc.PeerName("example.com"), + nc.PeerPort(8080), + nc.NetTransportTCP, + nc.NetSockFamilyKey.String("inet"), + nc.NetSockPeerAddrKey.String(rHost), + nc.NetSockPeerPortKey.Int(rPort), + nc.NetSockHostAddrKey.String(lHost), + nc.NetSockHostPortKey.Int(lPort), + } + assert.Equal(t, cap(expected), cap(got), "slice capacity") + assert.ElementsMatch(t, expected, got) +} + +type remoteOnlyConn struct{ net.Conn } + +func (remoteOnlyConn) LocalAddr() net.Addr { return nil } + +func TestNetClientTCPNilLocal(t *testing.T) { + conn, ln, err := newTCPConn("tcp") + require.NoError(t, err) + defer func() { require.NoError(t, ln.Close()) }() + defer func() { require.NoError(t, conn.Close()) }() + + conn = remoteOnlyConn{conn} + + rHost, pStr, err := net.SplitHostPort(conn.RemoteAddr().String()) + require.NoError(t, err) + rPort, err := strconv.Atoi(pStr) + require.NoError(t, err) + + got := nc.Client("example.com:8080", conn) + expected := []attribute.KeyValue{ + nc.PeerName("example.com"), + nc.PeerPort(8080), + nc.NetTransportTCP, + nc.NetSockFamilyKey.String("inet"), + nc.NetSockPeerAddrKey.String(rHost), + nc.NetSockPeerPortKey.Int(rPort), + } + assert.Equal(t, cap(expected), cap(got), "slice capacity") + assert.ElementsMatch(t, expected, got) +} + +func TestNetPeer(t *testing.T) { + testAddrs(t, []addrTest{ + {address: "", expected: nil}, + {address: "example.com", expected: []attribute.KeyValue{ + nc.PeerName("example.com"), + }}, + {address: "/tmp/file", expected: []attribute.KeyValue{ + nc.PeerName("/tmp/file"), + }}, + {address: "192.0.0.1", expected: []attribute.KeyValue{ + nc.PeerName("192.0.0.1"), + }}, + {address: ":9090", expected: nil}, + {address: "192.0.0.1:9090", expected: []attribute.KeyValue{ + nc.PeerName("192.0.0.1"), + nc.PeerPort(9090), + }}, + }, nc.Peer) +} + func TestNetPeerName(t *testing.T) { expected := attribute.Key("net.peer.name").String(addr) assert.Equal(t, expected, nc.PeerName(addr)) @@ -90,3 +270,75 @@ func TestNetSockPeerPort(t *testing.T) { expected := attribute.Key("net.sock.peer.port").Int(port) assert.Equal(t, expected, nc.SockPeerPort(port)) } + +func TestFamily(t *testing.T) { + tests := []struct { + network string + address string + expect string + }{ + {"", "", ""}, + {"unix", "", "unix"}, + {"unix", "gibberish", "unix"}, + {"unixgram", "", "unix"}, + {"unixgram", "gibberish", "unix"}, + {"unixpacket", "gibberish", "unix"}, + {"tcp", "123.0.2.8", "inet"}, + {"tcp", "gibberish", ""}, + {"", "123.0.2.8", "inet"}, + {"", "gibberish", ""}, + {"tcp", "fe80::1", "inet6"}, + {"", "fe80::1", "inet6"}, + } + + for _, test := range tests { + got := family(test.network, test.address) + assert.Equal(t, test.expect, got, test.network+"/"+test.address) + } +} + +func TestSplitHostPort(t *testing.T) { + tests := []struct { + hostport string + host string + port int + }{ + {"", "", -1}, + {":8080", "", 8080}, + {"127.0.0.1", "127.0.0.1", -1}, + {"www.example.com", "www.example.com", -1}, + {"127.0.0.1%25en0", "127.0.0.1%25en0", -1}, + {"[]", "", -1}, // Ensure this doesn't panic. + {"[fe80::1", "", -1}, + {"[fe80::1]", "fe80::1", -1}, + {"[fe80::1%25en0]", "fe80::1%25en0", -1}, + {"[fe80::1]:8080", "fe80::1", 8080}, + {"[fe80::1]::", "", -1}, // Too many colons. + {"127.0.0.1:", "127.0.0.1", -1}, + {"127.0.0.1:port", "127.0.0.1", -1}, + {"127.0.0.1:8080", "127.0.0.1", 8080}, + {"www.example.com:8080", "www.example.com", 8080}, + {"127.0.0.1%25en0:8080", "127.0.0.1%25en0", 8080}, + } + + for _, test := range tests { + h, p := splitHostPort(test.hostport) + assert.Equal(t, test.host, h, test.hostport) + assert.Equal(t, test.port, p, test.hostport) + } +} + +type addrTest struct { + address string + expected []attribute.KeyValue +} + +func testAddrs(t *testing.T, tests []addrTest, f func(string) []attribute.KeyValue) { + t.Helper() + + for _, test := range tests { + got := f(test.address) + assert.Equal(t, cap(test.expected), cap(got), "slice capacity") + assert.ElementsMatch(t, test.expected, got, test.address) + } +} From 13659a3d9d4c7026be05811e98150e3485f15b33 Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Thu, 1 Dec 2022 12:28:38 -0800 Subject: [PATCH 12/15] Generate Client/Server func for semconv ver --- .../semconvkit/templates/netconv/net.go.tmpl | 23 +++++++++++++++++++ semconv/internal/v2/net.go | 16 ++++++++----- semconv/v1.13.0/netconv/net.go | 23 +++++++++++++++++++ 3 files changed, 56 insertions(+), 6 deletions(-) diff --git a/internal/tools/semconvkit/templates/netconv/net.go.tmpl b/internal/tools/semconvkit/templates/netconv/net.go.tmpl index 11250337232..02b5f923a19 100644 --- a/internal/tools/semconvkit/templates/netconv/net.go.tmpl +++ b/internal/tools/semconvkit/templates/netconv/net.go.tmpl @@ -17,6 +17,8 @@ package netconv // import "go.opentelemetry.io/otel/semconv/{{.TagVer}}/netconv" import ( + "net" + "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/semconv/internal/v2" semconv "go.opentelemetry.io/otel/semconv/{{.TagVer}}" @@ -27,8 +29,11 @@ var nc = &internal.NetConv{ NetHostPortKey: semconv.NetHostPortKey, NetPeerNameKey: semconv.NetPeerNameKey, NetPeerPortKey: semconv.NetPeerPortKey, + NetSockFamilyKey: semconv.NetSockFamilyKey, NetSockPeerAddrKey: semconv.NetSockPeerAddrKey, NetSockPeerPortKey: semconv.NetSockPeerPortKey, + NetSockHostAddrKey: semconv.NetSockHostAddrKey, + NetSockHostPortKey: semconv.NetSockHostPortKey, NetTransportOther: semconv.NetTransportOther, NetTransportTCP: semconv.NetTransportTCP, NetTransportUDP: semconv.NetTransportUDP, @@ -41,3 +46,21 @@ var nc = &internal.NetConv{ func Transport(network string) attribute.KeyValue { return nc.Transport(network) } + +// Client returns attributes for a client network connection to address. See +// net.Dial for information about acceptable address values, address should be +// the same as the one used to create conn. If conn is nil, only network peer +// attributes will be returned that describe address. Otherwise, the socket +// level information about conn will also be included. +func Client(address string, conn net.Conn) []attribute.KeyValue { + return nc.Client(address, conn) +} + +// Server returns attributes for a network listener listening at address. See +// net.Listen for information about acceptable address values, address should +// be the same as the one used to create ln. If ln is nil, only network host +// attributes will be returned that describe address. Otherwise, the socket +// level information about ln will also be included. +func Server(address string, ln net.Listener) []attribute.KeyValue { + return nc.Server(address, ln) +} diff --git a/semconv/internal/v2/net.go b/semconv/internal/v2/net.go index fb761b6a127..4a711133a02 100644 --- a/semconv/internal/v2/net.go +++ b/semconv/internal/v2/net.go @@ -77,15 +77,17 @@ func (c *NetConv) Host(address string) []attribute.KeyValue { return attrs } -// Server returns attributes for a server network listener at address. See +// Server returns attributes for a network listener listening at address. See // net.Listen for information about acceptable address values, address should -// be the same as the one used to create l. -func (c *NetConv) Server(address string, l net.Listener) []attribute.KeyValue { - if l == nil { +// be the same as the one used to create ln. If ln is nil, only network host +// attributes will be returned that describe address. Otherwise, the socket +// level information about ln will also be included. +func (c *NetConv) Server(address string, ln net.Listener) []attribute.KeyValue { + if ln == nil { return c.Host(address) } - lAddr := l.Addr() + lAddr := ln.Addr() if lAddr == nil { return c.Host(address) } @@ -131,7 +133,9 @@ func (c *NetConv) HostPort(port int) attribute.KeyValue { // Client returns attributes for a client network connection to address. See // net.Dial for information about acceptable address values, address should be -// the same as the one used to create conn. +// the same as the one used to create conn. If conn is nil, only network peer +// attributes will be returned that describe address. Otherwise, the socket +// level information about conn will also be included. func (c *NetConv) Client(address string, conn net.Conn) []attribute.KeyValue { if conn == nil { return c.Peer(address) diff --git a/semconv/v1.13.0/netconv/net.go b/semconv/v1.13.0/netconv/net.go index c623e4b4008..6c9c6a500e8 100644 --- a/semconv/v1.13.0/netconv/net.go +++ b/semconv/v1.13.0/netconv/net.go @@ -17,6 +17,8 @@ package netconv // import "go.opentelemetry.io/otel/semconv/v1.13.0/netconv" import ( + "net" + "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/semconv/internal/v2" semconv "go.opentelemetry.io/otel/semconv/v1.13.0" @@ -27,8 +29,11 @@ var nc = &internal.NetConv{ NetHostPortKey: semconv.NetHostPortKey, NetPeerNameKey: semconv.NetPeerNameKey, NetPeerPortKey: semconv.NetPeerPortKey, + NetSockFamilyKey: semconv.NetSockFamilyKey, NetSockPeerAddrKey: semconv.NetSockPeerAddrKey, NetSockPeerPortKey: semconv.NetSockPeerPortKey, + NetSockHostAddrKey: semconv.NetSockHostAddrKey, + NetSockHostPortKey: semconv.NetSockHostPortKey, NetTransportOther: semconv.NetTransportOther, NetTransportTCP: semconv.NetTransportTCP, NetTransportUDP: semconv.NetTransportUDP, @@ -41,3 +46,21 @@ var nc = &internal.NetConv{ func Transport(network string) attribute.KeyValue { return nc.Transport(network) } + +// Client returns attributes for a client network connection to address. See +// net.Dial for information about acceptable address values, address should be +// the same as the one used to create conn. If conn is nil, only network peer +// attributes will be returned that describe address. Otherwise, the socket +// level information about conn will also be included. +func Client(address string, conn net.Conn) []attribute.KeyValue { + return nc.Client(address, conn) +} + +// Server returns attributes for a network listener listening at address. See +// net.Listen for information about acceptable address values, address should +// be the same as the one used to create ln. If ln is nil, only network host +// attributes will be returned that describe address. Otherwise, the socket +// level information about ln will also be included. +func Server(address string, ln net.Listener) []attribute.KeyValue { + return nc.Server(address, ln) +} From 7b418b59c3f5b64eaba6a822cf4c27f1cb29b6b8 Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Thu, 1 Dec 2022 12:31:56 -0800 Subject: [PATCH 13/15] Update RELEASING Add note about compatibility. Update example TAG. --- RELEASING.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/RELEASING.md b/RELEASING.md index b6302ac776e..77d56c93651 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -12,7 +12,7 @@ The `semconv-generate` make target is used for this. For example, ```sh -export TAG="v1.7.0" # Change to the release version you are generating. +export TAG="v1.13.0" # Change to the release version you are generating. export OTEL_SPEC_REPO="/absolute/path/to/opentelemetry-specification" git -C "$OTEL_SPEC_REPO" checkout "tags/$TAG" -b "$TAG" docker pull otel/semconvgen:latest @@ -22,6 +22,9 @@ make semconv-generate # Uses the exported TAG and OTEL_SPEC_REPO. This should create a new sub-package of [`semconv`](./semconv). Ensure things look correct before submitting a pull request to include the addition. +**Note**, the generation code was changed to generate versions >= 1.13. +To generate versions prior to this, checkout the old release of this repository (i.e. [2fe8861](https://github.com/open-telemetry/opentelemetry-go/commit/2fe8861a24e20088c065b116089862caf9e3cd8b)). + ## Pre-Release First, decide which module sets will be released and update their versions From ad586c1bd8b039a6eacfdb8f50754d1654471f64 Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Thu, 1 Dec 2022 12:35:05 -0800 Subject: [PATCH 14/15] Fix errors --- semconv/internal/v2/net_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/semconv/internal/v2/net_test.go b/semconv/internal/v2/net_test.go index a3d03daf927..e1e32469231 100644 --- a/semconv/internal/v2/net_test.go +++ b/semconv/internal/v2/net_test.go @@ -59,8 +59,8 @@ func TestNetTransport(t *testing.T) { "ip6:proto": attribute.String("net.transport", "other"), } - for net, want := range transports { - assert.Equal(t, want, nc.Transport(net)) + for network, want := range transports { + assert.Equal(t, want, nc.Transport(network)) } } @@ -163,7 +163,7 @@ func newTCPConn() (net.Conn, net.Listener, error) { conn, err := net.Dial("tcp4", ln.Addr().String()) if err != nil { - ln.Close() + _ = ln.Close() return nil, nil, err } @@ -206,7 +206,7 @@ type remoteOnlyConn struct{ net.Conn } func (remoteOnlyConn) LocalAddr() net.Addr { return nil } func TestNetClientTCPNilLocal(t *testing.T) { - conn, ln, err := newTCPConn("tcp") + conn, ln, err := newTCPConn() require.NoError(t, err) defer func() { require.NoError(t, ln.Close()) }() defer func() { require.NoError(t, conn.Close()) }() From fd070a5da68363e5015fc3e0d7c70558c1f916b7 Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Thu, 1 Dec 2022 12:38:34 -0800 Subject: [PATCH 15/15] Update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a96cd067a4a..36acda96d74 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,8 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - The `NetAttributesFromHTTPRequest` function in `go.opentelemetry.io/otel/semconv/v1.12.0` is split into `Transport` in `go.opentelemetry.io/otel/semconv/v1.13.0/netconv` and `ClientRequest` or `ServerRequest` in `go.opentelemetry.io/otel/semconv/v1.13.0/httpconv`. - The `SpanStatusFromHTTPStatusCode` function in `go.opentelemetry.io/otel/semconv/v1.12.0` is replaced by `ClientStatus` in `go.opentelemetry.io/otel/semconv/v1.13.0/httpconv`. - The `SpanStatusFromHTTPStatusCodeAndSpanKind` function in `go.opentelemetry.io/otel/semconv/v1.12.0` is split into `ClientStatus` and `ServerStatus` in `go.opentelemetry.io/otel/semconv/v1.13.0/httpconv`. + - The `Client` function is included in `go.opentelemetry.io/otel/semconv/v1.13.0/netconv` to generate attributes for a `net.Conn`. + - The `Server` function is included in `go.opentelemetry.io/otel/semconv/v1.13.0/netconv` to generate attributes for a `net.Listener`. ### Changed