You've already forked opentelemetry-go
mirror of
https://github.com/open-telemetry/opentelemetry-go.git
synced 2025-11-25 22:41:46 +02:00
Add semantic conventions generation Make target (#2758)
* No wrap RELEASING Semantic Convention Generation section * Initial generator * Update template render * Add exception and schema templates * Add semconv/internal http unification * Add http template * Add licenses header * Embed the templates * Update static version in schema tmpl * Add semconv-generate target to Makefile Use this target to generate versions of the semconv packages. * Generate semconv packages * Update RELEASING to use make semconv-generate * Add comments to semconvkit * Make SemVer a method instead of a field * Remove semconv/v* from codecov * Fix lint for semconvkit main.go * Fix documentation of validateHTTPStatusCode Co-authored-by: Chester Cheung <cheung.zhy.csu@gmail.com>
This commit is contained in:
15
Makefile
15
Makefile
@@ -47,6 +47,9 @@ $(TOOLS)/semconvgen: PACKAGE=go.opentelemetry.io/build-tools/semconvgen
|
|||||||
CROSSLINK = $(TOOLS)/crosslink
|
CROSSLINK = $(TOOLS)/crosslink
|
||||||
$(TOOLS)/crosslink: PACKAGE=go.opentelemetry.io/otel/$(TOOLS_MOD_DIR)/crosslink
|
$(TOOLS)/crosslink: PACKAGE=go.opentelemetry.io/otel/$(TOOLS_MOD_DIR)/crosslink
|
||||||
|
|
||||||
|
SEMCONVKIT = $(TOOLS)/semconvkit
|
||||||
|
$(TOOLS)/semconvkit: PACKAGE=go.opentelemetry.io/otel/$(TOOLS_MOD_DIR)/semconvkit
|
||||||
|
|
||||||
DBOTCONF = $(TOOLS)/dbotconf
|
DBOTCONF = $(TOOLS)/dbotconf
|
||||||
$(TOOLS)/dbotconf: PACKAGE=go.opentelemetry.io/build-tools/dbotconf
|
$(TOOLS)/dbotconf: PACKAGE=go.opentelemetry.io/build-tools/dbotconf
|
||||||
|
|
||||||
@@ -69,7 +72,7 @@ GOJQ = $(TOOLS)/gojq
|
|||||||
$(TOOLS)/gojq: PACKAGE=github.com/itchyny/gojq/cmd/gojq
|
$(TOOLS)/gojq: PACKAGE=github.com/itchyny/gojq/cmd/gojq
|
||||||
|
|
||||||
.PHONY: tools
|
.PHONY: tools
|
||||||
tools: $(CROSSLINK) $(DBOTCONF) $(GOLANGCI_LINT) $(MISSPELL) $(GOCOVMERGE) $(STRINGER) $(PORTO) $(GOJQ) $(SEMCONVGEN) $(MULTIMOD)
|
tools: $(CROSSLINK) $(DBOTCONF) $(GOLANGCI_LINT) $(MISSPELL) $(GOCOVMERGE) $(STRINGER) $(PORTO) $(GOJQ) $(SEMCONVGEN) $(MULTIMOD) $(SEMCONVKIT)
|
||||||
|
|
||||||
# Build
|
# Build
|
||||||
|
|
||||||
@@ -126,6 +129,7 @@ test-coverage: | $(GOCOVMERGE)
|
|||||||
(cd "$${dir}" && \
|
(cd "$${dir}" && \
|
||||||
$(GO) list ./... \
|
$(GO) list ./... \
|
||||||
| grep -v third_party \
|
| grep -v third_party \
|
||||||
|
| grep -v 'semconv/v.*' \
|
||||||
| xargs $(GO) test -coverpkg=./... -covermode=$(COVERAGE_MODE) -coverprofile="$(COVERAGE_PROFILE)" && \
|
| xargs $(GO) test -coverpkg=./... -covermode=$(COVERAGE_MODE) -coverprofile="$(COVERAGE_PROFILE)" && \
|
||||||
$(GO) tool cover -html=coverage.out -o coverage.html); \
|
$(GO) tool cover -html=coverage.out -o coverage.html); \
|
||||||
done; \
|
done; \
|
||||||
@@ -197,6 +201,15 @@ check-clean-work-tree:
|
|||||||
exit 1; \
|
exit 1; \
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
SEMCONVPKG ?= "semconv/"
|
||||||
|
.PHONY: semconv-generate
|
||||||
|
semconv-generate: | $(SEMCONVGEN) $(SEMCONVKIT)
|
||||||
|
@[ "$(TAG)" ] || ( echo "TAG unset: missing opentelemetry specification tag"; exit 1 )
|
||||||
|
@[ "$(OTEL_SPEC_REPO)" ] || ( echo "OTEL_SPEC_REPO unset: missing path to opentelemetry specification repo"; exit 1 )
|
||||||
|
@$(SEMCONVGEN) -i "$(OTEL_SPEC_REPO)/semantic_conventions/trace" -t "$(SEMCONVPKG)/template.j2" -s "$(TAG)"
|
||||||
|
@$(SEMCONVGEN) -i "$(OTEL_SPEC_REPO)/semantic_conventions/resource" -t "$(SEMCONVPKG)/template.j2" -s "$(TAG)"
|
||||||
|
@$(SEMCONVKIT) -output "$(SEMCONVPKG)/$(TAG)" -tag "$(TAG)"
|
||||||
|
|
||||||
.PHONY: prerelease
|
.PHONY: prerelease
|
||||||
prerelease: | $(MULTIMOD)
|
prerelease: | $(MULTIMOD)
|
||||||
@[ "${MODSET}" ] || ( echo ">> env var MODSET is not set"; exit 1 )
|
@[ "${MODSET}" ] || ( echo ">> env var MODSET is not set"; exit 1 )
|
||||||
|
|||||||
40
RELEASING.md
40
RELEASING.md
@@ -2,35 +2,23 @@
|
|||||||
|
|
||||||
## Semantic Convention Generation
|
## Semantic Convention Generation
|
||||||
|
|
||||||
If a new version of the OpenTelemetry Specification has been released it will be necessary to generate a new
|
New versions of the [OpenTelemetry specification] mean new versions of the `semconv` package need to be generated.
|
||||||
semantic convention package from the YAML definitions in the specification repository. There is a `semconvgen` utility
|
The `semconv-generate` make target is used for this.
|
||||||
installed by `make tools` that can be used to generate the a package with the name matching the specification
|
|
||||||
version number under the `semconv` package. This will ideally be done soon after the specification release is
|
|
||||||
tagged. Make sure that the specification repo contains a checkout of the the latest tagged release so that the
|
|
||||||
generated files match the released semantic conventions.
|
|
||||||
|
|
||||||
There are currently two categories of semantic conventions that must be generated, `resource` and `trace`.
|
1. Checkout a local copy of the [OpenTelemetry specification] to the desired release tag.
|
||||||
|
2. Run the `make semconv-generate ...` target from this repository.
|
||||||
|
|
||||||
```
|
For example,
|
||||||
.tools/semconvgen -i /path/to/specification/repo/semantic_conventions/resource -t semconv/template.j2
|
|
||||||
.tools/semconvgen -i /path/to/specification/repo/semantic_conventions/trace -t semconv/template.j2
|
```sh
|
||||||
|
export TAG="v1.7.0" # Change to the release version you are generating.
|
||||||
|
export OTEL_SPEC_REPO="/absolute/path/to/opentelemetry-specification"
|
||||||
|
cd "$OTEL_SPEC_REPO" && git checkout "tags/$TAG" && cd -
|
||||||
|
make semconv-generate # Uses the exported TAG and OTEL_SPEC_REPO.
|
||||||
```
|
```
|
||||||
|
|
||||||
Using default values for all options other than `input` will result in using the `template.j2` template to
|
This should create a new sub-package of [`semconv`](./semconv).
|
||||||
generate `resource.go` and `trace.go` in `/path/to/otelgo/repo/semconv/<version>`.
|
Ensure things look correct before submitting a pull request to include the addition.
|
||||||
|
|
||||||
There are several ancillary files that are not generated and should be copied into the new package from the
|
|
||||||
prior package, with updates made as appropriate to canonical import path statements and constant values.
|
|
||||||
These files include:
|
|
||||||
|
|
||||||
* doc.go
|
|
||||||
* exception.go
|
|
||||||
* http(_test)?.go
|
|
||||||
* schema.go
|
|
||||||
|
|
||||||
Uses of the previous schema version in this repository should be updated to use the newly generated version.
|
|
||||||
No tooling for this exists at present, so use find/replace in your editor of choice or craft a `grep | sed`
|
|
||||||
pipeline if you like living on the edge.
|
|
||||||
|
|
||||||
## Pre-Release
|
## Pre-Release
|
||||||
|
|
||||||
@@ -130,3 +118,5 @@ Once verified be sure to [make a release for the `contrib` repository](https://g
|
|||||||
|
|
||||||
Update [the documentation](./website_docs) for [the OpenTelemetry website](https://opentelemetry.io/docs/go/).
|
Update [the documentation](./website_docs) for [the OpenTelemetry website](https://opentelemetry.io/docs/go/).
|
||||||
Importantly, bump any package versions referenced to be the latest one you just released and ensure all code examples still compile and are accurate.
|
Importantly, bump any package versions referenced to be the latest one you just released and ensure all code examples still compile and are accurate.
|
||||||
|
|
||||||
|
[OpenTelemetry specification]: https://github.com/open-telemetry/opentelemetry-specification
|
||||||
|
|||||||
84
internal/tools/semconvkit/main.go
Normal file
84
internal/tools/semconvkit/main.go
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
// 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 semconvkit is used to generate opentelemetry-go specific semantic
|
||||||
|
// convention code. It is expected to be used in with the semconvgen utility
|
||||||
|
// (go.opentelemetry.io/build-tools/semconvgen) to completely generate
|
||||||
|
// versioned sub-packages of go.opentelemetry.io/otel/semconv.
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"embed"
|
||||||
|
"flag"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
out = flag.String("output", "./", "output directory")
|
||||||
|
tag = flag.String("tag", "", "OpenTelemetry tagged version")
|
||||||
|
|
||||||
|
//go:embed templates/*.tmpl
|
||||||
|
rootFS embed.FS
|
||||||
|
)
|
||||||
|
|
||||||
|
// SemanticConventions are information about the semantic conventions being
|
||||||
|
// generated.
|
||||||
|
type SemanticConventions struct {
|
||||||
|
// TagVer is the tagged version (i.e. v1.7.0 and not 1.7.0).
|
||||||
|
TagVer string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sc SemanticConventions) SemVer() string {
|
||||||
|
return strings.TrimPrefix(*tag, "v")
|
||||||
|
}
|
||||||
|
|
||||||
|
// render renders all templates to the dest directory using the data.
|
||||||
|
func render(dest string, data *SemanticConventions) error {
|
||||||
|
tmpls, err := template.ParseFS(rootFS, "templates/*.tmpl")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, tmpl := range tmpls.Templates() {
|
||||||
|
target := filepath.Join(dest, strings.TrimSuffix(tmpl.Name(), ".tmpl"))
|
||||||
|
wr, err := os.Create(target)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = tmpl.Execute(wr, data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
if *tag == "" {
|
||||||
|
log.Fatalf("invalid tag: %q", *tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
sc := &SemanticConventions{TagVer: *tag}
|
||||||
|
|
||||||
|
if err := render(*out, sc); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
20
internal/tools/semconvkit/templates/doc.go.tmpl
Normal file
20
internal/tools/semconvkit/templates/doc.go.tmpl
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 {{.TagVer}} version of the OpenTelemetry specification.
|
||||||
|
package semconv // import "go.opentelemetry.io/otel/semconv/{{.TagVer}}"
|
||||||
20
internal/tools/semconvkit/templates/exception.go.tmpl
Normal file
20
internal/tools/semconvkit/templates/exception.go.tmpl
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/{{.TagVer}}"
|
||||||
|
|
||||||
|
const (
|
||||||
|
// ExceptionEventName is the name of the Span event representing an exception.
|
||||||
|
ExceptionEventName = "exception"
|
||||||
|
)
|
||||||
114
internal/tools/semconvkit/templates/http.go.tmpl
Normal file
114
internal/tools/semconvkit/templates/http.go.tmpl
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
// 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/{{.TagVer}}"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"go.opentelemetry.io/otel/attribute"
|
||||||
|
"go.opentelemetry.io/otel/codes"
|
||||||
|
"go.opentelemetry.io/otel/semconv/internal"
|
||||||
|
"go.opentelemetry.io/otel/trace"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HTTP scheme attributes.
|
||||||
|
var (
|
||||||
|
HTTPSchemeHTTP = HTTPSchemeKey.String("http")
|
||||||
|
HTTPSchemeHTTPS = HTTPSchemeKey.String("https")
|
||||||
|
)
|
||||||
|
|
||||||
|
var sc = &internal.SemanticConventions{
|
||||||
|
EnduserIDKey: EnduserIDKey,
|
||||||
|
HTTPClientIPKey: HTTPClientIPKey,
|
||||||
|
HTTPFlavorKey: HTTPFlavorKey,
|
||||||
|
HTTPHostKey: HTTPHostKey,
|
||||||
|
HTTPMethodKey: HTTPMethodKey,
|
||||||
|
HTTPRequestContentLengthKey: HTTPRequestContentLengthKey,
|
||||||
|
HTTPRouteKey: HTTPRouteKey,
|
||||||
|
HTTPSchemeHTTP: HTTPSchemeHTTP,
|
||||||
|
HTTPSchemeHTTPS: HTTPSchemeHTTPS,
|
||||||
|
HTTPServerNameKey: HTTPServerNameKey,
|
||||||
|
HTTPStatusCodeKey: HTTPStatusCodeKey,
|
||||||
|
HTTPTargetKey: HTTPTargetKey,
|
||||||
|
HTTPURLKey: HTTPURLKey,
|
||||||
|
HTTPUserAgentKey: HTTPUserAgentKey,
|
||||||
|
NetHostIPKey: NetHostIPKey,
|
||||||
|
NetHostNameKey: NetHostNameKey,
|
||||||
|
NetHostPortKey: NetHostPortKey,
|
||||||
|
NetPeerIPKey: NetPeerIPKey,
|
||||||
|
NetPeerNameKey: NetPeerNameKey,
|
||||||
|
NetPeerPortKey: NetPeerPortKey,
|
||||||
|
NetTransportIP: NetTransportIP,
|
||||||
|
NetTransportOther: NetTransportOther,
|
||||||
|
NetTransportTCP: NetTransportTCP,
|
||||||
|
NetTransportUDP: NetTransportUDP,
|
||||||
|
NetTransportUnix: NetTransportUnix,
|
||||||
|
}
|
||||||
|
|
||||||
|
// NetAttributesFromHTTPRequest generates attributes of the net
|
||||||
|
// namespace as specified by the OpenTelemetry specification for a
|
||||||
|
// span. The network parameter is a string that net.Dial function
|
||||||
|
// from standard library can understand.
|
||||||
|
func NetAttributesFromHTTPRequest(network string, request *http.Request) []attribute.KeyValue {
|
||||||
|
return sc.NetAttributesFromHTTPRequest(network, request)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EndUserAttributesFromHTTPRequest generates attributes of the
|
||||||
|
// enduser namespace as specified by the OpenTelemetry specification
|
||||||
|
// for a span.
|
||||||
|
func EndUserAttributesFromHTTPRequest(request *http.Request) []attribute.KeyValue {
|
||||||
|
return sc.EndUserAttributesFromHTTPRequest(request)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTTPClientAttributesFromHTTPRequest generates attributes of the
|
||||||
|
// http namespace as specified by the OpenTelemetry specification for
|
||||||
|
// a span on the client side.
|
||||||
|
func HTTPClientAttributesFromHTTPRequest(request *http.Request) []attribute.KeyValue {
|
||||||
|
return sc.HTTPClientAttributesFromHTTPRequest(request)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTTPServerMetricAttributesFromHTTPRequest generates low-cardinality attributes
|
||||||
|
// to be used with server-side HTTP metrics.
|
||||||
|
func HTTPServerMetricAttributesFromHTTPRequest(serverName string, request *http.Request) []attribute.KeyValue {
|
||||||
|
return sc.HTTPServerMetricAttributesFromHTTPRequest(serverName, request)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTTPServerAttributesFromHTTPRequest generates attributes of the
|
||||||
|
// http namespace as specified by the OpenTelemetry specification for
|
||||||
|
// a span on the server side. Currently, only basic authentication is
|
||||||
|
// supported.
|
||||||
|
func HTTPServerAttributesFromHTTPRequest(serverName, route string, request *http.Request) []attribute.KeyValue {
|
||||||
|
return sc.HTTPServerAttributesFromHTTPRequest(serverName, route, request)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTTPAttributesFromHTTPStatusCode generates attributes of the http
|
||||||
|
// namespace as specified by the OpenTelemetry specification for a
|
||||||
|
// span.
|
||||||
|
func HTTPAttributesFromHTTPStatusCode(code int) []attribute.KeyValue {
|
||||||
|
return sc.HTTPAttributesFromHTTPStatusCode(code)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SpanStatusFromHTTPStatusCode generates a status code and a message
|
||||||
|
// as specified by the OpenTelemetry specification for a span.
|
||||||
|
func SpanStatusFromHTTPStatusCode(code int) (codes.Code, string) {
|
||||||
|
return internal.SpanStatusFromHTTPStatusCode(code)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SpanStatusFromHTTPStatusCodeAndSpanKind generates a status code and a message
|
||||||
|
// as specified by the OpenTelemetry specification for a span.
|
||||||
|
// Exclude 4xx for SERVER to set the appropriate status.
|
||||||
|
func SpanStatusFromHTTPStatusCodeAndSpanKind(code int, spanKind trace.SpanKind) (codes.Code, string) {
|
||||||
|
return internal.SpanStatusFromHTTPStatusCodeAndSpanKind(code, spanKind)
|
||||||
|
}
|
||||||
20
internal/tools/semconvkit/templates/schema.go.tmpl
Normal file
20
internal/tools/semconvkit/templates/schema.go.tmpl
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/{{.TagVer}}"
|
||||||
|
|
||||||
|
// 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/{{.SemVer}}"
|
||||||
337
semconv/internal/http.go
Normal file
337
semconv/internal/http.go
Normal file
@@ -0,0 +1,337 @@
|
|||||||
|
// 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"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"go.opentelemetry.io/otel/attribute"
|
||||||
|
"go.opentelemetry.io/otel/codes"
|
||||||
|
"go.opentelemetry.io/otel/trace"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SemanticConventions are the semantic convention values defined for a
|
||||||
|
// version of the OpenTelemetry specification.
|
||||||
|
type SemanticConventions struct {
|
||||||
|
EnduserIDKey attribute.Key
|
||||||
|
HTTPClientIPKey attribute.Key
|
||||||
|
HTTPFlavorKey attribute.Key
|
||||||
|
HTTPHostKey attribute.Key
|
||||||
|
HTTPMethodKey attribute.Key
|
||||||
|
HTTPRequestContentLengthKey attribute.Key
|
||||||
|
HTTPRouteKey attribute.Key
|
||||||
|
HTTPSchemeHTTP attribute.KeyValue
|
||||||
|
HTTPSchemeHTTPS attribute.KeyValue
|
||||||
|
HTTPServerNameKey attribute.Key
|
||||||
|
HTTPStatusCodeKey attribute.Key
|
||||||
|
HTTPTargetKey attribute.Key
|
||||||
|
HTTPURLKey attribute.Key
|
||||||
|
HTTPUserAgentKey attribute.Key
|
||||||
|
NetHostIPKey attribute.Key
|
||||||
|
NetHostNameKey attribute.Key
|
||||||
|
NetHostPortKey attribute.Key
|
||||||
|
NetPeerIPKey attribute.Key
|
||||||
|
NetPeerNameKey attribute.Key
|
||||||
|
NetPeerPortKey attribute.Key
|
||||||
|
NetTransportIP attribute.KeyValue
|
||||||
|
NetTransportOther attribute.KeyValue
|
||||||
|
NetTransportTCP attribute.KeyValue
|
||||||
|
NetTransportUDP attribute.KeyValue
|
||||||
|
NetTransportUnix attribute.KeyValue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 (sc *SemanticConventions) NetAttributesFromHTTPRequest(network string, request *http.Request) []attribute.KeyValue {
|
||||||
|
attrs := []attribute.KeyValue{}
|
||||||
|
|
||||||
|
switch network {
|
||||||
|
case "tcp", "tcp4", "tcp6":
|
||||||
|
attrs = append(attrs, sc.NetTransportTCP)
|
||||||
|
case "udp", "udp4", "udp6":
|
||||||
|
attrs = append(attrs, sc.NetTransportUDP)
|
||||||
|
case "ip", "ip4", "ip6":
|
||||||
|
attrs = append(attrs, sc.NetTransportIP)
|
||||||
|
case "unix", "unixgram", "unixpacket":
|
||||||
|
attrs = append(attrs, sc.NetTransportUnix)
|
||||||
|
default:
|
||||||
|
attrs = append(attrs, sc.NetTransportOther)
|
||||||
|
}
|
||||||
|
|
||||||
|
peerIP, peerName, peerPort := hostIPNamePort(request.RemoteAddr)
|
||||||
|
if peerIP != "" {
|
||||||
|
attrs = append(attrs, sc.NetPeerIPKey.String(peerIP))
|
||||||
|
}
|
||||||
|
if peerName != "" {
|
||||||
|
attrs = append(attrs, sc.NetPeerNameKey.String(peerName))
|
||||||
|
}
|
||||||
|
if peerPort != 0 {
|
||||||
|
attrs = append(attrs, sc.NetPeerPortKey.Int(peerPort))
|
||||||
|
}
|
||||||
|
|
||||||
|
hostIP, hostName, hostPort := "", "", 0
|
||||||
|
for _, someHost := range []string{request.Host, request.Header.Get("Host"), request.URL.Host} {
|
||||||
|
hostIP, hostName, hostPort = hostIPNamePort(someHost)
|
||||||
|
if hostIP != "" || hostName != "" || hostPort != 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if hostIP != "" {
|
||||||
|
attrs = append(attrs, sc.NetHostIPKey.String(hostIP))
|
||||||
|
}
|
||||||
|
if hostName != "" {
|
||||||
|
attrs = append(attrs, sc.NetHostNameKey.String(hostName))
|
||||||
|
}
|
||||||
|
if hostPort != 0 {
|
||||||
|
attrs = append(attrs, sc.NetHostPortKey.Int(hostPort))
|
||||||
|
}
|
||||||
|
|
||||||
|
return attrs
|
||||||
|
}
|
||||||
|
|
||||||
|
// hostIPNamePort extracts the IP address, name and (optional) port from hostWithPort.
|
||||||
|
// It handles both IPv4 and IPv6 addresses. If the host portion is not recognized
|
||||||
|
// as a valid IPv4 or IPv6 address, the `ip` result will be empty and the
|
||||||
|
// host portion will instead be returned in `name`.
|
||||||
|
func hostIPNamePort(hostWithPort string) (ip string, name string, port int) {
|
||||||
|
var (
|
||||||
|
hostPart, portPart string
|
||||||
|
parsedPort uint64
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if hostPart, portPart, err = net.SplitHostPort(hostWithPort); err != nil {
|
||||||
|
hostPart, portPart = hostWithPort, ""
|
||||||
|
}
|
||||||
|
if parsedIP := net.ParseIP(hostPart); parsedIP != nil {
|
||||||
|
ip = parsedIP.String()
|
||||||
|
} else {
|
||||||
|
name = hostPart
|
||||||
|
}
|
||||||
|
if parsedPort, err = strconv.ParseUint(portPart, 10, 16); err == nil {
|
||||||
|
port = int(parsedPort)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// EndUserAttributesFromHTTPRequest generates attributes of the
|
||||||
|
// enduser namespace as specified by the OpenTelemetry specification
|
||||||
|
// for a span.
|
||||||
|
func (sc *SemanticConventions) EndUserAttributesFromHTTPRequest(request *http.Request) []attribute.KeyValue {
|
||||||
|
if username, _, ok := request.BasicAuth(); ok {
|
||||||
|
return []attribute.KeyValue{sc.EnduserIDKey.String(username)}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTTPClientAttributesFromHTTPRequest generates attributes of the
|
||||||
|
// http namespace as specified by the OpenTelemetry specification for
|
||||||
|
// a span on the client side.
|
||||||
|
func (sc *SemanticConventions) HTTPClientAttributesFromHTTPRequest(request *http.Request) []attribute.KeyValue {
|
||||||
|
attrs := []attribute.KeyValue{}
|
||||||
|
|
||||||
|
if request.Method != "" {
|
||||||
|
attrs = append(attrs, sc.HTTPMethodKey.String(request.Method))
|
||||||
|
} else {
|
||||||
|
attrs = append(attrs, sc.HTTPMethodKey.String(http.MethodGet))
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove any username/password info that may be in the URL
|
||||||
|
// before adding it to the attributes
|
||||||
|
userinfo := request.URL.User
|
||||||
|
request.URL.User = nil
|
||||||
|
|
||||||
|
attrs = append(attrs, sc.HTTPURLKey.String(request.URL.String()))
|
||||||
|
|
||||||
|
// restore any username/password info that was removed
|
||||||
|
request.URL.User = userinfo
|
||||||
|
|
||||||
|
return append(attrs, sc.httpCommonAttributesFromHTTPRequest(request)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sc *SemanticConventions) httpCommonAttributesFromHTTPRequest(request *http.Request) []attribute.KeyValue {
|
||||||
|
attrs := []attribute.KeyValue{}
|
||||||
|
if ua := request.UserAgent(); ua != "" {
|
||||||
|
attrs = append(attrs, sc.HTTPUserAgentKey.String(ua))
|
||||||
|
}
|
||||||
|
if request.ContentLength > 0 {
|
||||||
|
attrs = append(attrs, sc.HTTPRequestContentLengthKey.Int64(request.ContentLength))
|
||||||
|
}
|
||||||
|
|
||||||
|
return append(attrs, sc.httpBasicAttributesFromHTTPRequest(request)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sc *SemanticConventions) httpBasicAttributesFromHTTPRequest(request *http.Request) []attribute.KeyValue {
|
||||||
|
// as these attributes are used by HTTPServerMetricAttributesFromHTTPRequest, they should be low-cardinality
|
||||||
|
attrs := []attribute.KeyValue{}
|
||||||
|
|
||||||
|
if request.TLS != nil {
|
||||||
|
attrs = append(attrs, sc.HTTPSchemeHTTPS)
|
||||||
|
} else {
|
||||||
|
attrs = append(attrs, sc.HTTPSchemeHTTP)
|
||||||
|
}
|
||||||
|
|
||||||
|
if request.Host != "" {
|
||||||
|
attrs = append(attrs, sc.HTTPHostKey.String(request.Host))
|
||||||
|
} else if request.URL != nil && request.URL.Host != "" {
|
||||||
|
attrs = append(attrs, sc.HTTPHostKey.String(request.URL.Host))
|
||||||
|
}
|
||||||
|
|
||||||
|
flavor := ""
|
||||||
|
if request.ProtoMajor == 1 {
|
||||||
|
flavor = fmt.Sprintf("1.%d", request.ProtoMinor)
|
||||||
|
} else if request.ProtoMajor == 2 {
|
||||||
|
flavor = "2"
|
||||||
|
}
|
||||||
|
if flavor != "" {
|
||||||
|
attrs = append(attrs, sc.HTTPFlavorKey.String(flavor))
|
||||||
|
}
|
||||||
|
|
||||||
|
return attrs
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTTPServerMetricAttributesFromHTTPRequest generates low-cardinality attributes
|
||||||
|
// to be used with server-side HTTP metrics.
|
||||||
|
func (sc *SemanticConventions) HTTPServerMetricAttributesFromHTTPRequest(serverName string, request *http.Request) []attribute.KeyValue {
|
||||||
|
attrs := []attribute.KeyValue{}
|
||||||
|
if serverName != "" {
|
||||||
|
attrs = append(attrs, sc.HTTPServerNameKey.String(serverName))
|
||||||
|
}
|
||||||
|
return append(attrs, sc.httpBasicAttributesFromHTTPRequest(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 (sc *SemanticConventions) HTTPServerAttributesFromHTTPRequest(serverName, route string, request *http.Request) []attribute.KeyValue {
|
||||||
|
attrs := []attribute.KeyValue{
|
||||||
|
sc.HTTPMethodKey.String(request.Method),
|
||||||
|
sc.HTTPTargetKey.String(request.RequestURI),
|
||||||
|
}
|
||||||
|
|
||||||
|
if serverName != "" {
|
||||||
|
attrs = append(attrs, sc.HTTPServerNameKey.String(serverName))
|
||||||
|
}
|
||||||
|
if route != "" {
|
||||||
|
attrs = append(attrs, sc.HTTPRouteKey.String(route))
|
||||||
|
}
|
||||||
|
if values, ok := request.Header["X-Forwarded-For"]; ok && len(values) > 0 {
|
||||||
|
if addresses := strings.SplitN(values[0], ",", 2); len(addresses) > 0 {
|
||||||
|
attrs = append(attrs, sc.HTTPClientIPKey.String(addresses[0]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return append(attrs, sc.httpCommonAttributesFromHTTPRequest(request)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTTPAttributesFromHTTPStatusCode generates attributes of the http
|
||||||
|
// namespace as specified by the OpenTelemetry specification for a
|
||||||
|
// span.
|
||||||
|
func (sc *SemanticConventions) HTTPAttributesFromHTTPStatusCode(code int) []attribute.KeyValue {
|
||||||
|
attrs := []attribute.KeyValue{
|
||||||
|
sc.HTTPStatusCodeKey.Int(code),
|
||||||
|
}
|
||||||
|
return attrs
|
||||||
|
}
|
||||||
|
|
||||||
|
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},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// SpanStatusFromHTTPStatusCode generates a status code and a message
|
||||||
|
// as specified by the OpenTelemetry specification for a span.
|
||||||
|
func SpanStatusFromHTTPStatusCode(code int) (codes.Code, string) {
|
||||||
|
spanCode, valid := validateHTTPStatusCode(code)
|
||||||
|
if !valid {
|
||||||
|
return spanCode, fmt.Sprintf("Invalid HTTP status code %d", code)
|
||||||
|
}
|
||||||
|
return spanCode, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
spanCode, valid := validateHTTPStatusCode(code)
|
||||||
|
if !valid {
|
||||||
|
return spanCode, fmt.Sprintf("Invalid HTTP status code %d", code)
|
||||||
|
}
|
||||||
|
category := code / 100
|
||||||
|
if spanKind == trace.SpanKindServer && category == 4 {
|
||||||
|
return codes.Unset, ""
|
||||||
|
}
|
||||||
|
return spanCode, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package semconv
|
package internal
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
@@ -36,6 +36,34 @@ const (
|
|||||||
withTLS
|
withTLS
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var sc = &SemanticConventions{
|
||||||
|
EnduserIDKey: attribute.Key("enduser.id"),
|
||||||
|
HTTPClientIPKey: attribute.Key("http.client_ip"),
|
||||||
|
HTTPFlavorKey: attribute.Key("http.flavor"),
|
||||||
|
HTTPHostKey: attribute.Key("http.host"),
|
||||||
|
HTTPMethodKey: attribute.Key("http.method"),
|
||||||
|
HTTPRequestContentLengthKey: attribute.Key("http.request_content_length"),
|
||||||
|
HTTPRouteKey: attribute.Key("http.route"),
|
||||||
|
HTTPSchemeHTTP: attribute.String("http.scheme", "http"),
|
||||||
|
HTTPSchemeHTTPS: attribute.String("http.scheme", "https"),
|
||||||
|
HTTPServerNameKey: attribute.Key("http.server_name"),
|
||||||
|
HTTPStatusCodeKey: attribute.Key("http.status_code"),
|
||||||
|
HTTPTargetKey: attribute.Key("http.target"),
|
||||||
|
HTTPURLKey: attribute.Key("http.url"),
|
||||||
|
HTTPUserAgentKey: attribute.Key("http.user_agent"),
|
||||||
|
NetHostIPKey: attribute.Key("net.host.ip"),
|
||||||
|
NetHostNameKey: attribute.Key("net.host.name"),
|
||||||
|
NetHostPortKey: attribute.Key("net.host.port"),
|
||||||
|
NetPeerIPKey: attribute.Key("net.peer.ip"),
|
||||||
|
NetPeerNameKey: attribute.Key("net.peer.name"),
|
||||||
|
NetPeerPortKey: attribute.Key("net.peer.port"),
|
||||||
|
NetTransportIP: attribute.String("net.transport", "ip"),
|
||||||
|
NetTransportOther: attribute.String("net.transport", "other"),
|
||||||
|
NetTransportTCP: attribute.String("net.transport", "ip_tcp"),
|
||||||
|
NetTransportUDP: attribute.String("net.transport", "ip_udp"),
|
||||||
|
NetTransportUnix: attribute.String("net.transport", "unix"),
|
||||||
|
}
|
||||||
|
|
||||||
func TestNetAttributesFromHTTPRequest(t *testing.T) {
|
func TestNetAttributesFromHTTPRequest(t *testing.T) {
|
||||||
type testcase struct {
|
type testcase struct {
|
||||||
name string
|
name string
|
||||||
@@ -551,7 +579,7 @@ func TestNetAttributesFromHTTPRequest(t *testing.T) {
|
|||||||
for _, tc := range testcases {
|
for _, tc := range testcases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
r := testRequest(tc.method, tc.requestURI, tc.proto, tc.remoteAddr, tc.host, tc.url, tc.header, noTLS)
|
r := testRequest(tc.method, tc.requestURI, tc.proto, tc.remoteAddr, tc.host, tc.url, tc.header, noTLS)
|
||||||
got := NetAttributesFromHTTPRequest(tc.network, r)
|
got := sc.NetAttributesFromHTTPRequest(tc.network, r)
|
||||||
if diff := cmp.Diff(
|
if diff := cmp.Diff(
|
||||||
tc.expected,
|
tc.expected,
|
||||||
got,
|
got,
|
||||||
@@ -565,11 +593,11 @@ func TestNetAttributesFromHTTPRequest(t *testing.T) {
|
|||||||
func TestEndUserAttributesFromHTTPRequest(t *testing.T) {
|
func TestEndUserAttributesFromHTTPRequest(t *testing.T) {
|
||||||
r := testRequest("GET", "/user/123", "HTTP/1.1", "", "", nil, http.Header{}, withTLS)
|
r := testRequest("GET", "/user/123", "HTTP/1.1", "", "", nil, http.Header{}, withTLS)
|
||||||
var expected []attribute.KeyValue
|
var expected []attribute.KeyValue
|
||||||
got := EndUserAttributesFromHTTPRequest(r)
|
got := sc.EndUserAttributesFromHTTPRequest(r)
|
||||||
assert.ElementsMatch(t, expected, got)
|
assert.ElementsMatch(t, expected, got)
|
||||||
r.SetBasicAuth("admin", "password")
|
r.SetBasicAuth("admin", "password")
|
||||||
expected = []attribute.KeyValue{attribute.String("enduser.id", "admin")}
|
expected = []attribute.KeyValue{attribute.String("enduser.id", "admin")}
|
||||||
got = EndUserAttributesFromHTTPRequest(r)
|
got = sc.EndUserAttributesFromHTTPRequest(r)
|
||||||
assert.ElementsMatch(t, expected, got)
|
assert.ElementsMatch(t, expected, got)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -860,7 +888,7 @@ func TestHTTPServerAttributesFromHTTPRequest(t *testing.T) {
|
|||||||
for idx, tc := range testcases {
|
for idx, tc := range testcases {
|
||||||
r := testRequest(tc.method, tc.requestURI, tc.proto, tc.remoteAddr, tc.host, tc.url, tc.header, tc.tls)
|
r := testRequest(tc.method, tc.requestURI, tc.proto, tc.remoteAddr, tc.host, tc.url, tc.header, tc.tls)
|
||||||
r.ContentLength = tc.contentLength
|
r.ContentLength = tc.contentLength
|
||||||
got := HTTPServerAttributesFromHTTPRequest(tc.serverName, tc.route, r)
|
got := sc.HTTPServerAttributesFromHTTPRequest(tc.serverName, tc.route, r)
|
||||||
assertElementsMatch(t, tc.expected, got, "testcase %d - %s", idx, tc.name)
|
assertElementsMatch(t, tc.expected, got, "testcase %d - %s", idx, tc.name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -869,13 +897,13 @@ func TestHTTPAttributesFromHTTPStatusCode(t *testing.T) {
|
|||||||
expected := []attribute.KeyValue{
|
expected := []attribute.KeyValue{
|
||||||
attribute.Int("http.status_code", 404),
|
attribute.Int("http.status_code", 404),
|
||||||
}
|
}
|
||||||
got := HTTPAttributesFromHTTPStatusCode(http.StatusNotFound)
|
got := sc.HTTPAttributesFromHTTPStatusCode(http.StatusNotFound)
|
||||||
assertElementsMatch(t, expected, got, "with valid HTTP status code")
|
assertElementsMatch(t, expected, got, "with valid HTTP status code")
|
||||||
assert.ElementsMatch(t, expected, got)
|
assert.ElementsMatch(t, expected, got)
|
||||||
expected = []attribute.KeyValue{
|
expected = []attribute.KeyValue{
|
||||||
attribute.Int("http.status_code", 499),
|
attribute.Int("http.status_code", 499),
|
||||||
}
|
}
|
||||||
got = HTTPAttributesFromHTTPStatusCode(499)
|
got = sc.HTTPAttributesFromHTTPStatusCode(499)
|
||||||
assertElementsMatch(t, expected, got, "with invalid HTTP status code")
|
assertElementsMatch(t, expected, got, "with invalid HTTP status code")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1203,7 +1231,7 @@ func TestHTTPClientAttributesFromHTTPRequest(t *testing.T) {
|
|||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
r := testRequest(tc.method, tc.requestURI, tc.proto, tc.remoteAddr, tc.host, tc.url, tc.header, tc.tls)
|
r := testRequest(tc.method, tc.requestURI, tc.proto, tc.remoteAddr, tc.host, tc.url, tc.header, tc.tls)
|
||||||
r.ContentLength = tc.contentLength
|
r.ContentLength = tc.contentLength
|
||||||
got := HTTPClientAttributesFromHTTPRequest(r)
|
got := sc.HTTPClientAttributesFromHTTPRequest(r)
|
||||||
assert.ElementsMatch(t, tc.expected, got)
|
assert.ElementsMatch(t, tc.expected, got)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -15,14 +15,11 @@
|
|||||||
package semconv // import "go.opentelemetry.io/otel/semconv/v1.4.0"
|
package semconv // import "go.opentelemetry.io/otel/semconv/v1.4.0"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"go.opentelemetry.io/otel/attribute"
|
"go.opentelemetry.io/otel/attribute"
|
||||||
"go.opentelemetry.io/otel/codes"
|
"go.opentelemetry.io/otel/codes"
|
||||||
|
"go.opentelemetry.io/otel/semconv/internal"
|
||||||
"go.opentelemetry.io/otel/trace"
|
"go.opentelemetry.io/otel/trace"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -32,165 +29,60 @@ var (
|
|||||||
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
|
// NetAttributesFromHTTPRequest generates attributes of the net
|
||||||
// namespace as specified by the OpenTelemetry specification for a
|
// namespace as specified by the OpenTelemetry specification for a
|
||||||
// span. The network parameter is a string that net.Dial function
|
// span. The network parameter is a string that net.Dial function
|
||||||
// from standard library can understand.
|
// from standard library can understand.
|
||||||
func NetAttributesFromHTTPRequest(network string, request *http.Request) []attribute.KeyValue {
|
func NetAttributesFromHTTPRequest(network string, request *http.Request) []attribute.KeyValue {
|
||||||
attrs := []attribute.KeyValue{}
|
return sc.NetAttributesFromHTTPRequest(network, request)
|
||||||
|
|
||||||
switch network {
|
|
||||||
case "tcp", "tcp4", "tcp6":
|
|
||||||
attrs = append(attrs, NetTransportTCP)
|
|
||||||
case "udp", "udp4", "udp6":
|
|
||||||
attrs = append(attrs, NetTransportUDP)
|
|
||||||
case "ip", "ip4", "ip6":
|
|
||||||
attrs = append(attrs, NetTransportIP)
|
|
||||||
case "unix", "unixgram", "unixpacket":
|
|
||||||
attrs = append(attrs, NetTransportUnix)
|
|
||||||
default:
|
|
||||||
attrs = append(attrs, NetTransportOther)
|
|
||||||
}
|
|
||||||
|
|
||||||
peerIP, peerName, peerPort := hostIPNamePort(request.RemoteAddr)
|
|
||||||
if peerIP != "" {
|
|
||||||
attrs = append(attrs, NetPeerIPKey.String(peerIP))
|
|
||||||
}
|
|
||||||
if peerName != "" {
|
|
||||||
attrs = append(attrs, NetPeerNameKey.String(peerName))
|
|
||||||
}
|
|
||||||
if peerPort != 0 {
|
|
||||||
attrs = append(attrs, NetPeerPortKey.Int(peerPort))
|
|
||||||
}
|
|
||||||
|
|
||||||
hostIP, hostName, hostPort := "", "", 0
|
|
||||||
for _, someHost := range []string{request.Host, request.Header.Get("Host"), request.URL.Host} {
|
|
||||||
hostIP, hostName, hostPort = hostIPNamePort(someHost)
|
|
||||||
if hostIP != "" || hostName != "" || hostPort != 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if hostIP != "" {
|
|
||||||
attrs = append(attrs, NetHostIPKey.String(hostIP))
|
|
||||||
}
|
|
||||||
if hostName != "" {
|
|
||||||
attrs = append(attrs, NetHostNameKey.String(hostName))
|
|
||||||
}
|
|
||||||
if hostPort != 0 {
|
|
||||||
attrs = append(attrs, NetHostPortKey.Int(hostPort))
|
|
||||||
}
|
|
||||||
|
|
||||||
return attrs
|
|
||||||
}
|
|
||||||
|
|
||||||
// hostIPNamePort extracts the IP address, name and (optional) port from hostWithPort.
|
|
||||||
// It handles both IPv4 and IPv6 addresses. If the host portion is not recognized
|
|
||||||
// as a valid IPv4 or IPv6 address, the `ip` result will be empty and the
|
|
||||||
// host portion will instead be returned in `name`.
|
|
||||||
func hostIPNamePort(hostWithPort string) (ip string, name string, port int) {
|
|
||||||
var (
|
|
||||||
hostPart, portPart string
|
|
||||||
parsedPort uint64
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
if hostPart, portPart, err = net.SplitHostPort(hostWithPort); err != nil {
|
|
||||||
hostPart, portPart = hostWithPort, ""
|
|
||||||
}
|
|
||||||
if parsedIP := net.ParseIP(hostPart); parsedIP != nil {
|
|
||||||
ip = parsedIP.String()
|
|
||||||
} else {
|
|
||||||
name = hostPart
|
|
||||||
}
|
|
||||||
if parsedPort, err = strconv.ParseUint(portPart, 10, 16); err == nil {
|
|
||||||
port = int(parsedPort)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// EndUserAttributesFromHTTPRequest generates attributes of the
|
// EndUserAttributesFromHTTPRequest generates attributes of the
|
||||||
// enduser namespace as specified by the OpenTelemetry specification
|
// enduser namespace as specified by the OpenTelemetry specification
|
||||||
// for a span.
|
// for a span.
|
||||||
func EndUserAttributesFromHTTPRequest(request *http.Request) []attribute.KeyValue {
|
func EndUserAttributesFromHTTPRequest(request *http.Request) []attribute.KeyValue {
|
||||||
if username, _, ok := request.BasicAuth(); ok {
|
return sc.EndUserAttributesFromHTTPRequest(request)
|
||||||
return []attribute.KeyValue{EnduserIDKey.String(username)}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTTPClientAttributesFromHTTPRequest generates attributes of the
|
// HTTPClientAttributesFromHTTPRequest generates attributes of the
|
||||||
// http namespace as specified by the OpenTelemetry specification for
|
// http namespace as specified by the OpenTelemetry specification for
|
||||||
// a span on the client side.
|
// a span on the client side.
|
||||||
func HTTPClientAttributesFromHTTPRequest(request *http.Request) []attribute.KeyValue {
|
func HTTPClientAttributesFromHTTPRequest(request *http.Request) []attribute.KeyValue {
|
||||||
attrs := []attribute.KeyValue{}
|
return sc.HTTPClientAttributesFromHTTPRequest(request)
|
||||||
|
|
||||||
if request.Method != "" {
|
|
||||||
attrs = append(attrs, HTTPMethodKey.String(request.Method))
|
|
||||||
} else {
|
|
||||||
attrs = append(attrs, HTTPMethodKey.String(http.MethodGet))
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove any username/password info that may be in the URL
|
|
||||||
// before adding it to the attributes
|
|
||||||
userinfo := request.URL.User
|
|
||||||
request.URL.User = nil
|
|
||||||
|
|
||||||
attrs = append(attrs, HTTPURLKey.String(request.URL.String()))
|
|
||||||
|
|
||||||
// restore any username/password info that was removed
|
|
||||||
request.URL.User = userinfo
|
|
||||||
|
|
||||||
return append(attrs, httpCommonAttributesFromHTTPRequest(request)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func httpCommonAttributesFromHTTPRequest(request *http.Request) []attribute.KeyValue {
|
|
||||||
attrs := []attribute.KeyValue{}
|
|
||||||
if ua := request.UserAgent(); ua != "" {
|
|
||||||
attrs = append(attrs, HTTPUserAgentKey.String(ua))
|
|
||||||
}
|
|
||||||
if request.ContentLength > 0 {
|
|
||||||
attrs = append(attrs, HTTPRequestContentLengthKey.Int64(request.ContentLength))
|
|
||||||
}
|
|
||||||
|
|
||||||
return append(attrs, httpBasicAttributesFromHTTPRequest(request)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func httpBasicAttributesFromHTTPRequest(request *http.Request) []attribute.KeyValue {
|
|
||||||
// as these attributes are used by HTTPServerMetricAttributesFromHTTPRequest, they should be low-cardinality
|
|
||||||
attrs := []attribute.KeyValue{}
|
|
||||||
|
|
||||||
if request.TLS != nil {
|
|
||||||
attrs = append(attrs, HTTPSchemeHTTPS)
|
|
||||||
} else {
|
|
||||||
attrs = append(attrs, HTTPSchemeHTTP)
|
|
||||||
}
|
|
||||||
|
|
||||||
if request.Host != "" {
|
|
||||||
attrs = append(attrs, HTTPHostKey.String(request.Host))
|
|
||||||
} else if request.URL != nil && request.URL.Host != "" {
|
|
||||||
attrs = append(attrs, HTTPHostKey.String(request.URL.Host))
|
|
||||||
}
|
|
||||||
|
|
||||||
flavor := ""
|
|
||||||
if request.ProtoMajor == 1 {
|
|
||||||
flavor = fmt.Sprintf("1.%d", request.ProtoMinor)
|
|
||||||
} else if request.ProtoMajor == 2 {
|
|
||||||
flavor = "2"
|
|
||||||
}
|
|
||||||
if flavor != "" {
|
|
||||||
attrs = append(attrs, HTTPFlavorKey.String(flavor))
|
|
||||||
}
|
|
||||||
|
|
||||||
return attrs
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTTPServerMetricAttributesFromHTTPRequest generates low-cardinality attributes
|
// HTTPServerMetricAttributesFromHTTPRequest generates low-cardinality attributes
|
||||||
// to be used with server-side HTTP metrics.
|
// to be used with server-side HTTP metrics.
|
||||||
func HTTPServerMetricAttributesFromHTTPRequest(serverName string, request *http.Request) []attribute.KeyValue {
|
func HTTPServerMetricAttributesFromHTTPRequest(serverName string, request *http.Request) []attribute.KeyValue {
|
||||||
attrs := []attribute.KeyValue{}
|
return sc.HTTPServerMetricAttributesFromHTTPRequest(serverName, request)
|
||||||
if serverName != "" {
|
|
||||||
attrs = append(attrs, HTTPServerNameKey.String(serverName))
|
|
||||||
}
|
|
||||||
return append(attrs, httpBasicAttributesFromHTTPRequest(request)...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTTPServerAttributesFromHTTPRequest generates attributes of the
|
// HTTPServerAttributesFromHTTPRequest generates attributes of the
|
||||||
@@ -198,116 +90,25 @@ func HTTPServerMetricAttributesFromHTTPRequest(serverName string, request *http.
|
|||||||
// a span on the server side. Currently, only basic authentication is
|
// a span on the server side. Currently, only basic authentication is
|
||||||
// supported.
|
// supported.
|
||||||
func HTTPServerAttributesFromHTTPRequest(serverName, route string, request *http.Request) []attribute.KeyValue {
|
func HTTPServerAttributesFromHTTPRequest(serverName, route string, request *http.Request) []attribute.KeyValue {
|
||||||
attrs := []attribute.KeyValue{
|
return sc.HTTPServerAttributesFromHTTPRequest(serverName, route, request)
|
||||||
HTTPMethodKey.String(request.Method),
|
|
||||||
HTTPTargetKey.String(request.RequestURI),
|
|
||||||
}
|
|
||||||
|
|
||||||
if serverName != "" {
|
|
||||||
attrs = append(attrs, HTTPServerNameKey.String(serverName))
|
|
||||||
}
|
|
||||||
if route != "" {
|
|
||||||
attrs = append(attrs, HTTPRouteKey.String(route))
|
|
||||||
}
|
|
||||||
if values, ok := request.Header["X-Forwarded-For"]; ok && len(values) > 0 {
|
|
||||||
if addresses := strings.SplitN(values[0], ",", 2); len(addresses) > 0 {
|
|
||||||
attrs = append(attrs, HTTPClientIPKey.String(addresses[0]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return append(attrs, httpCommonAttributesFromHTTPRequest(request)...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTTPAttributesFromHTTPStatusCode generates attributes of the http
|
// HTTPAttributesFromHTTPStatusCode generates attributes of the http
|
||||||
// namespace as specified by the OpenTelemetry specification for a
|
// namespace as specified by the OpenTelemetry specification for a
|
||||||
// span.
|
// span.
|
||||||
func HTTPAttributesFromHTTPStatusCode(code int) []attribute.KeyValue {
|
func HTTPAttributesFromHTTPStatusCode(code int) []attribute.KeyValue {
|
||||||
attrs := []attribute.KeyValue{
|
return sc.HTTPAttributesFromHTTPStatusCode(code)
|
||||||
HTTPStatusCodeKey.Int(code),
|
|
||||||
}
|
|
||||||
return attrs
|
|
||||||
}
|
|
||||||
|
|
||||||
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},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SpanStatusFromHTTPStatusCode generates a status code and a message
|
// SpanStatusFromHTTPStatusCode generates a status code and a message
|
||||||
// as specified by the OpenTelemetry specification for a span.
|
// as specified by the OpenTelemetry specification for a span.
|
||||||
func SpanStatusFromHTTPStatusCode(code int) (codes.Code, string) {
|
func SpanStatusFromHTTPStatusCode(code int) (codes.Code, string) {
|
||||||
spanCode, valid := validateHTTPStatusCode(code)
|
return internal.SpanStatusFromHTTPStatusCode(code)
|
||||||
if !valid {
|
|
||||||
return spanCode, fmt.Sprintf("Invalid HTTP status code %d", code)
|
|
||||||
}
|
|
||||||
return spanCode, ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SpanStatusFromHTTPStatusCodeAndSpanKind generates a status code and a message
|
// SpanStatusFromHTTPStatusCodeAndSpanKind generates a status code and a message
|
||||||
// as specified by the OpenTelemetry specification for a span.
|
// as specified by the OpenTelemetry specification for a span.
|
||||||
// Exclude 4xx for SERVER to set the appropriate status.
|
// Exclude 4xx for SERVER to set the appropriate status.
|
||||||
func SpanStatusFromHTTPStatusCodeAndSpanKind(code int, spanKind trace.SpanKind) (codes.Code, string) {
|
func SpanStatusFromHTTPStatusCodeAndSpanKind(code int, spanKind trace.SpanKind) (codes.Code, string) {
|
||||||
spanCode, valid := validateHTTPStatusCode(code)
|
return internal.SpanStatusFromHTTPStatusCodeAndSpanKind(code, spanKind)
|
||||||
if !valid {
|
|
||||||
return spanCode, fmt.Sprintf("Invalid HTTP status code %d", code)
|
|
||||||
}
|
|
||||||
category := code / 100
|
|
||||||
if spanKind == trace.SpanKindServer && category == 4 {
|
|
||||||
return codes.Unset, ""
|
|
||||||
}
|
|
||||||
return spanCode, ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,14 +15,11 @@
|
|||||||
package semconv // import "go.opentelemetry.io/otel/semconv/v1.5.0"
|
package semconv // import "go.opentelemetry.io/otel/semconv/v1.5.0"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"go.opentelemetry.io/otel/attribute"
|
"go.opentelemetry.io/otel/attribute"
|
||||||
"go.opentelemetry.io/otel/codes"
|
"go.opentelemetry.io/otel/codes"
|
||||||
|
"go.opentelemetry.io/otel/semconv/internal"
|
||||||
"go.opentelemetry.io/otel/trace"
|
"go.opentelemetry.io/otel/trace"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -32,165 +29,60 @@ var (
|
|||||||
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
|
// NetAttributesFromHTTPRequest generates attributes of the net
|
||||||
// namespace as specified by the OpenTelemetry specification for a
|
// namespace as specified by the OpenTelemetry specification for a
|
||||||
// span. The network parameter is a string that net.Dial function
|
// span. The network parameter is a string that net.Dial function
|
||||||
// from standard library can understand.
|
// from standard library can understand.
|
||||||
func NetAttributesFromHTTPRequest(network string, request *http.Request) []attribute.KeyValue {
|
func NetAttributesFromHTTPRequest(network string, request *http.Request) []attribute.KeyValue {
|
||||||
attrs := []attribute.KeyValue{}
|
return sc.NetAttributesFromHTTPRequest(network, request)
|
||||||
|
|
||||||
switch network {
|
|
||||||
case "tcp", "tcp4", "tcp6":
|
|
||||||
attrs = append(attrs, NetTransportTCP)
|
|
||||||
case "udp", "udp4", "udp6":
|
|
||||||
attrs = append(attrs, NetTransportUDP)
|
|
||||||
case "ip", "ip4", "ip6":
|
|
||||||
attrs = append(attrs, NetTransportIP)
|
|
||||||
case "unix", "unixgram", "unixpacket":
|
|
||||||
attrs = append(attrs, NetTransportUnix)
|
|
||||||
default:
|
|
||||||
attrs = append(attrs, NetTransportOther)
|
|
||||||
}
|
|
||||||
|
|
||||||
peerIP, peerName, peerPort := hostIPNamePort(request.RemoteAddr)
|
|
||||||
if peerIP != "" {
|
|
||||||
attrs = append(attrs, NetPeerIPKey.String(peerIP))
|
|
||||||
}
|
|
||||||
if peerName != "" {
|
|
||||||
attrs = append(attrs, NetPeerNameKey.String(peerName))
|
|
||||||
}
|
|
||||||
if peerPort != 0 {
|
|
||||||
attrs = append(attrs, NetPeerPortKey.Int(peerPort))
|
|
||||||
}
|
|
||||||
|
|
||||||
hostIP, hostName, hostPort := "", "", 0
|
|
||||||
for _, someHost := range []string{request.Host, request.Header.Get("Host"), request.URL.Host} {
|
|
||||||
hostIP, hostName, hostPort = hostIPNamePort(someHost)
|
|
||||||
if hostIP != "" || hostName != "" || hostPort != 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if hostIP != "" {
|
|
||||||
attrs = append(attrs, NetHostIPKey.String(hostIP))
|
|
||||||
}
|
|
||||||
if hostName != "" {
|
|
||||||
attrs = append(attrs, NetHostNameKey.String(hostName))
|
|
||||||
}
|
|
||||||
if hostPort != 0 {
|
|
||||||
attrs = append(attrs, NetHostPortKey.Int(hostPort))
|
|
||||||
}
|
|
||||||
|
|
||||||
return attrs
|
|
||||||
}
|
|
||||||
|
|
||||||
// hostIPNamePort extracts the IP address, name and (optional) port from hostWithPort.
|
|
||||||
// It handles both IPv4 and IPv6 addresses. If the host portion is not recognized
|
|
||||||
// as a valid IPv4 or IPv6 address, the `ip` result will be empty and the
|
|
||||||
// host portion will instead be returned in `name`.
|
|
||||||
func hostIPNamePort(hostWithPort string) (ip string, name string, port int) {
|
|
||||||
var (
|
|
||||||
hostPart, portPart string
|
|
||||||
parsedPort uint64
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
if hostPart, portPart, err = net.SplitHostPort(hostWithPort); err != nil {
|
|
||||||
hostPart, portPart = hostWithPort, ""
|
|
||||||
}
|
|
||||||
if parsedIP := net.ParseIP(hostPart); parsedIP != nil {
|
|
||||||
ip = parsedIP.String()
|
|
||||||
} else {
|
|
||||||
name = hostPart
|
|
||||||
}
|
|
||||||
if parsedPort, err = strconv.ParseUint(portPart, 10, 16); err == nil {
|
|
||||||
port = int(parsedPort)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// EndUserAttributesFromHTTPRequest generates attributes of the
|
// EndUserAttributesFromHTTPRequest generates attributes of the
|
||||||
// enduser namespace as specified by the OpenTelemetry specification
|
// enduser namespace as specified by the OpenTelemetry specification
|
||||||
// for a span.
|
// for a span.
|
||||||
func EndUserAttributesFromHTTPRequest(request *http.Request) []attribute.KeyValue {
|
func EndUserAttributesFromHTTPRequest(request *http.Request) []attribute.KeyValue {
|
||||||
if username, _, ok := request.BasicAuth(); ok {
|
return sc.EndUserAttributesFromHTTPRequest(request)
|
||||||
return []attribute.KeyValue{EnduserIDKey.String(username)}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTTPClientAttributesFromHTTPRequest generates attributes of the
|
// HTTPClientAttributesFromHTTPRequest generates attributes of the
|
||||||
// http namespace as specified by the OpenTelemetry specification for
|
// http namespace as specified by the OpenTelemetry specification for
|
||||||
// a span on the client side.
|
// a span on the client side.
|
||||||
func HTTPClientAttributesFromHTTPRequest(request *http.Request) []attribute.KeyValue {
|
func HTTPClientAttributesFromHTTPRequest(request *http.Request) []attribute.KeyValue {
|
||||||
attrs := []attribute.KeyValue{}
|
return sc.HTTPClientAttributesFromHTTPRequest(request)
|
||||||
|
|
||||||
if request.Method != "" {
|
|
||||||
attrs = append(attrs, HTTPMethodKey.String(request.Method))
|
|
||||||
} else {
|
|
||||||
attrs = append(attrs, HTTPMethodKey.String(http.MethodGet))
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove any username/password info that may be in the URL
|
|
||||||
// before adding it to the attributes
|
|
||||||
userinfo := request.URL.User
|
|
||||||
request.URL.User = nil
|
|
||||||
|
|
||||||
attrs = append(attrs, HTTPURLKey.String(request.URL.String()))
|
|
||||||
|
|
||||||
// restore any username/password info that was removed
|
|
||||||
request.URL.User = userinfo
|
|
||||||
|
|
||||||
return append(attrs, httpCommonAttributesFromHTTPRequest(request)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func httpCommonAttributesFromHTTPRequest(request *http.Request) []attribute.KeyValue {
|
|
||||||
attrs := []attribute.KeyValue{}
|
|
||||||
if ua := request.UserAgent(); ua != "" {
|
|
||||||
attrs = append(attrs, HTTPUserAgentKey.String(ua))
|
|
||||||
}
|
|
||||||
if request.ContentLength > 0 {
|
|
||||||
attrs = append(attrs, HTTPRequestContentLengthKey.Int64(request.ContentLength))
|
|
||||||
}
|
|
||||||
|
|
||||||
return append(attrs, httpBasicAttributesFromHTTPRequest(request)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func httpBasicAttributesFromHTTPRequest(request *http.Request) []attribute.KeyValue {
|
|
||||||
// as these attributes are used by HTTPServerMetricAttributesFromHTTPRequest, they should be low-cardinality
|
|
||||||
attrs := []attribute.KeyValue{}
|
|
||||||
|
|
||||||
if request.TLS != nil {
|
|
||||||
attrs = append(attrs, HTTPSchemeHTTPS)
|
|
||||||
} else {
|
|
||||||
attrs = append(attrs, HTTPSchemeHTTP)
|
|
||||||
}
|
|
||||||
|
|
||||||
if request.Host != "" {
|
|
||||||
attrs = append(attrs, HTTPHostKey.String(request.Host))
|
|
||||||
} else if request.URL != nil && request.URL.Host != "" {
|
|
||||||
attrs = append(attrs, HTTPHostKey.String(request.URL.Host))
|
|
||||||
}
|
|
||||||
|
|
||||||
flavor := ""
|
|
||||||
if request.ProtoMajor == 1 {
|
|
||||||
flavor = fmt.Sprintf("1.%d", request.ProtoMinor)
|
|
||||||
} else if request.ProtoMajor == 2 {
|
|
||||||
flavor = "2"
|
|
||||||
}
|
|
||||||
if flavor != "" {
|
|
||||||
attrs = append(attrs, HTTPFlavorKey.String(flavor))
|
|
||||||
}
|
|
||||||
|
|
||||||
return attrs
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTTPServerMetricAttributesFromHTTPRequest generates low-cardinality attributes
|
// HTTPServerMetricAttributesFromHTTPRequest generates low-cardinality attributes
|
||||||
// to be used with server-side HTTP metrics.
|
// to be used with server-side HTTP metrics.
|
||||||
func HTTPServerMetricAttributesFromHTTPRequest(serverName string, request *http.Request) []attribute.KeyValue {
|
func HTTPServerMetricAttributesFromHTTPRequest(serverName string, request *http.Request) []attribute.KeyValue {
|
||||||
attrs := []attribute.KeyValue{}
|
return sc.HTTPServerMetricAttributesFromHTTPRequest(serverName, request)
|
||||||
if serverName != "" {
|
|
||||||
attrs = append(attrs, HTTPServerNameKey.String(serverName))
|
|
||||||
}
|
|
||||||
return append(attrs, httpBasicAttributesFromHTTPRequest(request)...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTTPServerAttributesFromHTTPRequest generates attributes of the
|
// HTTPServerAttributesFromHTTPRequest generates attributes of the
|
||||||
@@ -198,116 +90,25 @@ func HTTPServerMetricAttributesFromHTTPRequest(serverName string, request *http.
|
|||||||
// a span on the server side. Currently, only basic authentication is
|
// a span on the server side. Currently, only basic authentication is
|
||||||
// supported.
|
// supported.
|
||||||
func HTTPServerAttributesFromHTTPRequest(serverName, route string, request *http.Request) []attribute.KeyValue {
|
func HTTPServerAttributesFromHTTPRequest(serverName, route string, request *http.Request) []attribute.KeyValue {
|
||||||
attrs := []attribute.KeyValue{
|
return sc.HTTPServerAttributesFromHTTPRequest(serverName, route, request)
|
||||||
HTTPMethodKey.String(request.Method),
|
|
||||||
HTTPTargetKey.String(request.RequestURI),
|
|
||||||
}
|
|
||||||
|
|
||||||
if serverName != "" {
|
|
||||||
attrs = append(attrs, HTTPServerNameKey.String(serverName))
|
|
||||||
}
|
|
||||||
if route != "" {
|
|
||||||
attrs = append(attrs, HTTPRouteKey.String(route))
|
|
||||||
}
|
|
||||||
if values, ok := request.Header["X-Forwarded-For"]; ok && len(values) > 0 {
|
|
||||||
if addresses := strings.SplitN(values[0], ",", 2); len(addresses) > 0 {
|
|
||||||
attrs = append(attrs, HTTPClientIPKey.String(addresses[0]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return append(attrs, httpCommonAttributesFromHTTPRequest(request)...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTTPAttributesFromHTTPStatusCode generates attributes of the http
|
// HTTPAttributesFromHTTPStatusCode generates attributes of the http
|
||||||
// namespace as specified by the OpenTelemetry specification for a
|
// namespace as specified by the OpenTelemetry specification for a
|
||||||
// span.
|
// span.
|
||||||
func HTTPAttributesFromHTTPStatusCode(code int) []attribute.KeyValue {
|
func HTTPAttributesFromHTTPStatusCode(code int) []attribute.KeyValue {
|
||||||
attrs := []attribute.KeyValue{
|
return sc.HTTPAttributesFromHTTPStatusCode(code)
|
||||||
HTTPStatusCodeKey.Int(code),
|
|
||||||
}
|
|
||||||
return attrs
|
|
||||||
}
|
|
||||||
|
|
||||||
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},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SpanStatusFromHTTPStatusCode generates a status code and a message
|
// SpanStatusFromHTTPStatusCode generates a status code and a message
|
||||||
// as specified by the OpenTelemetry specification for a span.
|
// as specified by the OpenTelemetry specification for a span.
|
||||||
func SpanStatusFromHTTPStatusCode(code int) (codes.Code, string) {
|
func SpanStatusFromHTTPStatusCode(code int) (codes.Code, string) {
|
||||||
spanCode, valid := validateHTTPStatusCode(code)
|
return internal.SpanStatusFromHTTPStatusCode(code)
|
||||||
if !valid {
|
|
||||||
return spanCode, fmt.Sprintf("Invalid HTTP status code %d", code)
|
|
||||||
}
|
|
||||||
return spanCode, ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SpanStatusFromHTTPStatusCodeAndSpanKind generates a status code and a message
|
// SpanStatusFromHTTPStatusCodeAndSpanKind generates a status code and a message
|
||||||
// as specified by the OpenTelemetry specification for a span.
|
// as specified by the OpenTelemetry specification for a span.
|
||||||
// Exclude 4xx for SERVER to set the appropriate status.
|
// Exclude 4xx for SERVER to set the appropriate status.
|
||||||
func SpanStatusFromHTTPStatusCodeAndSpanKind(code int, spanKind trace.SpanKind) (codes.Code, string) {
|
func SpanStatusFromHTTPStatusCodeAndSpanKind(code int, spanKind trace.SpanKind) (codes.Code, string) {
|
||||||
spanCode, valid := validateHTTPStatusCode(code)
|
return internal.SpanStatusFromHTTPStatusCodeAndSpanKind(code, spanKind)
|
||||||
if !valid {
|
|
||||||
return spanCode, fmt.Sprintf("Invalid HTTP status code %d", code)
|
|
||||||
}
|
|
||||||
category := code / 100
|
|
||||||
if spanKind == trace.SpanKindServer && category == 4 {
|
|
||||||
return codes.Unset, ""
|
|
||||||
}
|
|
||||||
return spanCode, ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -15,14 +15,11 @@
|
|||||||
package semconv // import "go.opentelemetry.io/otel/semconv/v1.6.1"
|
package semconv // import "go.opentelemetry.io/otel/semconv/v1.6.1"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"go.opentelemetry.io/otel/attribute"
|
"go.opentelemetry.io/otel/attribute"
|
||||||
"go.opentelemetry.io/otel/codes"
|
"go.opentelemetry.io/otel/codes"
|
||||||
|
"go.opentelemetry.io/otel/semconv/internal"
|
||||||
"go.opentelemetry.io/otel/trace"
|
"go.opentelemetry.io/otel/trace"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -32,165 +29,60 @@ var (
|
|||||||
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
|
// NetAttributesFromHTTPRequest generates attributes of the net
|
||||||
// namespace as specified by the OpenTelemetry specification for a
|
// namespace as specified by the OpenTelemetry specification for a
|
||||||
// span. The network parameter is a string that net.Dial function
|
// span. The network parameter is a string that net.Dial function
|
||||||
// from standard library can understand.
|
// from standard library can understand.
|
||||||
func NetAttributesFromHTTPRequest(network string, request *http.Request) []attribute.KeyValue {
|
func NetAttributesFromHTTPRequest(network string, request *http.Request) []attribute.KeyValue {
|
||||||
attrs := []attribute.KeyValue{}
|
return sc.NetAttributesFromHTTPRequest(network, request)
|
||||||
|
|
||||||
switch network {
|
|
||||||
case "tcp", "tcp4", "tcp6":
|
|
||||||
attrs = append(attrs, NetTransportTCP)
|
|
||||||
case "udp", "udp4", "udp6":
|
|
||||||
attrs = append(attrs, NetTransportUDP)
|
|
||||||
case "ip", "ip4", "ip6":
|
|
||||||
attrs = append(attrs, NetTransportIP)
|
|
||||||
case "unix", "unixgram", "unixpacket":
|
|
||||||
attrs = append(attrs, NetTransportUnix)
|
|
||||||
default:
|
|
||||||
attrs = append(attrs, NetTransportOther)
|
|
||||||
}
|
|
||||||
|
|
||||||
peerIP, peerName, peerPort := hostIPNamePort(request.RemoteAddr)
|
|
||||||
if peerIP != "" {
|
|
||||||
attrs = append(attrs, NetPeerIPKey.String(peerIP))
|
|
||||||
}
|
|
||||||
if peerName != "" {
|
|
||||||
attrs = append(attrs, NetPeerNameKey.String(peerName))
|
|
||||||
}
|
|
||||||
if peerPort != 0 {
|
|
||||||
attrs = append(attrs, NetPeerPortKey.Int(peerPort))
|
|
||||||
}
|
|
||||||
|
|
||||||
hostIP, hostName, hostPort := "", "", 0
|
|
||||||
for _, someHost := range []string{request.Host, request.Header.Get("Host"), request.URL.Host} {
|
|
||||||
hostIP, hostName, hostPort = hostIPNamePort(someHost)
|
|
||||||
if hostIP != "" || hostName != "" || hostPort != 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if hostIP != "" {
|
|
||||||
attrs = append(attrs, NetHostIPKey.String(hostIP))
|
|
||||||
}
|
|
||||||
if hostName != "" {
|
|
||||||
attrs = append(attrs, NetHostNameKey.String(hostName))
|
|
||||||
}
|
|
||||||
if hostPort != 0 {
|
|
||||||
attrs = append(attrs, NetHostPortKey.Int(hostPort))
|
|
||||||
}
|
|
||||||
|
|
||||||
return attrs
|
|
||||||
}
|
|
||||||
|
|
||||||
// hostIPNamePort extracts the IP address, name and (optional) port from hostWithPort.
|
|
||||||
// It handles both IPv4 and IPv6 addresses. If the host portion is not recognized
|
|
||||||
// as a valid IPv4 or IPv6 address, the `ip` result will be empty and the
|
|
||||||
// host portion will instead be returned in `name`.
|
|
||||||
func hostIPNamePort(hostWithPort string) (ip string, name string, port int) {
|
|
||||||
var (
|
|
||||||
hostPart, portPart string
|
|
||||||
parsedPort uint64
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
if hostPart, portPart, err = net.SplitHostPort(hostWithPort); err != nil {
|
|
||||||
hostPart, portPart = hostWithPort, ""
|
|
||||||
}
|
|
||||||
if parsedIP := net.ParseIP(hostPart); parsedIP != nil {
|
|
||||||
ip = parsedIP.String()
|
|
||||||
} else {
|
|
||||||
name = hostPart
|
|
||||||
}
|
|
||||||
if parsedPort, err = strconv.ParseUint(portPart, 10, 16); err == nil {
|
|
||||||
port = int(parsedPort)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// EndUserAttributesFromHTTPRequest generates attributes of the
|
// EndUserAttributesFromHTTPRequest generates attributes of the
|
||||||
// enduser namespace as specified by the OpenTelemetry specification
|
// enduser namespace as specified by the OpenTelemetry specification
|
||||||
// for a span.
|
// for a span.
|
||||||
func EndUserAttributesFromHTTPRequest(request *http.Request) []attribute.KeyValue {
|
func EndUserAttributesFromHTTPRequest(request *http.Request) []attribute.KeyValue {
|
||||||
if username, _, ok := request.BasicAuth(); ok {
|
return sc.EndUserAttributesFromHTTPRequest(request)
|
||||||
return []attribute.KeyValue{EnduserIDKey.String(username)}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTTPClientAttributesFromHTTPRequest generates attributes of the
|
// HTTPClientAttributesFromHTTPRequest generates attributes of the
|
||||||
// http namespace as specified by the OpenTelemetry specification for
|
// http namespace as specified by the OpenTelemetry specification for
|
||||||
// a span on the client side.
|
// a span on the client side.
|
||||||
func HTTPClientAttributesFromHTTPRequest(request *http.Request) []attribute.KeyValue {
|
func HTTPClientAttributesFromHTTPRequest(request *http.Request) []attribute.KeyValue {
|
||||||
attrs := []attribute.KeyValue{}
|
return sc.HTTPClientAttributesFromHTTPRequest(request)
|
||||||
|
|
||||||
if request.Method != "" {
|
|
||||||
attrs = append(attrs, HTTPMethodKey.String(request.Method))
|
|
||||||
} else {
|
|
||||||
attrs = append(attrs, HTTPMethodKey.String(http.MethodGet))
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove any username/password info that may be in the URL
|
|
||||||
// before adding it to the attributes
|
|
||||||
userinfo := request.URL.User
|
|
||||||
request.URL.User = nil
|
|
||||||
|
|
||||||
attrs = append(attrs, HTTPURLKey.String(request.URL.String()))
|
|
||||||
|
|
||||||
// restore any username/password info that was removed
|
|
||||||
request.URL.User = userinfo
|
|
||||||
|
|
||||||
return append(attrs, httpCommonAttributesFromHTTPRequest(request)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func httpCommonAttributesFromHTTPRequest(request *http.Request) []attribute.KeyValue {
|
|
||||||
attrs := []attribute.KeyValue{}
|
|
||||||
if ua := request.UserAgent(); ua != "" {
|
|
||||||
attrs = append(attrs, HTTPUserAgentKey.String(ua))
|
|
||||||
}
|
|
||||||
if request.ContentLength > 0 {
|
|
||||||
attrs = append(attrs, HTTPRequestContentLengthKey.Int64(request.ContentLength))
|
|
||||||
}
|
|
||||||
|
|
||||||
return append(attrs, httpBasicAttributesFromHTTPRequest(request)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func httpBasicAttributesFromHTTPRequest(request *http.Request) []attribute.KeyValue {
|
|
||||||
// as these attributes are used by HTTPServerMetricAttributesFromHTTPRequest, they should be low-cardinality
|
|
||||||
attrs := []attribute.KeyValue{}
|
|
||||||
|
|
||||||
if request.TLS != nil {
|
|
||||||
attrs = append(attrs, HTTPSchemeHTTPS)
|
|
||||||
} else {
|
|
||||||
attrs = append(attrs, HTTPSchemeHTTP)
|
|
||||||
}
|
|
||||||
|
|
||||||
if request.Host != "" {
|
|
||||||
attrs = append(attrs, HTTPHostKey.String(request.Host))
|
|
||||||
} else if request.URL != nil && request.URL.Host != "" {
|
|
||||||
attrs = append(attrs, HTTPHostKey.String(request.URL.Host))
|
|
||||||
}
|
|
||||||
|
|
||||||
flavor := ""
|
|
||||||
if request.ProtoMajor == 1 {
|
|
||||||
flavor = fmt.Sprintf("1.%d", request.ProtoMinor)
|
|
||||||
} else if request.ProtoMajor == 2 {
|
|
||||||
flavor = "2"
|
|
||||||
}
|
|
||||||
if flavor != "" {
|
|
||||||
attrs = append(attrs, HTTPFlavorKey.String(flavor))
|
|
||||||
}
|
|
||||||
|
|
||||||
return attrs
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTTPServerMetricAttributesFromHTTPRequest generates low-cardinality attributes
|
// HTTPServerMetricAttributesFromHTTPRequest generates low-cardinality attributes
|
||||||
// to be used with server-side HTTP metrics.
|
// to be used with server-side HTTP metrics.
|
||||||
func HTTPServerMetricAttributesFromHTTPRequest(serverName string, request *http.Request) []attribute.KeyValue {
|
func HTTPServerMetricAttributesFromHTTPRequest(serverName string, request *http.Request) []attribute.KeyValue {
|
||||||
attrs := []attribute.KeyValue{}
|
return sc.HTTPServerMetricAttributesFromHTTPRequest(serverName, request)
|
||||||
if serverName != "" {
|
|
||||||
attrs = append(attrs, HTTPServerNameKey.String(serverName))
|
|
||||||
}
|
|
||||||
return append(attrs, httpBasicAttributesFromHTTPRequest(request)...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTTPServerAttributesFromHTTPRequest generates attributes of the
|
// HTTPServerAttributesFromHTTPRequest generates attributes of the
|
||||||
@@ -198,116 +90,25 @@ func HTTPServerMetricAttributesFromHTTPRequest(serverName string, request *http.
|
|||||||
// a span on the server side. Currently, only basic authentication is
|
// a span on the server side. Currently, only basic authentication is
|
||||||
// supported.
|
// supported.
|
||||||
func HTTPServerAttributesFromHTTPRequest(serverName, route string, request *http.Request) []attribute.KeyValue {
|
func HTTPServerAttributesFromHTTPRequest(serverName, route string, request *http.Request) []attribute.KeyValue {
|
||||||
attrs := []attribute.KeyValue{
|
return sc.HTTPServerAttributesFromHTTPRequest(serverName, route, request)
|
||||||
HTTPMethodKey.String(request.Method),
|
|
||||||
HTTPTargetKey.String(request.RequestURI),
|
|
||||||
}
|
|
||||||
|
|
||||||
if serverName != "" {
|
|
||||||
attrs = append(attrs, HTTPServerNameKey.String(serverName))
|
|
||||||
}
|
|
||||||
if route != "" {
|
|
||||||
attrs = append(attrs, HTTPRouteKey.String(route))
|
|
||||||
}
|
|
||||||
if values, ok := request.Header["X-Forwarded-For"]; ok && len(values) > 0 {
|
|
||||||
if addresses := strings.SplitN(values[0], ",", 2); len(addresses) > 0 {
|
|
||||||
attrs = append(attrs, HTTPClientIPKey.String(addresses[0]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return append(attrs, httpCommonAttributesFromHTTPRequest(request)...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTTPAttributesFromHTTPStatusCode generates attributes of the http
|
// HTTPAttributesFromHTTPStatusCode generates attributes of the http
|
||||||
// namespace as specified by the OpenTelemetry specification for a
|
// namespace as specified by the OpenTelemetry specification for a
|
||||||
// span.
|
// span.
|
||||||
func HTTPAttributesFromHTTPStatusCode(code int) []attribute.KeyValue {
|
func HTTPAttributesFromHTTPStatusCode(code int) []attribute.KeyValue {
|
||||||
attrs := []attribute.KeyValue{
|
return sc.HTTPAttributesFromHTTPStatusCode(code)
|
||||||
HTTPStatusCodeKey.Int(code),
|
|
||||||
}
|
|
||||||
return attrs
|
|
||||||
}
|
|
||||||
|
|
||||||
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},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SpanStatusFromHTTPStatusCode generates a status code and a message
|
// SpanStatusFromHTTPStatusCode generates a status code and a message
|
||||||
// as specified by the OpenTelemetry specification for a span.
|
// as specified by the OpenTelemetry specification for a span.
|
||||||
func SpanStatusFromHTTPStatusCode(code int) (codes.Code, string) {
|
func SpanStatusFromHTTPStatusCode(code int) (codes.Code, string) {
|
||||||
spanCode, valid := validateHTTPStatusCode(code)
|
return internal.SpanStatusFromHTTPStatusCode(code)
|
||||||
if !valid {
|
|
||||||
return spanCode, fmt.Sprintf("Invalid HTTP status code %d", code)
|
|
||||||
}
|
|
||||||
return spanCode, ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SpanStatusFromHTTPStatusCodeAndSpanKind generates a status code and a message
|
// SpanStatusFromHTTPStatusCodeAndSpanKind generates a status code and a message
|
||||||
// as specified by the OpenTelemetry specification for a span.
|
// as specified by the OpenTelemetry specification for a span.
|
||||||
// Exclude 4xx for SERVER to set the appropriate status.
|
// Exclude 4xx for SERVER to set the appropriate status.
|
||||||
func SpanStatusFromHTTPStatusCodeAndSpanKind(code int, spanKind trace.SpanKind) (codes.Code, string) {
|
func SpanStatusFromHTTPStatusCodeAndSpanKind(code int, spanKind trace.SpanKind) (codes.Code, string) {
|
||||||
spanCode, valid := validateHTTPStatusCode(code)
|
return internal.SpanStatusFromHTTPStatusCodeAndSpanKind(code, spanKind)
|
||||||
if !valid {
|
|
||||||
return spanCode, fmt.Sprintf("Invalid HTTP status code %d", code)
|
|
||||||
}
|
|
||||||
category := code / 100
|
|
||||||
if spanKind == trace.SpanKindServer && category == 4 {
|
|
||||||
return codes.Unset, ""
|
|
||||||
}
|
|
||||||
return spanCode, ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -15,16 +15,12 @@
|
|||||||
package semconv // import "go.opentelemetry.io/otel/semconv/v1.7.0"
|
package semconv // import "go.opentelemetry.io/otel/semconv/v1.7.0"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"go.opentelemetry.io/otel/trace"
|
|
||||||
|
|
||||||
"go.opentelemetry.io/otel/attribute"
|
"go.opentelemetry.io/otel/attribute"
|
||||||
"go.opentelemetry.io/otel/codes"
|
"go.opentelemetry.io/otel/codes"
|
||||||
|
"go.opentelemetry.io/otel/semconv/internal"
|
||||||
|
"go.opentelemetry.io/otel/trace"
|
||||||
)
|
)
|
||||||
|
|
||||||
// HTTP scheme attributes.
|
// HTTP scheme attributes.
|
||||||
@@ -33,165 +29,60 @@ var (
|
|||||||
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
|
// NetAttributesFromHTTPRequest generates attributes of the net
|
||||||
// namespace as specified by the OpenTelemetry specification for a
|
// namespace as specified by the OpenTelemetry specification for a
|
||||||
// span. The network parameter is a string that net.Dial function
|
// span. The network parameter is a string that net.Dial function
|
||||||
// from standard library can understand.
|
// from standard library can understand.
|
||||||
func NetAttributesFromHTTPRequest(network string, request *http.Request) []attribute.KeyValue {
|
func NetAttributesFromHTTPRequest(network string, request *http.Request) []attribute.KeyValue {
|
||||||
attrs := []attribute.KeyValue{}
|
return sc.NetAttributesFromHTTPRequest(network, request)
|
||||||
|
|
||||||
switch network {
|
|
||||||
case "tcp", "tcp4", "tcp6":
|
|
||||||
attrs = append(attrs, NetTransportTCP)
|
|
||||||
case "udp", "udp4", "udp6":
|
|
||||||
attrs = append(attrs, NetTransportUDP)
|
|
||||||
case "ip", "ip4", "ip6":
|
|
||||||
attrs = append(attrs, NetTransportIP)
|
|
||||||
case "unix", "unixgram", "unixpacket":
|
|
||||||
attrs = append(attrs, NetTransportUnix)
|
|
||||||
default:
|
|
||||||
attrs = append(attrs, NetTransportOther)
|
|
||||||
}
|
|
||||||
|
|
||||||
peerIP, peerName, peerPort := hostIPNamePort(request.RemoteAddr)
|
|
||||||
if peerIP != "" {
|
|
||||||
attrs = append(attrs, NetPeerIPKey.String(peerIP))
|
|
||||||
}
|
|
||||||
if peerName != "" {
|
|
||||||
attrs = append(attrs, NetPeerNameKey.String(peerName))
|
|
||||||
}
|
|
||||||
if peerPort != 0 {
|
|
||||||
attrs = append(attrs, NetPeerPortKey.Int(peerPort))
|
|
||||||
}
|
|
||||||
|
|
||||||
hostIP, hostName, hostPort := "", "", 0
|
|
||||||
for _, someHost := range []string{request.Host, request.Header.Get("Host"), request.URL.Host} {
|
|
||||||
hostIP, hostName, hostPort = hostIPNamePort(someHost)
|
|
||||||
if hostIP != "" || hostName != "" || hostPort != 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if hostIP != "" {
|
|
||||||
attrs = append(attrs, NetHostIPKey.String(hostIP))
|
|
||||||
}
|
|
||||||
if hostName != "" {
|
|
||||||
attrs = append(attrs, NetHostNameKey.String(hostName))
|
|
||||||
}
|
|
||||||
if hostPort != 0 {
|
|
||||||
attrs = append(attrs, NetHostPortKey.Int(hostPort))
|
|
||||||
}
|
|
||||||
|
|
||||||
return attrs
|
|
||||||
}
|
|
||||||
|
|
||||||
// hostIPNamePort extracts the IP address, name and (optional) port from hostWithPort.
|
|
||||||
// It handles both IPv4 and IPv6 addresses. If the host portion is not recognized
|
|
||||||
// as a valid IPv4 or IPv6 address, the `ip` result will be empty and the
|
|
||||||
// host portion will instead be returned in `name`.
|
|
||||||
func hostIPNamePort(hostWithPort string) (ip string, name string, port int) {
|
|
||||||
var (
|
|
||||||
hostPart, portPart string
|
|
||||||
parsedPort uint64
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
if hostPart, portPart, err = net.SplitHostPort(hostWithPort); err != nil {
|
|
||||||
hostPart, portPart = hostWithPort, ""
|
|
||||||
}
|
|
||||||
if parsedIP := net.ParseIP(hostPart); parsedIP != nil {
|
|
||||||
ip = parsedIP.String()
|
|
||||||
} else {
|
|
||||||
name = hostPart
|
|
||||||
}
|
|
||||||
if parsedPort, err = strconv.ParseUint(portPart, 10, 16); err == nil {
|
|
||||||
port = int(parsedPort)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// EndUserAttributesFromHTTPRequest generates attributes of the
|
// EndUserAttributesFromHTTPRequest generates attributes of the
|
||||||
// enduser namespace as specified by the OpenTelemetry specification
|
// enduser namespace as specified by the OpenTelemetry specification
|
||||||
// for a span.
|
// for a span.
|
||||||
func EndUserAttributesFromHTTPRequest(request *http.Request) []attribute.KeyValue {
|
func EndUserAttributesFromHTTPRequest(request *http.Request) []attribute.KeyValue {
|
||||||
if username, _, ok := request.BasicAuth(); ok {
|
return sc.EndUserAttributesFromHTTPRequest(request)
|
||||||
return []attribute.KeyValue{EnduserIDKey.String(username)}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTTPClientAttributesFromHTTPRequest generates attributes of the
|
// HTTPClientAttributesFromHTTPRequest generates attributes of the
|
||||||
// http namespace as specified by the OpenTelemetry specification for
|
// http namespace as specified by the OpenTelemetry specification for
|
||||||
// a span on the client side.
|
// a span on the client side.
|
||||||
func HTTPClientAttributesFromHTTPRequest(request *http.Request) []attribute.KeyValue {
|
func HTTPClientAttributesFromHTTPRequest(request *http.Request) []attribute.KeyValue {
|
||||||
attrs := []attribute.KeyValue{}
|
return sc.HTTPClientAttributesFromHTTPRequest(request)
|
||||||
|
|
||||||
if request.Method != "" {
|
|
||||||
attrs = append(attrs, HTTPMethodKey.String(request.Method))
|
|
||||||
} else {
|
|
||||||
attrs = append(attrs, HTTPMethodKey.String(http.MethodGet))
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove any username/password info that may be in the URL
|
|
||||||
// before adding it to the attributes
|
|
||||||
userinfo := request.URL.User
|
|
||||||
request.URL.User = nil
|
|
||||||
|
|
||||||
attrs = append(attrs, HTTPURLKey.String(request.URL.String()))
|
|
||||||
|
|
||||||
// restore any username/password info that was removed
|
|
||||||
request.URL.User = userinfo
|
|
||||||
|
|
||||||
return append(attrs, httpCommonAttributesFromHTTPRequest(request)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func httpCommonAttributesFromHTTPRequest(request *http.Request) []attribute.KeyValue {
|
|
||||||
attrs := []attribute.KeyValue{}
|
|
||||||
if ua := request.UserAgent(); ua != "" {
|
|
||||||
attrs = append(attrs, HTTPUserAgentKey.String(ua))
|
|
||||||
}
|
|
||||||
if request.ContentLength > 0 {
|
|
||||||
attrs = append(attrs, HTTPRequestContentLengthKey.Int64(request.ContentLength))
|
|
||||||
}
|
|
||||||
|
|
||||||
return append(attrs, httpBasicAttributesFromHTTPRequest(request)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func httpBasicAttributesFromHTTPRequest(request *http.Request) []attribute.KeyValue {
|
|
||||||
// as these attributes are used by HTTPServerMetricAttributesFromHTTPRequest, they should be low-cardinality
|
|
||||||
attrs := []attribute.KeyValue{}
|
|
||||||
|
|
||||||
if request.TLS != nil {
|
|
||||||
attrs = append(attrs, HTTPSchemeHTTPS)
|
|
||||||
} else {
|
|
||||||
attrs = append(attrs, HTTPSchemeHTTP)
|
|
||||||
}
|
|
||||||
|
|
||||||
if request.Host != "" {
|
|
||||||
attrs = append(attrs, HTTPHostKey.String(request.Host))
|
|
||||||
} else if request.URL != nil && request.URL.Host != "" {
|
|
||||||
attrs = append(attrs, HTTPHostKey.String(request.URL.Host))
|
|
||||||
}
|
|
||||||
|
|
||||||
flavor := ""
|
|
||||||
if request.ProtoMajor == 1 {
|
|
||||||
flavor = fmt.Sprintf("1.%d", request.ProtoMinor)
|
|
||||||
} else if request.ProtoMajor == 2 {
|
|
||||||
flavor = "2"
|
|
||||||
}
|
|
||||||
if flavor != "" {
|
|
||||||
attrs = append(attrs, HTTPFlavorKey.String(flavor))
|
|
||||||
}
|
|
||||||
|
|
||||||
return attrs
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTTPServerMetricAttributesFromHTTPRequest generates low-cardinality attributes
|
// HTTPServerMetricAttributesFromHTTPRequest generates low-cardinality attributes
|
||||||
// to be used with server-side HTTP metrics.
|
// to be used with server-side HTTP metrics.
|
||||||
func HTTPServerMetricAttributesFromHTTPRequest(serverName string, request *http.Request) []attribute.KeyValue {
|
func HTTPServerMetricAttributesFromHTTPRequest(serverName string, request *http.Request) []attribute.KeyValue {
|
||||||
attrs := []attribute.KeyValue{}
|
return sc.HTTPServerMetricAttributesFromHTTPRequest(serverName, request)
|
||||||
if serverName != "" {
|
|
||||||
attrs = append(attrs, HTTPServerNameKey.String(serverName))
|
|
||||||
}
|
|
||||||
return append(attrs, httpBasicAttributesFromHTTPRequest(request)...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTTPServerAttributesFromHTTPRequest generates attributes of the
|
// HTTPServerAttributesFromHTTPRequest generates attributes of the
|
||||||
@@ -199,116 +90,25 @@ func HTTPServerMetricAttributesFromHTTPRequest(serverName string, request *http.
|
|||||||
// a span on the server side. Currently, only basic authentication is
|
// a span on the server side. Currently, only basic authentication is
|
||||||
// supported.
|
// supported.
|
||||||
func HTTPServerAttributesFromHTTPRequest(serverName, route string, request *http.Request) []attribute.KeyValue {
|
func HTTPServerAttributesFromHTTPRequest(serverName, route string, request *http.Request) []attribute.KeyValue {
|
||||||
attrs := []attribute.KeyValue{
|
return sc.HTTPServerAttributesFromHTTPRequest(serverName, route, request)
|
||||||
HTTPMethodKey.String(request.Method),
|
|
||||||
HTTPTargetKey.String(request.RequestURI),
|
|
||||||
}
|
|
||||||
|
|
||||||
if serverName != "" {
|
|
||||||
attrs = append(attrs, HTTPServerNameKey.String(serverName))
|
|
||||||
}
|
|
||||||
if route != "" {
|
|
||||||
attrs = append(attrs, HTTPRouteKey.String(route))
|
|
||||||
}
|
|
||||||
if values, ok := request.Header["X-Forwarded-For"]; ok && len(values) > 0 {
|
|
||||||
if addresses := strings.SplitN(values[0], ",", 2); len(addresses) > 0 {
|
|
||||||
attrs = append(attrs, HTTPClientIPKey.String(addresses[0]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return append(attrs, httpCommonAttributesFromHTTPRequest(request)...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTTPAttributesFromHTTPStatusCode generates attributes of the http
|
// HTTPAttributesFromHTTPStatusCode generates attributes of the http
|
||||||
// namespace as specified by the OpenTelemetry specification for a
|
// namespace as specified by the OpenTelemetry specification for a
|
||||||
// span.
|
// span.
|
||||||
func HTTPAttributesFromHTTPStatusCode(code int) []attribute.KeyValue {
|
func HTTPAttributesFromHTTPStatusCode(code int) []attribute.KeyValue {
|
||||||
attrs := []attribute.KeyValue{
|
return sc.HTTPAttributesFromHTTPStatusCode(code)
|
||||||
HTTPStatusCodeKey.Int(code),
|
|
||||||
}
|
|
||||||
return attrs
|
|
||||||
}
|
|
||||||
|
|
||||||
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},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SpanStatusFromHTTPStatusCode generates a status code and a message
|
// SpanStatusFromHTTPStatusCode generates a status code and a message
|
||||||
// as specified by the OpenTelemetry specification for a span.
|
// as specified by the OpenTelemetry specification for a span.
|
||||||
func SpanStatusFromHTTPStatusCode(code int) (codes.Code, string) {
|
func SpanStatusFromHTTPStatusCode(code int) (codes.Code, string) {
|
||||||
spanCode, valid := validateHTTPStatusCode(code)
|
return internal.SpanStatusFromHTTPStatusCode(code)
|
||||||
if !valid {
|
|
||||||
return spanCode, fmt.Sprintf("Invalid HTTP status code %d", code)
|
|
||||||
}
|
|
||||||
return spanCode, ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SpanStatusFromHTTPStatusCodeAndSpanKind generates a status code and a message
|
// SpanStatusFromHTTPStatusCodeAndSpanKind generates a status code and a message
|
||||||
// as specified by the OpenTelemetry specification for a span.
|
// as specified by the OpenTelemetry specification for a span.
|
||||||
// Exclude 4xx for SERVER to set the appropriate status.
|
// Exclude 4xx for SERVER to set the appropriate status.
|
||||||
func SpanStatusFromHTTPStatusCodeAndSpanKind(code int, spanKind trace.SpanKind) (codes.Code, string) {
|
func SpanStatusFromHTTPStatusCodeAndSpanKind(code int, spanKind trace.SpanKind) (codes.Code, string) {
|
||||||
spanCode, valid := validateHTTPStatusCode(code)
|
return internal.SpanStatusFromHTTPStatusCodeAndSpanKind(code, spanKind)
|
||||||
if !valid {
|
|
||||||
return spanCode, fmt.Sprintf("Invalid HTTP status code %d", code)
|
|
||||||
}
|
|
||||||
category := code / 100
|
|
||||||
if spanKind == trace.SpanKindServer && category == 4 {
|
|
||||||
return codes.Unset, ""
|
|
||||||
}
|
|
||||||
return spanCode, ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user