1
0
mirror of https://github.com/open-telemetry/opentelemetry-go.git synced 2025-11-29 23:07:45 +02:00

Add semconv/v1.20.0 (#4078)

* Add semconv/v1.20.0

* Update changelog

* Change http.flavor to net.protocol.(name|version)

* Update comments in httpconv

* Fix vanity import

* Update CHANGELOG.md

Co-authored-by: Tyler Yahn <MrAlias@users.noreply.github.com>

---------

Co-authored-by: Chester Cheung <cheung.zhy.csu@gmail.com>
Co-authored-by: Tyler Yahn <MrAlias@users.noreply.github.com>
This commit is contained in:
Robert Pająk
2023-05-16 20:17:28 +02:00
committed by GitHub
parent fadd3d6a63
commit 8445f21305
18 changed files with 7996 additions and 12 deletions

View File

@@ -8,6 +8,11 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
## [Unreleased]
### Added
- The `go.opentelemetry.io/otel/semconv/v1.20.0` package.
The package contains semantic conventions from the `v1.20.0` version of the OpenTelemetry specification. (#4078)
### Removed
- The deprecated `go.opentelemetry.io/otel/metric/instrument` package is removed.

View File

@@ -14,7 +14,6 @@ For example,
```sh
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
make semconv-generate # Uses the exported TAG and OTEL_SPEC_REPO.
```

View File

@@ -21,7 +21,7 @@ import (
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/semconv/internal/v3"
"go.opentelemetry.io/otel/semconv/internal/v4"
semconv "go.opentelemetry.io/otel/semconv/{{.TagVer}}"
)
@@ -44,7 +44,8 @@ var (
EnduserIDKey: semconv.EnduserIDKey,
HTTPClientIPKey: semconv.HTTPClientIPKey,
HTTPFlavorKey: semconv.HTTPFlavorKey,
NetProtocolNameKey: semconv.NetProtocolNameKey,
NetProtocolVersionKey: semconv.NetProtocolVersionKey,
HTTPMethodKey: semconv.HTTPMethodKey,
HTTPRequestContentLengthKey: semconv.HTTPRequestContentLengthKey,
HTTPResponseContentLengthKey: semconv.HTTPResponseContentLengthKey,
@@ -74,10 +75,11 @@ func ClientResponse(resp *http.Response) []attribute.KeyValue {
}
// ClientRequest returns trace 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".
// The following attributes are always returned: "http.url",
// "net.protocol.(name|version)", "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)
}
@@ -106,10 +108,10 @@ func ClientStatus(code int) (codes.Code, string) {
// The req Host will be used to determine the server instead.
//
// 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", "user_agent.original", "enduser.id",
// "http.client_ip".
// ""net.protocol.(name|version)", "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",
// "user_agent.original", "enduser.id", "http.client_ip".
func ServerRequest(server string, req *http.Request) []attribute.KeyValue {
return hc.ServerRequest(server, req)
}

View File

@@ -20,7 +20,7 @@ import (
"net"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/semconv/internal/v3"
"go.opentelemetry.io/otel/semconv/internal/v4"
semconv "go.opentelemetry.io/otel/semconv/{{.TagVer}}"
)

405
semconv/internal/v4/http.go Normal file
View File

@@ -0,0 +1,405 @@
// 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/v4"
import (
"fmt"
"net/http"
"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
NetProtocolNameKey attribute.Key
NetProtocolVersionKey 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
UserAgentOriginalKey attribute.Key
}
// 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".
//
// 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 {
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 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
if req.URL != nil {
h = req.URL.Host
}
peer, p := firstHostPort(h, req.Header.Get("Host"))
port := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", p)
if port > 0 {
n++
}
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))
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 {
attrs = append(attrs, c.NetConv.PeerPort(port))
}
if useragent != "" {
attrs = append(attrs, c.UserAgentOriginalKey.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
}
// ServerRequest returns attributes for an HTTP request received by a server.
//
// The server must be the primary server name if it is known. For example this
// would be the ServerName directive
// (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache
// server, and the server_name directive
// (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an
// nginx server. More generically, the primary server name would be the host
// header value that matches the default virtual host of an HTTP server. It
// should include the host identifier and if a port is used to route to the
// server that port identifier should be included as an appropriate port
// suffix.
//
// If the primary server name is not known, server should be an empty string.
// The req Host will be used to determine the server instead.
//
// 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(server string, req *http.Request) []attribute.KeyValue {
// TODO: This currently does not add the specification required
// `http.target` attribute. It has too high of a cardinality to safely be
// added. An alternate should be added, or this comment removed, when it is
// addressed by the specification. If it is ultimately decided to continue
// not including the attribute, the HTTPTargetKey field of the HTTPConv
// should be removed as well.
n := 4 // Method, scheme, proto, and host name.
var host string
var p int
if server == "" {
host, p = splitHostPort(req.Host)
} else {
// Prioritize the primary server name.
host, p = splitHostPort(server)
if p < 0 {
_, 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.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.UserAgentOriginalKey.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 { // nolint:revive
if https {
return c.HTTPSchemeHTTPS
}
return c.HTTPSchemeHTTP
}
func (c *HTTPConv) proto(proto string) attribute.KeyValue {
switch proto {
case "HTTP/1.0":
return c.NetProtocolVersionKey.String("1.0")
case "HTTP/1.1":
return c.NetProtocolVersionKey.String("1.1")
case "HTTP/2":
return c.NetProtocolVersionKey.String("2.0")
case "HTTP/3":
return c.NetProtocolVersionKey.String("3.0")
default:
return c.NetProtocolNameKey.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 { // nolint:revive
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
}
// 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
}

View File

@@ -0,0 +1,515 @@
// 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 (
"net/http"
"net/http/httptest"
"net/url"
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
)
var hc = &HTTPConv{
NetConv: nc,
EnduserIDKey: attribute.Key("enduser.id"),
HTTPClientIPKey: attribute.Key("http.client_ip"),
NetProtocolNameKey: attribute.Key("net.protocol.name"),
NetProtocolVersionKey: attribute.Key("net.protocol.version"),
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"),
UserAgentOriginalKey: attribute.Key("user_agent.original"),
}
func TestHTTPClientResponse(t *testing.T) {
const stat, n = 201, 397
resp := &http.Response{
StatusCode: stat,
ContentLength: n,
}
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 TestHTTPSClientRequest(t *testing.T) {
req := &http.Request{
Method: http.MethodGet,
URL: &url.URL{
Scheme: "https",
Host: "127.0.0.1:443",
Path: "/resource",
},
Proto: "HTTP/1.0",
ProtoMajor: 1,
ProtoMinor: 0,
}
assert.Equal(
t,
[]attribute.KeyValue{
attribute.String("http.method", "GET"),
attribute.String("net.protocol.version", "1.0"),
attribute.String("http.url", "https://127.0.0.1:443/resource"),
attribute.String("net.peer.name", "127.0.0.1"),
},
hc.ClientRequest(req),
)
}
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("net.protocol.version", "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("user_agent.original", 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("net.protocol.name", ""),
attribute.String("http.url", ""),
attribute.String("net.peer.name", ""),
}
assert.Equal(t, want, got)
}
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,
[]attribute.KeyValue{
attribute.String("http.method", "GET"),
attribute.String("http.scheme", "http"),
attribute.String("net.protocol.version", "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("user_agent.original", "Go-http-client/1.1"),
attribute.String("enduser.id", user),
attribute.String("http.client_ip", clientIP),
},
hc.ServerRequest("", req))
}
func TestHTTPServerName(t *testing.T) {
req := new(http.Request)
var got []attribute.KeyValue
const (
host = "test.semconv.server"
port = 8080
)
portStr := strconv.Itoa(port)
server := host + ":" + portStr
assert.NotPanics(t, func() { got = hc.ServerRequest(server, req) })
assert.Contains(t, got, attribute.String("net.host.name", host))
assert.Contains(t, got, attribute.Int("net.host.port", port))
req = &http.Request{Host: "alt.host.name:" + portStr}
// The server parameter does not include a port, ServerRequest should use
// the port in the request Host field.
assert.NotPanics(t, func() { got = hc.ServerRequest(host, req) })
assert.Contains(t, got, attribute.String("net.host.name", host))
assert.Contains(t, got, attribute.Int("net.host.port", port))
}
func TestHTTPServerRequestFailsGracefully(t *testing.T) {
req := new(http.Request)
var got []attribute.KeyValue
assert.NotPanics(t, func() { got = hc.ServerRequest("", req) })
want := []attribute.KeyValue{
attribute.String("http.method", "GET"),
attribute.String("http.scheme", "http"),
attribute.String("net.protocol.name", ""),
attribute.String("net.host.name", ""),
}
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) {
testCases := []struct {
in string
want attribute.KeyValue
}{
{
in: "HTTP/1.0",
want: attribute.String("net.protocol.version", "1.0"),
},
{
in: "HTTP/1.1",
want: attribute.String("net.protocol.version", "1.1"),
},
{
in: "HTTP/2",
want: attribute.String("net.protocol.version", "2.0"),
},
{
in: "HTTP/3",
want: attribute.String("net.protocol.version", "3.0"),
},
{
in: "SPDY",
want: attribute.String("net.protocol.name", "SPDY"),
},
{
in: "QUIC",
want: attribute.String("net.protocol.name", "QUIC"),
},
{
in: "other",
want: attribute.String("net.protocol.name", "other"),
},
}
for _, tc := range testCases {
t.Run(tc.in, func(t *testing.T) {
got := hc.proto(tc.in)
assert.Equal(t, tc.want, got)
})
}
}
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 {
got := serverClientIP(test.xForwardedFor)
assert.Equal(t, test.want, got, 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 {
got := requiredHTTPPort(test.https, test.port)
assert.Equal(t, test.want, got, test.https, test.port)
}
}
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"},
}
for _, src := range sources {
h, p := firstHostPort(src...)
assert.Equal(t, host, h, src)
assert.Equal(t, port, p, src)
}
}
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 TestReponseHeader(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.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 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 _, 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 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 _, 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)
}
}
}

