You've already forked opentelemetry-go
mirror of
https://github.com/open-telemetry/opentelemetry-go.git
synced 2025-11-23 22:34:47 +02:00
Add v1.13 semantic conventions (#3499)
* WIP * Add NetConv unit tests * Add ServerRequest unit tests * Unit test ClientRequest * Remove unneeded * Unit test helper funcs * Add unit tests for remaining funcs * Update exported docs * Fix lint * Add changelog entry * Add Client/Server func to semconv/internal/v2 * Generate Client/Server func for semconv ver * Update RELEASING Add note about compatibility. Update example TAG. * Fix errors * Update changelog
This commit is contained in:
12
CHANGELOG.md
12
CHANGELOG.md
@@ -89,6 +89,18 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
|
|||||||
These additions are replacements for the `Instrument` and `InstrumentKind` types from `go.opentelemetry.io/otel/sdk/metric/view`. (#3459)
|
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)
|
- The `Stream` type is added to `go.opentelemetry.io/otel/sdk/metric` to define a metric data stream a view will produce. (#3459)
|
||||||
- The `AssertHasAttributes` allows instrument authors to test that datapoints returned have appropriate attributes. (#3487)
|
- The `AssertHasAttributes` allows instrument authors to test that datapoints returned have appropriate attributes. (#3487)
|
||||||
|
- 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`.
|
||||||
|
- 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
|
### Changed
|
||||||
|
|
||||||
|
|||||||
11
RELEASING.md
11
RELEASING.md
@@ -6,20 +6,25 @@ New versions of the [OpenTelemetry specification] mean new versions of the `semc
|
|||||||
The `semconv-generate` make target is used for this.
|
The `semconv-generate` make target is used for this.
|
||||||
|
|
||||||
1. Checkout a local copy of the [OpenTelemetry specification] to the desired release tag.
|
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,
|
For example,
|
||||||
|
|
||||||
```sh
|
```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"
|
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.
|
make semconv-generate # Uses the exported TAG and OTEL_SPEC_REPO.
|
||||||
```
|
```
|
||||||
|
|
||||||
This should create a new sub-package of [`semconv`](./semconv).
|
This should create a new sub-package of [`semconv`](./semconv).
|
||||||
Ensure things look correct before submitting a pull request to include the addition.
|
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
|
## Pre-Release
|
||||||
|
|
||||||
First, decide which module sets will be released and update their versions
|
First, decide which module sets will be released and update their versions
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"embed"
|
"embed"
|
||||||
"flag"
|
"flag"
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -32,7 +33,7 @@ var (
|
|||||||
out = flag.String("output", "./", "output directory")
|
out = flag.String("output", "./", "output directory")
|
||||||
tag = flag.String("tag", "", "OpenTelemetry tagged version")
|
tag = flag.String("tag", "", "OpenTelemetry tagged version")
|
||||||
|
|
||||||
//go:embed templates/*.tmpl
|
//go:embed templates/*.tmpl templates/netconv/*.tmpl templates/httpconv/*.tmpl
|
||||||
rootFS embed.FS
|
rootFS embed.FS
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -48,8 +49,8 @@ func (sc SemanticConventions) SemVer() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// render renders all templates to the dest directory using the data.
|
// render renders all templates to the dest directory using the data.
|
||||||
func render(dest string, data *SemanticConventions) error {
|
func render(src, dest string, data *SemanticConventions) error {
|
||||||
tmpls, err := template.ParseFS(rootFS, "templates/*.tmpl")
|
tmpls, err := template.ParseFS(rootFS, src)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -78,7 +79,17 @@ func main() {
|
|||||||
|
|
||||||
sc := &SemanticConventions{TagVer: *tag}
|
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)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,101 +14,8 @@
|
|||||||
|
|
||||||
package semconv // import "go.opentelemetry.io/otel/semconv/{{.TagVer}}"
|
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.
|
// HTTP scheme attributes.
|
||||||
var (
|
var (
|
||||||
HTTPSchemeHTTP = HTTPSchemeKey.String("http")
|
HTTPSchemeHTTP = HTTPSchemeKey.String("http")
|
||||||
HTTPSchemeHTTPS = HTTPSchemeKey.String("https")
|
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)
|
|
||||||
}
|
|
||||||
|
|||||||
135
internal/tools/semconvkit/templates/httpconv/http.go.tmpl
Normal file
135
internal/tools/semconvkit/templates/httpconv/http.go.tmpl
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
// 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. 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 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
66
internal/tools/semconvkit/templates/netconv/net.go.tmpl
Normal file
66
internal/tools/semconvkit/templates/netconv/net.go.tmpl
Normal 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 semantic convetions for the net
|
||||||
|
// package from the standard library.
|
||||||
|
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}}"
|
||||||
|
)
|
||||||
|
|
||||||
|
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 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
380
semconv/internal/v2/http.go
Normal file
380
semconv/internal/v2/http.go
Normal file
@@ -0,0 +1,380 @@
|
|||||||
|
// 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/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
|
||||||
|
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 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.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))
|
||||||
|
|
||||||
|
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.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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
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 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))
|
||||||
|
}
|
||||||
|
|
||||||
|
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 { // 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.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 { // 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
|
||||||
|
}
|
||||||
445
semconv/internal/v2/http_test.go
Normal file
445
semconv/internal/v2/http_test.go
Normal file
@@ -0,0 +1,445 @@
|
|||||||
|
// 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"),
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
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 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 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.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))
|
||||||
|
}
|
||||||
|
|
||||||
|
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.target", ""),
|
||||||
|
attribute.String("http.scheme", "http"),
|
||||||
|
attribute.String("http.flavor", ""),
|
||||||
|
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) {
|
||||||
|
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 {
|
||||||
|
expect := attribute.String("http.flavor", want)
|
||||||
|
assert.Equal(t, expect, hc.proto(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 {
|
||||||
|
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/v2/net.go
Normal file
324
semconv/internal/v2/net.go
Normal 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/v2"
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
344
semconv/internal/v2/net_test.go
Normal file
344
semconv/internal/v2/net_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
20
semconv/v1.13.0/doc.go
Normal file
20
semconv/v1.13.0/doc.go
Normal 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.13.0 version of the OpenTelemetry specification.
|
||||||
|
package semconv // import "go.opentelemetry.io/otel/semconv/v1.13.0"
|
||||||
20
semconv/v1.13.0/exception.go
Normal file
20
semconv/v1.13.0/exception.go
Normal 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.13.0"
|
||||||
|
|
||||||
|
const (
|
||||||
|
// ExceptionEventName is the name of the Span event representing an exception.
|
||||||
|
ExceptionEventName = "exception"
|
||||||
|
)
|
||||||
21
semconv/v1.13.0/http.go
Normal file
21
semconv/v1.13.0/http.go
Normal 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.13.0"
|
||||||
|
|
||||||
|
// HTTP scheme attributes.
|
||||||
|
var (
|
||||||
|
HTTPSchemeHTTP = HTTPSchemeKey.String("http")
|
||||||
|
HTTPSchemeHTTPS = HTTPSchemeKey.String("https")
|
||||||
|
)
|
||||||
135
semconv/v1.13.0/httpconv/http.go
Normal file
135
semconv/v1.13.0/httpconv/http.go
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
// 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. 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 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
66
semconv/v1.13.0/netconv/net.go
Normal file
66
semconv/v1.13.0/netconv/net.go
Normal 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 semantic convetions for the net
|
||||||
|
// package from the standard library.
|
||||||
|
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"
|
||||||
|
)
|
||||||
|
|
||||||
|
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 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
1049
semconv/v1.13.0/resource.go
Normal file
1049
semconv/v1.13.0/resource.go
Normal file
File diff suppressed because it is too large
Load Diff
20
semconv/v1.13.0/schema.go
Normal file
20
semconv/v1.13.0/schema.go
Normal 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.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/<version>
|
||||||
|
const SchemaURL = "https://opentelemetry.io/schemas/1.13.0"
|
||||||
1772
semconv/v1.13.0/trace.go
Normal file
1772
semconv/v1.13.0/trace.go
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user