324
semconv/internal/v4/net.go Normal file
View File

@@ -0,0 +1,324 @@
// 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/v4"
import (
"net"
"strconv"
"strings"
"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
NetSockFamilyKey attribute.Key
NetSockPeerAddrKey attribute.Key
NetSockPeerPortKey attribute.Key
NetSockHostAddrKey attribute.Key
NetSockHostPortKey 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
}
}
// 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 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 (c *NetConv) Server(address string, ln net.Listener) []attribute.KeyValue {
if ln == nil {
return c.Host(address)
}
lAddr := ln.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)
}
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. 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)
}
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)
}
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)
}
// 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)
}

View File

@@ -0,0 +1,344 @@
// 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 (
"net"
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"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 network, want := range transports {
assert.Equal(t, want, nc.Transport(network))
}
}
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))
}
func TestNetHostPort(t *testing.T) {
expected := attribute.Key("net.host.port").Int(port)
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()
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))
}
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))
}
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)
}
}

File diff suppressed because it is too large Load Diff

20
semconv/v1.20.0/doc.go Normal file
View File

@@ -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.20.0 version of the OpenTelemetry specification.
package semconv // import "go.opentelemetry.io/otel/semconv/v1.20.0"

199
semconv/v1.20.0/event.go Normal file
View File

@@ -0,0 +1,199 @@
// 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.20.0"
import "go.opentelemetry.io/otel/attribute"
// This semantic convention defines the attributes used to represent a feature
// flag evaluation as an event.
const (
// FeatureFlagKeyKey is the attribute Key conforming to the
// "feature_flag.key" semantic conventions. It represents the unique
// identifier of the feature flag.
//
// Type: string
// RequirementLevel: Required
// Stability: stable
// Examples: 'logo-color'
FeatureFlagKeyKey = attribute.Key("feature_flag.key")
// FeatureFlagProviderNameKey is the attribute Key conforming to the
// "feature_flag.provider_name" semantic conventions. It represents the
// name of the service provider that performs the flag evaluation.
//
// Type: string
// RequirementLevel: Recommended
// Stability: stable
// Examples: 'Flag Manager'
FeatureFlagProviderNameKey = attribute.Key("feature_flag.provider_name")
// FeatureFlagVariantKey is the attribute Key conforming to the
// "feature_flag.variant" semantic conventions. It represents the sHOULD be
// a semantic identifier for a value. If one is unavailable, a stringified
// version of the value can be used.
//
// Type: string
// RequirementLevel: Recommended
// Stability: stable
// Examples: 'red', 'true', 'on'
// Note: A semantic identifier, commonly referred to as a variant, provides
// a means
// for referring to a value without including the value itself. This can
// provide additional context for understanding the meaning behind a value.
// For example, the variant `red` maybe be used for the value `#c05543`.
//
// A stringified version of the value can be used in situations where a
// semantic identifier is unavailable. String representation of the value
// should be determined by the implementer.
FeatureFlagVariantKey = attribute.Key("feature_flag.variant")
)
// FeatureFlagKey returns an attribute KeyValue conforming to the
// "feature_flag.key" semantic conventions. It represents the unique identifier
// of the feature flag.
func FeatureFlagKey(val string) attribute.KeyValue {
return FeatureFlagKeyKey.String(val)
}
// FeatureFlagProviderName returns an attribute KeyValue conforming to the
// "feature_flag.provider_name" semantic conventions. It represents the name of
// the service provider that performs the flag evaluation.
func FeatureFlagProviderName(val string) attribute.KeyValue {
return FeatureFlagProviderNameKey.String(val)
}
// FeatureFlagVariant returns an attribute KeyValue conforming to the
// "feature_flag.variant" semantic conventions. It represents the sHOULD be a
// semantic identifier for a value. If one is unavailable, a stringified
// version of the value can be used.
func FeatureFlagVariant(val string) attribute.KeyValue {
return FeatureFlagVariantKey.String(val)
}
// RPC received/sent message.
const (
// MessageTypeKey is the attribute Key conforming to the "message.type"
// semantic conventions. It represents the whether this is a received or
// sent message.
//
// Type: Enum
// RequirementLevel: Optional
// Stability: stable
MessageTypeKey = attribute.Key("message.type")
// MessageIDKey is the attribute Key conforming to the "message.id"
// semantic conventions. It represents the 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")
// MessageCompressedSizeKey is the attribute Key conforming to the
// "message.compressed_size" semantic conventions. It represents the
// compressed size of the message in bytes.
//
// Type: int
// RequirementLevel: Optional
// Stability: stable
MessageCompressedSizeKey = attribute.Key("message.compressed_size")
// MessageUncompressedSizeKey is the attribute Key conforming to the
// "message.uncompressed_size" semantic conventions. It represents the
// 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")
)
// MessageID returns an attribute KeyValue conforming to the "message.id"
// semantic conventions. It represents the mUST be calculated as two different
// counters starting from `1` one for sent messages and one for received
// message.
func MessageID(val int) attribute.KeyValue {
return MessageIDKey.Int(val)
}
// MessageCompressedSize returns an attribute KeyValue conforming to the
// "message.compressed_size" semantic conventions. It represents the compressed
// size of the message in bytes.
func MessageCompressedSize(val int) attribute.KeyValue {
return MessageCompressedSizeKey.Int(val)
}
// MessageUncompressedSize returns an attribute KeyValue conforming to the
// "message.uncompressed_size" semantic conventions. It represents the
// uncompressed size of the message in bytes.
func MessageUncompressedSize(val int) attribute.KeyValue {
return MessageUncompressedSizeKey.Int(val)
}
// The attributes used to report a single exception associated with a span.
const (
// ExceptionEscapedKey is the attribute Key conforming to the
// "exception.escaped" semantic conventions. It represents the 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")
)
// ExceptionEscaped returns an attribute KeyValue conforming to the
// "exception.escaped" semantic conventions. It represents the 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.
func ExceptionEscaped(val bool) attribute.KeyValue {
return ExceptionEscapedKey.Bool(val)
}

View File

@@ -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.20.0"
const (
// ExceptionEventName is the name of the Span event representing an exception.
ExceptionEventName = "exception"
)

21
semconv/v1.20.0/http.go Normal file
View File

@@ -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.20.0"
// HTTP scheme attributes.
var (
HTTPSchemeHTTP = HTTPSchemeKey.String("http")
HTTPSchemeHTTPS = HTTPSchemeKey.String("https")
)

View File

@@ -0,0 +1,154 @@
// 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 HTTP semantic conventions for
// tracing telemetry.
package httpconv // import "go.opentelemetry.io/otel/semconv/v1.20.0/httpconv"
import (
"net/http"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/semconv/internal/v4"
semconv "go.opentelemetry.io/otel/semconv/v1.20.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,
NetProtocolNameKey: semconv.NetProtocolNameKey,
NetProtocolVersionKey: semconv.NetProtocolVersionKey,
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,
UserAgentOriginalKey: semconv.UserAgentOriginalKey,
}
)
// ClientResponse returns trace attributes for an HTTP response received by a
// client 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
// 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 trace attributes for an HTTP request made by a client.
// The following attributes are always returned: "http.url",
// "net.protocol.(name|version)", "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)
}
// 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 hc.ClientStatus(code)
}
// ServerRequest returns trace attributes for an HTTP request received by a
// server.
//
// The server must be the primary server name if it is known. For example this
// would be the ServerName directive
// (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache
// server, and the server_name directive
// (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an
// nginx server. More generically, the primary server name would be the host
// header value that matches the default virtual host of an HTTP server. It
// should include the host identifier and if a port is used to route to the
// server that port identifier should be included as an appropriate port
// suffix.
//
// If the primary server name is not known, server should be an empty string.
// The req Host will be used to determine the server instead.
//
// The following attributes are always returned: "http.method", "http.scheme",
// ""net.protocol.(name|version)", "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",
// "user_agent.original", "enduser.id", "http.client_ip".
func ServerRequest(server string, req *http.Request) []attribute.KeyValue {
return hc.ServerRequest(server, 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 user_agent.original 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 user_agent.original 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)
}

View File

@@ -0,0 +1,66 @@
// 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 network semantic conventions for
// tracing telemetry.
package netconv // import "go.opentelemetry.io/otel/semconv/v1.20.0/netconv"
import (
"net"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/semconv/internal/v3"
semconv "go.opentelemetry.io/otel/semconv/v1.20.0"
)
var nc = &internal.NetConv{
NetHostNameKey: semconv.NetHostNameKey,
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,
NetTransportInProc: semconv.NetTransportInProc,
}
// Transport returns a trace 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)
}
// Client returns trace 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 trace 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)
}

2071
semconv/v1.20.0/resource.go Normal file

File diff suppressed because it is too large Load Diff

20
semconv/v1.20.0/schema.go Normal file
View File

@@ -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.20.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/<version>
const SchemaURL = "https://opentelemetry.io/schemas/1.20.0"

2610
semconv/v1.20.0/trace.go Normal file

File diff suppressed because it is too large Load Diff