mirror of
https://github.com/open-telemetry/opentelemetry-go.git
synced 2025-02-05 13:15:41 +02:00
Refactor Tracestate (#1931)
* Refactor TraceState * Update tracecontext propagator to use new TraceState * Add TraceStateFromKeyValues to oteltest * Use oteltest to test TraceState * Replace IsEmpty with Len for TraceState * Replace ParseTraceState with ParseTraceStateString * Clean up naming * Add immutability test for TraceState * Add changes to changelog * Fixes * Document argument type change in changelog * Address feedback Remove circularity of TestTraceStateLen.
This commit is contained in:
parent
d3b1280863
commit
0eeb8f87e9
14
CHANGELOG.md
14
CHANGELOG.md
@ -26,6 +26,10 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
|
||||
This type can be used as a testing replacement for the `SpanSnapshot` that was removed from the `go.opentelemetry.io/otel/sdk/trace` package. (#1873)
|
||||
- Adds support for scheme in `OTEL_EXPORTER_OTLP_ENDPOINT` according to the spec. (#1886)
|
||||
- An example of using OpenTelemetry Go as a trace context forwarder. (#1912)
|
||||
- `ParseTraceState` is added to the `go.opentelemetry.io/otel/trace` package.
|
||||
It can be used to decode a `TraceState` from a `tracestate` header string value. (#1937)
|
||||
- The `Len` method is added to the `TraceState` type in the `go.opentelemetry.io/otel/trace` package.
|
||||
This method returns the number of list-members the `TraceState` holds. (#1937)
|
||||
|
||||
### Changed
|
||||
|
||||
@ -47,6 +51,14 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
|
||||
- The `"go.opentelemetry.io/otel".Tracer` function now accepts tracer options. (#1902)
|
||||
- Move the `go.opentelemetry.io/otel/unit` package to `go.opentelemetry.io/otel/metric/unit`. (#1903)
|
||||
- Refactor option types according to the contribution style guide. (#1882)
|
||||
- Move the `go.opentelemetry.io/otel/trace.TraceStateFromKeyValues` function to the `go.opentelemetry.io/otel/oteltest` package.
|
||||
This function is preserved for testing purposes where it may be useful to create a `TraceState` from `attribute.KeyValue`s, but it is not intended for production use.
|
||||
The new `ParseTraceState` function should be used to create a `TraceState`. (#1931)
|
||||
- The `MarshalJSON` method of the `go.opentelemetry.io/otel/trace.TraceState` type is updated to marshal the type in to the string representation of the `TraceState`. (#1931)
|
||||
- The `TraceState.Delete` method from the `go.opentelemetry.io/otel/trace` package no longer returns an error in addition to a `TraceState`. (#1931)
|
||||
- The `Get` method of the `TraceState` type from the `go.opentelemetry.io/otel/trace` package has been updated to accept a `string` instead of an `attribute.Key` type. (#1931)
|
||||
- The `Insert` method of the `TraceState` type from the `go.opentelemetry.io/otel/trace` package has been updated to accept a pair of `string`s instead of an `attribute.KeyValue` type. (#1931)
|
||||
- The `Delete` method of the `TraceState` type from the `go.opentelemetry.io/otel/trace` package has been updated to accept a `string` instead of an `attribute.Key` type. (#1931)
|
||||
|
||||
### Deprecated
|
||||
|
||||
@ -66,6 +78,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
|
||||
Using the same tracer that created a span introduces the error where an instrumentation library's `Tracer` is used by other code instead of their own.
|
||||
The `"go.opentelemetry.io/otel".Tracer` function or a `TracerProvider` should be used to acquire a library specific `Tracer` instead. (#1900)
|
||||
- The `http.url` attribute generated by `HTTPClientAttributesFromHTTPRequest` will no longer include username or password information. (#1919)
|
||||
- The `IsEmpty` method of the `TraceState` type in the `go.opentelemetry.io/otel/trace` package is removed in favor of using the added `TraceState.Len` method. (#1931)
|
||||
|
||||
### Fixed
|
||||
|
||||
@ -73,6 +86,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
|
||||
- The `Shutdown` method of the simple `SpanProcessor` in the `go.opentelemetry.io/otel/sdk/trace` package now honors the context deadline or cancellation. (#1616, #1856)
|
||||
- BatchSpanProcessor now drops span batches that failed to be exported. (#1860)
|
||||
- Use `http://localhost:14268/api/traces` as default Jaeger collector endpoint instead of `http://localhost:14250`. (#1898)
|
||||
- Allow trailing and leading whitespace in the parsing of a `tracestate` header. (#1931)
|
||||
|
||||
### Security
|
||||
|
||||
|
@ -13,6 +13,7 @@ require (
|
||||
github.com/stretchr/testify v1.7.0
|
||||
go.opentelemetry.io/otel v0.20.0
|
||||
go.opentelemetry.io/otel/metric v0.20.0
|
||||
go.opentelemetry.io/otel/oteltest v0.20.0
|
||||
go.opentelemetry.io/otel/sdk v0.20.0
|
||||
go.opentelemetry.io/otel/sdk/export/metric v0.20.0
|
||||
go.opentelemetry.io/otel/sdk/metric v0.20.0
|
||||
|
@ -25,6 +25,7 @@ import (
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/oteltest"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
tracepb "go.opentelemetry.io/proto/otlp/trace/v1"
|
||||
|
||||
@ -199,7 +200,7 @@ func TestSpanData(t *testing.T) {
|
||||
// March 31, 2020 5:01:26 1234nanos (UTC)
|
||||
startTime := time.Unix(1585674086, 1234)
|
||||
endTime := startTime.Add(10 * time.Second)
|
||||
traceState, _ := trace.TraceStateFromKeyValues(attribute.String("key1", "val1"), attribute.String("key2", "val2"))
|
||||
traceState, _ := oteltest.TraceStateFromKeyValues(attribute.String("key1", "val1"), attribute.String("key2", "val2"))
|
||||
spanData := tracetest.SpanStub{
|
||||
SpanContext: trace.NewSpanContext(trace.SpanContextConfig{
|
||||
TraceID: trace.TraceID{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F},
|
||||
|
@ -11,6 +11,7 @@ require (
|
||||
github.com/stretchr/testify v1.7.0
|
||||
go.opentelemetry.io/otel v0.20.0
|
||||
go.opentelemetry.io/otel/metric v0.20.0
|
||||
go.opentelemetry.io/otel/oteltest v0.20.0
|
||||
go.opentelemetry.io/otel/sdk v0.20.0
|
||||
go.opentelemetry.io/otel/sdk/export/metric v0.20.0
|
||||
go.opentelemetry.io/otel/sdk/metric v0.20.0
|
||||
|
@ -27,6 +27,7 @@ import (
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/codes"
|
||||
"go.opentelemetry.io/otel/exporters/stdout"
|
||||
"go.opentelemetry.io/otel/oteltest"
|
||||
"go.opentelemetry.io/otel/sdk/resource"
|
||||
tracesdk "go.opentelemetry.io/otel/sdk/trace"
|
||||
"go.opentelemetry.io/otel/sdk/trace/tracetest"
|
||||
@ -45,7 +46,7 @@ func TestExporter_ExportSpan(t *testing.T) {
|
||||
now := time.Now()
|
||||
traceID, _ := trace.TraceIDFromHex("0102030405060708090a0b0c0d0e0f10")
|
||||
spanID, _ := trace.SpanIDFromHex("0102030405060708")
|
||||
traceState, _ := trace.TraceStateFromKeyValues(attribute.String("key", "val"))
|
||||
traceState, _ := oteltest.TraceStateFromKeyValues(attribute.String("key", "val"))
|
||||
keyValue := "value"
|
||||
doubleValue := 123.456
|
||||
resource := resource.NewWithAttributes(attribute.String("rk1", "rv11"))
|
||||
@ -90,22 +91,14 @@ func TestExporter_ExportSpan(t *testing.T) {
|
||||
"TraceID": "0102030405060708090a0b0c0d0e0f10",
|
||||
"SpanID": "0102030405060708",
|
||||
"TraceFlags": "00",
|
||||
"TraceState": [
|
||||
{
|
||||
"Key": "key",
|
||||
"Value": {
|
||||
"Type": "STRING",
|
||||
"Value": "val"
|
||||
}
|
||||
}
|
||||
],
|
||||
"TraceState": "key=val",
|
||||
"Remote": false
|
||||
},
|
||||
"Parent": {
|
||||
"TraceID": "00000000000000000000000000000000",
|
||||
"SpanID": "0000000000000000",
|
||||
"TraceFlags": "00",
|
||||
"TraceState": null,
|
||||
"TraceState": "",
|
||||
"Remote": false
|
||||
},
|
||||
"SpanKind": 1,
|
||||
|
@ -47,6 +47,7 @@ replace go.opentelemetry.io/otel/sdk/metric => ../sdk/metric
|
||||
replace go.opentelemetry.io/otel/trace => ../trace
|
||||
|
||||
require (
|
||||
github.com/stretchr/testify v1.7.0
|
||||
go.opentelemetry.io/otel v0.20.0
|
||||
go.opentelemetry.io/otel/metric v0.20.0
|
||||
go.opentelemetry.io/otel/trace v0.20.0
|
||||
|
41
oteltest/tracestate.go
Normal file
41
oteltest/tracestate.go
Normal file
@ -0,0 +1,41 @@
|
||||
// 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 oteltest
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
// TraceStateFromKeyValues is a convenience function to create a
|
||||
// trace.TraceState from provided key/value pairs. There is no inverse to this
|
||||
// function, returning attributes from a TraceState, because the TraceState,
|
||||
// by definition from the W3C tracecontext specification, stores values as
|
||||
// opaque strings. Therefore, it is not possible to decode the original value
|
||||
// type from TraceState. Be sure to not use this outside of testing purposes.
|
||||
func TraceStateFromKeyValues(kvs ...attribute.KeyValue) (trace.TraceState, error) {
|
||||
if len(kvs) == 0 {
|
||||
return trace.TraceState{}, nil
|
||||
}
|
||||
|
||||
members := make([]string, len(kvs))
|
||||
for i, kv := range kvs {
|
||||
members[i] = fmt.Sprintf("%s=%s", string(kv.Key), kv.Value.Emit())
|
||||
}
|
||||
return trace.ParseTraceState(strings.Join(members, ","))
|
||||
}
|
41
oteltest/tracestate_test.go
Normal file
41
oteltest/tracestate_test.go
Normal file
@ -0,0 +1,41 @@
|
||||
// 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 oteltest
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
)
|
||||
|
||||
func TestTraceStateFromKeyValues(t *testing.T) {
|
||||
ts, err := TraceStateFromKeyValues()
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 0, ts.Len(), "empty attributes creats zero value TraceState")
|
||||
|
||||
ts, err = TraceStateFromKeyValues(
|
||||
attribute.String("key0", "string"),
|
||||
attribute.Bool("key1", true),
|
||||
attribute.Int64("key2", 1),
|
||||
attribute.Float64("key3", 1.1),
|
||||
attribute.Array("key4", []int{1, 1}),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
expected := "key0=string,key1=true,key2=1,key3=1.1,key4=[1 1]"
|
||||
assert.Equal(t, expected, ts.String())
|
||||
}
|
@ -19,9 +19,7 @@ import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
@ -139,7 +137,10 @@ func (tc TraceContext) extract(carrier TextMapCarrier) trace.SpanContext {
|
||||
// Clear all flags other than the trace-context supported sampling bit.
|
||||
scc.TraceFlags = trace.TraceFlags(opts[0]) & trace.FlagsSampled
|
||||
|
||||
scc.TraceState = parseTraceState(carrier.Get(tracestateHeader))
|
||||
// Ignore the error returned here. Failure to parse tracestate MUST NOT
|
||||
// affect the parsing of traceparent according to the W3C tracecontext
|
||||
// specification.
|
||||
scc.TraceState, _ = trace.ParseTraceState(carrier.Get(tracestateHeader))
|
||||
scc.Remote = true
|
||||
|
||||
sc := trace.NewSpanContext(scc)
|
||||
@ -154,25 +155,3 @@ func (tc TraceContext) extract(carrier TextMapCarrier) trace.SpanContext {
|
||||
func (tc TraceContext) Fields() []string {
|
||||
return []string{traceparentHeader, tracestateHeader}
|
||||
}
|
||||
|
||||
func parseTraceState(in string) trace.TraceState {
|
||||
if in == "" {
|
||||
return trace.TraceState{}
|
||||
}
|
||||
|
||||
kvs := []attribute.KeyValue{}
|
||||
for _, entry := range strings.Split(in, ",") {
|
||||
parts := strings.SplitN(entry, "=", 2)
|
||||
if len(parts) != 2 {
|
||||
// Parse failure, abort!
|
||||
return trace.TraceState{}
|
||||
}
|
||||
kvs = append(kvs, attribute.String(parts[0], parts[1]))
|
||||
}
|
||||
|
||||
// Ignoring error here as "failure to parse tracestate MUST NOT
|
||||
// affect the parsing of traceparent."
|
||||
// https://www.w3.org/TR/trace-context/#tracestate-header
|
||||
ts, _ := trace.TraceStateFromKeyValues(kvs...)
|
||||
return ts
|
||||
}
|
||||
|
@ -288,7 +288,7 @@ func TestTraceStatePropagation(t *testing.T) {
|
||||
prop := propagation.TraceContext{}
|
||||
stateHeader := "tracestate"
|
||||
parentHeader := "traceparent"
|
||||
state, err := trace.TraceStateFromKeyValues(attribute.String("key1", "value1"), attribute.String("key2", "value2"))
|
||||
state, err := oteltest.TraceStateFromKeyValues(attribute.String("key1", "value1"), attribute.String("key2", "value2"))
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to construct expected TraceState: %s", err.Error())
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/oteltest"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
@ -240,7 +241,7 @@ func TestTracestateIsPassed(t *testing.T) {
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
traceState, err := trace.TraceStateFromKeyValues(attribute.String("k", "v"))
|
||||
traceState, err := oteltest.TraceStateFromKeyValues(attribute.String("k", "v"))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
@ -354,7 +354,7 @@ func TestStartSpanWithParent(t *testing.T) {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
ts, err := trace.TraceStateFromKeyValues(attribute.String("k", "v"))
|
||||
ts, err := oteltest.TraceStateFromKeyValues(attribute.String("k", "v"))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@ -1597,13 +1597,13 @@ func TestSamplerTraceState(t *testing.T) {
|
||||
makeInserter := func(k attribute.KeyValue, prefix string) Sampler {
|
||||
return &stateSampler{
|
||||
prefix: prefix,
|
||||
f: func(t trace.TraceState) trace.TraceState { return mustTS(t.Insert(k)) },
|
||||
f: func(t trace.TraceState) trace.TraceState { return mustTS(t.Insert(string(k.Key), k.Value.Emit())) },
|
||||
}
|
||||
}
|
||||
makeDeleter := func(k attribute.Key, prefix string) Sampler {
|
||||
return &stateSampler{
|
||||
prefix: prefix,
|
||||
f: func(t trace.TraceState) trace.TraceState { return mustTS(t.Delete(k)) },
|
||||
f: func(t trace.TraceState) trace.TraceState { return t.Delete(string(k)) },
|
||||
}
|
||||
}
|
||||
clearer := func(prefix string) Sampler {
|
||||
@ -1624,55 +1624,55 @@ func TestSamplerTraceState(t *testing.T) {
|
||||
{
|
||||
name: "alwaysOn",
|
||||
sampler: AlwaysSample(),
|
||||
input: mustTS(trace.TraceStateFromKeyValues(kv1)),
|
||||
want: mustTS(trace.TraceStateFromKeyValues(kv1)),
|
||||
input: mustTS(oteltest.TraceStateFromKeyValues(kv1)),
|
||||
want: mustTS(oteltest.TraceStateFromKeyValues(kv1)),
|
||||
exportSpan: true,
|
||||
},
|
||||
{
|
||||
name: "alwaysOff",
|
||||
sampler: NeverSample(),
|
||||
input: mustTS(trace.TraceStateFromKeyValues(kv1)),
|
||||
want: mustTS(trace.TraceStateFromKeyValues(kv1)),
|
||||
input: mustTS(oteltest.TraceStateFromKeyValues(kv1)),
|
||||
want: mustTS(oteltest.TraceStateFromKeyValues(kv1)),
|
||||
exportSpan: false,
|
||||
},
|
||||
{
|
||||
name: "insertKeySampled",
|
||||
sampler: makeInserter(kv2, "span"),
|
||||
spanName: "span0",
|
||||
input: mustTS(trace.TraceStateFromKeyValues(kv1)),
|
||||
want: mustTS(trace.TraceStateFromKeyValues(kv2, kv1)),
|
||||
input: mustTS(oteltest.TraceStateFromKeyValues(kv1)),
|
||||
want: mustTS(oteltest.TraceStateFromKeyValues(kv2, kv1)),
|
||||
exportSpan: true,
|
||||
},
|
||||
{
|
||||
name: "insertKeyDropped",
|
||||
sampler: makeInserter(kv2, "span"),
|
||||
spanName: "nospan0",
|
||||
input: mustTS(trace.TraceStateFromKeyValues(kv1)),
|
||||
want: mustTS(trace.TraceStateFromKeyValues(kv2, kv1)),
|
||||
input: mustTS(oteltest.TraceStateFromKeyValues(kv1)),
|
||||
want: mustTS(oteltest.TraceStateFromKeyValues(kv2, kv1)),
|
||||
exportSpan: false,
|
||||
},
|
||||
{
|
||||
name: "deleteKeySampled",
|
||||
sampler: makeDeleter(k1, "span"),
|
||||
spanName: "span0",
|
||||
input: mustTS(trace.TraceStateFromKeyValues(kv1, kv2)),
|
||||
want: mustTS(trace.TraceStateFromKeyValues(kv2)),
|
||||
input: mustTS(oteltest.TraceStateFromKeyValues(kv1, kv2)),
|
||||
want: mustTS(oteltest.TraceStateFromKeyValues(kv2)),
|
||||
exportSpan: true,
|
||||
},
|
||||
{
|
||||
name: "deleteKeyDropped",
|
||||
sampler: makeDeleter(k1, "span"),
|
||||
spanName: "nospan0",
|
||||
input: mustTS(trace.TraceStateFromKeyValues(kv1, kv2, kv3)),
|
||||
want: mustTS(trace.TraceStateFromKeyValues(kv2, kv3)),
|
||||
input: mustTS(oteltest.TraceStateFromKeyValues(kv1, kv2, kv3)),
|
||||
want: mustTS(oteltest.TraceStateFromKeyValues(kv2, kv3)),
|
||||
exportSpan: false,
|
||||
},
|
||||
{
|
||||
name: "clearer",
|
||||
sampler: clearer("span"),
|
||||
spanName: "span0",
|
||||
input: mustTS(trace.TraceStateFromKeyValues(kv1, kv3)),
|
||||
want: mustTS(trace.TraceStateFromKeyValues()),
|
||||
input: mustTS(oteltest.TraceStateFromKeyValues(kv1, kv3)),
|
||||
want: mustTS(oteltest.TraceStateFromKeyValues()),
|
||||
exportSpan: true,
|
||||
},
|
||||
}
|
||||
|
163
trace/trace.go
163
trace/trace.go
@ -19,8 +19,6 @@ import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/codes"
|
||||
@ -38,18 +36,6 @@ const (
|
||||
|
||||
errInvalidSpanIDLength errorConst = "hex encoded span-id must have length equals to 16"
|
||||
errNilSpanID errorConst = "span-id can't be all zero"
|
||||
|
||||
// based on the W3C Trace Context specification, see https://www.w3.org/TR/trace-context-1/#tracestate-header
|
||||
traceStateKeyFormat = `[a-z][_0-9a-z\-\*\/]{0,255}`
|
||||
traceStateKeyFormatWithMultiTenantVendor = `[a-z0-9][_0-9a-z\-\*\/]{0,240}@[a-z][_0-9a-z\-\*\/]{0,13}`
|
||||
traceStateValueFormat = `[\x20-\x2b\x2d-\x3c\x3e-\x7e]{0,255}[\x21-\x2b\x2d-\x3c\x3e-\x7e]`
|
||||
|
||||
traceStateMaxListMembers = 32
|
||||
|
||||
errInvalidTraceStateKeyValue errorConst = "provided key or value is not valid according to the" +
|
||||
" W3C Trace Context specification"
|
||||
errInvalidTraceStateMembersNumber errorConst = "trace state would exceed the maximum limit of members (32)"
|
||||
errInvalidTraceStateDuplicate errorConst = "trace state key/value pairs with duplicate keys provided"
|
||||
)
|
||||
|
||||
type errorConst string
|
||||
@ -165,153 +151,6 @@ func decodeHex(h string, b []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// TraceState provides additional vendor-specific trace identification information
|
||||
// across different distributed tracing systems. It represents an immutable list consisting
|
||||
// of key/value pairs. There can be a maximum of 32 entries in the list.
|
||||
//
|
||||
// Key and value of each list member must be valid according to the W3C Trace Context specification
|
||||
// (see https://www.w3.org/TR/trace-context-1/#key and https://www.w3.org/TR/trace-context-1/#value
|
||||
// respectively).
|
||||
//
|
||||
// Trace state must be valid according to the W3C Trace Context specification at all times. All
|
||||
// mutating operations validate their input and, in case of valid parameters, return a new TraceState.
|
||||
type TraceState struct { //nolint:golint
|
||||
// TODO @matej-g: Consider implementing this as attribute.Set, see
|
||||
// comment https://github.com/open-telemetry/opentelemetry-go/pull/1340#discussion_r540599226
|
||||
kvs []attribute.KeyValue
|
||||
}
|
||||
|
||||
var _ json.Marshaler = TraceState{}
|
||||
var _ json.Marshaler = SpanContext{}
|
||||
|
||||
var keyFormatRegExp = regexp.MustCompile(
|
||||
`^((` + traceStateKeyFormat + `)|(` + traceStateKeyFormatWithMultiTenantVendor + `))$`,
|
||||
)
|
||||
var valueFormatRegExp = regexp.MustCompile(`^(` + traceStateValueFormat + `)$`)
|
||||
|
||||
// MarshalJSON implements a custom marshal function to encode trace state.
|
||||
func (ts TraceState) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(ts.kvs)
|
||||
}
|
||||
|
||||
// String returns trace state as a string valid according to the
|
||||
// W3C Trace Context specification.
|
||||
func (ts TraceState) String() string {
|
||||
var sb strings.Builder
|
||||
|
||||
for i, kv := range ts.kvs {
|
||||
sb.WriteString((string)(kv.Key))
|
||||
sb.WriteByte('=')
|
||||
sb.WriteString(kv.Value.Emit())
|
||||
|
||||
if i != len(ts.kvs)-1 {
|
||||
sb.WriteByte(',')
|
||||
}
|
||||
}
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// Get returns a value for given key from the trace state.
|
||||
// If no key is found or provided key is invalid, returns an empty value.
|
||||
func (ts TraceState) Get(key attribute.Key) attribute.Value {
|
||||
if !isTraceStateKeyValid(key) {
|
||||
return attribute.Value{}
|
||||
}
|
||||
|
||||
for _, kv := range ts.kvs {
|
||||
if kv.Key == key {
|
||||
return kv.Value
|
||||
}
|
||||
}
|
||||
|
||||
return attribute.Value{}
|
||||
}
|
||||
|
||||
// Insert adds a new key/value, if one doesn't exists; otherwise updates the existing entry.
|
||||
// The new or updated entry is always inserted at the beginning of the TraceState, i.e.
|
||||
// on the left side, as per the W3C Trace Context specification requirement.
|
||||
func (ts TraceState) Insert(entry attribute.KeyValue) (TraceState, error) {
|
||||
if !isTraceStateKeyValueValid(entry) {
|
||||
return ts, errInvalidTraceStateKeyValue
|
||||
}
|
||||
|
||||
ckvs := ts.copyKVsAndDeleteEntry(entry.Key)
|
||||
if len(ckvs)+1 > traceStateMaxListMembers {
|
||||
return ts, errInvalidTraceStateMembersNumber
|
||||
}
|
||||
|
||||
ckvs = append(ckvs, attribute.KeyValue{})
|
||||
copy(ckvs[1:], ckvs)
|
||||
ckvs[0] = entry
|
||||
|
||||
return TraceState{ckvs}, nil
|
||||
}
|
||||
|
||||
// Delete removes specified entry from the trace state.
|
||||
func (ts TraceState) Delete(key attribute.Key) (TraceState, error) {
|
||||
if !isTraceStateKeyValid(key) {
|
||||
return ts, errInvalidTraceStateKeyValue
|
||||
}
|
||||
|
||||
return TraceState{ts.copyKVsAndDeleteEntry(key)}, nil
|
||||
}
|
||||
|
||||
// IsEmpty returns true if the TraceState does not contain any entries
|
||||
func (ts TraceState) IsEmpty() bool {
|
||||
return len(ts.kvs) == 0
|
||||
}
|
||||
|
||||
func (ts TraceState) copyKVsAndDeleteEntry(key attribute.Key) []attribute.KeyValue {
|
||||
ckvs := make([]attribute.KeyValue, len(ts.kvs))
|
||||
copy(ckvs, ts.kvs)
|
||||
for i, kv := range ts.kvs {
|
||||
if kv.Key == key {
|
||||
ckvs = append(ckvs[:i], ckvs[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return ckvs
|
||||
}
|
||||
|
||||
// TraceStateFromKeyValues is a convenience method to create a new TraceState from
|
||||
// provided key/value pairs.
|
||||
func TraceStateFromKeyValues(kvs ...attribute.KeyValue) (TraceState, error) { //nolint:golint
|
||||
if len(kvs) == 0 {
|
||||
return TraceState{}, nil
|
||||
}
|
||||
|
||||
if len(kvs) > traceStateMaxListMembers {
|
||||
return TraceState{}, errInvalidTraceStateMembersNumber
|
||||
}
|
||||
|
||||
km := make(map[attribute.Key]bool)
|
||||
for _, kv := range kvs {
|
||||
if !isTraceStateKeyValueValid(kv) {
|
||||
return TraceState{}, errInvalidTraceStateKeyValue
|
||||
}
|
||||
_, ok := km[kv.Key]
|
||||
if ok {
|
||||
return TraceState{}, errInvalidTraceStateDuplicate
|
||||
}
|
||||
km[kv.Key] = true
|
||||
}
|
||||
|
||||
ckvs := make([]attribute.KeyValue, len(kvs))
|
||||
copy(ckvs, kvs)
|
||||
return TraceState{ckvs}, nil
|
||||
}
|
||||
|
||||
func isTraceStateKeyValid(key attribute.Key) bool {
|
||||
return keyFormatRegExp.MatchString(string(key))
|
||||
}
|
||||
|
||||
func isTraceStateKeyValueValid(kv attribute.KeyValue) bool {
|
||||
return isTraceStateKeyValid(kv.Key) &&
|
||||
valueFormatRegExp.MatchString(kv.Value.Emit())
|
||||
}
|
||||
|
||||
// TraceFlags contains flags that can be set on a SpanContext
|
||||
type TraceFlags byte //nolint:golint
|
||||
|
||||
@ -371,6 +210,8 @@ type SpanContext struct {
|
||||
remote bool
|
||||
}
|
||||
|
||||
var _ json.Marshaler = SpanContext{}
|
||||
|
||||
// IsValid returns if the SpanContext is valid. A valid span context has a
|
||||
// valid TraceID and SpanID.
|
||||
func (sc SpanContext) IsValid() bool {
|
||||
|
@ -15,14 +15,9 @@
|
||||
package trace
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
)
|
||||
|
||||
func TestIsValid(t *testing.T) {
|
||||
@ -370,571 +365,14 @@ func TestSpanKindString(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestTraceStateString(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
traceState TraceState
|
||||
expectedStr string
|
||||
}{
|
||||
{
|
||||
name: "Non-empty trace state",
|
||||
traceState: TraceState{
|
||||
kvs: []attribute.KeyValue{
|
||||
attribute.String("key1", "val1"),
|
||||
attribute.String("key2", "val2"),
|
||||
attribute.String("key3@vendor", "val3"),
|
||||
},
|
||||
},
|
||||
expectedStr: "key1=val1,key2=val2,key3@vendor=val3",
|
||||
},
|
||||
{
|
||||
name: "Empty trace state",
|
||||
traceState: TraceState{},
|
||||
expectedStr: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
assert.Equal(t, tc.expectedStr, tc.traceState.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTraceStateGet(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
traceState TraceState
|
||||
key attribute.Key
|
||||
expectedValue string
|
||||
}{
|
||||
{
|
||||
name: "OK case",
|
||||
traceState: TraceState{kvsWithMaxMembers},
|
||||
key: "key16",
|
||||
expectedValue: "value16",
|
||||
},
|
||||
{
|
||||
name: "Not found",
|
||||
traceState: TraceState{kvsWithMaxMembers},
|
||||
key: "keyxx",
|
||||
expectedValue: "",
|
||||
},
|
||||
{
|
||||
name: "Invalid key",
|
||||
traceState: TraceState{kvsWithMaxMembers},
|
||||
key: "key!",
|
||||
expectedValue: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
kv := tc.traceState.Get(tc.key)
|
||||
assert.Equal(t, tc.expectedValue, kv.AsString())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTraceStateDelete(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
traceState TraceState
|
||||
key attribute.Key
|
||||
expectedTraceState TraceState
|
||||
expectedErr error
|
||||
}{
|
||||
{
|
||||
name: "OK case",
|
||||
traceState: TraceState{
|
||||
kvs: []attribute.KeyValue{
|
||||
attribute.String("key1", "val1"),
|
||||
attribute.String("key2", "val2"),
|
||||
attribute.String("key3", "val3"),
|
||||
},
|
||||
},
|
||||
key: "key2",
|
||||
expectedTraceState: TraceState{
|
||||
kvs: []attribute.KeyValue{
|
||||
attribute.String("key1", "val1"),
|
||||
attribute.String("key3", "val3"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Non-existing key",
|
||||
traceState: TraceState{
|
||||
kvs: []attribute.KeyValue{
|
||||
attribute.String("key1", "val1"),
|
||||
attribute.String("key2", "val2"),
|
||||
attribute.String("key3", "val3"),
|
||||
},
|
||||
},
|
||||
key: "keyx",
|
||||
expectedTraceState: TraceState{
|
||||
kvs: []attribute.KeyValue{
|
||||
attribute.String("key1", "val1"),
|
||||
attribute.String("key2", "val2"),
|
||||
attribute.String("key3", "val3"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Invalid key",
|
||||
traceState: TraceState{
|
||||
kvs: []attribute.KeyValue{
|
||||
attribute.String("key1", "val1"),
|
||||
attribute.String("key2", "val2"),
|
||||
attribute.String("key3", "val3"),
|
||||
},
|
||||
},
|
||||
key: "in va lid",
|
||||
expectedTraceState: TraceState{
|
||||
kvs: []attribute.KeyValue{
|
||||
attribute.String("key1", "val1"),
|
||||
attribute.String("key2", "val2"),
|
||||
attribute.String("key3", "val3"),
|
||||
},
|
||||
},
|
||||
expectedErr: errInvalidTraceStateKeyValue,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
result, err := tc.traceState.Delete(tc.key)
|
||||
if tc.expectedErr != nil {
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, tc.expectedErr, err)
|
||||
assert.Equal(t, tc.traceState, result)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tc.expectedTraceState, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTraceStateInsert(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
traceState TraceState
|
||||
keyValue attribute.KeyValue
|
||||
expectedTraceState TraceState
|
||||
expectedErr error
|
||||
}{
|
||||
{
|
||||
name: "OK case - add new",
|
||||
traceState: TraceState{
|
||||
kvs: []attribute.KeyValue{
|
||||
attribute.String("key1", "val1"),
|
||||
attribute.String("key2", "val2"),
|
||||
attribute.String("key3", "val3"),
|
||||
},
|
||||
},
|
||||
keyValue: attribute.String("key4@vendor", "val4"),
|
||||
expectedTraceState: TraceState{
|
||||
kvs: []attribute.KeyValue{
|
||||
attribute.String("key4@vendor", "val4"),
|
||||
attribute.String("key1", "val1"),
|
||||
attribute.String("key2", "val2"),
|
||||
attribute.String("key3", "val3"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "OK case - replace",
|
||||
traceState: TraceState{
|
||||
kvs: []attribute.KeyValue{
|
||||
attribute.String("key1", "val1"),
|
||||
attribute.String("key2", "val2"),
|
||||
attribute.String("key3", "val3"),
|
||||
},
|
||||
},
|
||||
keyValue: attribute.String("key2", "valX"),
|
||||
expectedTraceState: TraceState{
|
||||
kvs: []attribute.KeyValue{
|
||||
attribute.String("key2", "valX"),
|
||||
attribute.String("key1", "val1"),
|
||||
attribute.String("key3", "val3"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Invalid key/value",
|
||||
traceState: TraceState{
|
||||
kvs: []attribute.KeyValue{
|
||||
attribute.String("key1", "val1"),
|
||||
},
|
||||
},
|
||||
keyValue: attribute.String("key!", "val!"),
|
||||
expectedTraceState: TraceState{
|
||||
kvs: []attribute.KeyValue{
|
||||
attribute.String("key1", "val1"),
|
||||
},
|
||||
},
|
||||
expectedErr: errInvalidTraceStateKeyValue,
|
||||
},
|
||||
{
|
||||
name: "Too many entries",
|
||||
traceState: TraceState{kvsWithMaxMembers},
|
||||
keyValue: attribute.String("keyx", "valx"),
|
||||
expectedTraceState: TraceState{kvsWithMaxMembers},
|
||||
expectedErr: errInvalidTraceStateMembersNumber,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
result, err := tc.traceState.Insert(tc.keyValue)
|
||||
if tc.expectedErr != nil {
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, tc.expectedErr, err)
|
||||
assert.Equal(t, tc.traceState, result)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tc.expectedTraceState, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTraceStateFromKeyValues(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
kvs []attribute.KeyValue
|
||||
expectedTraceState TraceState
|
||||
expectedErr error
|
||||
}{
|
||||
{
|
||||
name: "OK case",
|
||||
kvs: kvsWithMaxMembers,
|
||||
expectedTraceState: TraceState{kvsWithMaxMembers},
|
||||
},
|
||||
{
|
||||
name: "OK case (empty)",
|
||||
expectedTraceState: TraceState{},
|
||||
},
|
||||
{
|
||||
name: "Too many entries",
|
||||
kvs: func() []attribute.KeyValue {
|
||||
kvs := kvsWithMaxMembers
|
||||
kvs = append(kvs, attribute.String("keyx", "valX"))
|
||||
return kvs
|
||||
}(),
|
||||
expectedTraceState: TraceState{},
|
||||
expectedErr: errInvalidTraceStateMembersNumber,
|
||||
},
|
||||
{
|
||||
name: "Duplicate key",
|
||||
kvs: []attribute.KeyValue{
|
||||
attribute.String("key1", "val1"),
|
||||
attribute.String("key1", "val2"),
|
||||
},
|
||||
expectedTraceState: TraceState{},
|
||||
expectedErr: errInvalidTraceStateDuplicate,
|
||||
},
|
||||
{
|
||||
name: "Duplicate key/value",
|
||||
kvs: []attribute.KeyValue{
|
||||
attribute.String("key1", "val1"),
|
||||
attribute.String("key1", "val1"),
|
||||
},
|
||||
expectedTraceState: TraceState{},
|
||||
expectedErr: errInvalidTraceStateDuplicate,
|
||||
},
|
||||
{
|
||||
name: "Invalid key/value",
|
||||
kvs: []attribute.KeyValue{
|
||||
attribute.String("key!", "val!"),
|
||||
},
|
||||
expectedTraceState: TraceState{},
|
||||
expectedErr: errInvalidTraceStateKeyValue,
|
||||
},
|
||||
{
|
||||
name: "Full character set",
|
||||
kvs: []attribute.KeyValue{
|
||||
attribute.String(
|
||||
"abcdefghijklmnopqrstuvwxyz0123456789_-*/",
|
||||
" !\"#$%&'()*+-./0123456789:;<>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~",
|
||||
),
|
||||
},
|
||||
expectedTraceState: TraceState{[]attribute.KeyValue{
|
||||
attribute.String(
|
||||
"abcdefghijklmnopqrstuvwxyz0123456789_-*/",
|
||||
" !\"#$%&'()*+-./0123456789:;<>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~",
|
||||
),
|
||||
}},
|
||||
},
|
||||
{
|
||||
name: "Full character set with vendor",
|
||||
kvs: []attribute.KeyValue{
|
||||
attribute.String(
|
||||
"abcdefghijklmnopqrstuvwxyz0123456789_-*/@a-z0-9_-*/",
|
||||
"!\"#$%&'()*+-./0123456789:;<>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~",
|
||||
),
|
||||
},
|
||||
expectedTraceState: TraceState{[]attribute.KeyValue{
|
||||
attribute.String(
|
||||
"abcdefghijklmnopqrstuvwxyz0123456789_-*/@a-z0-9_-*/",
|
||||
"!\"#$%&'()*+-./0123456789:;<>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~",
|
||||
),
|
||||
}},
|
||||
},
|
||||
{
|
||||
name: "Full character with vendor starting with number",
|
||||
kvs: []attribute.KeyValue{
|
||||
attribute.String(
|
||||
"0123456789_-*/abcdefghijklmnopqrstuvwxyz@a-z0-9_-*/",
|
||||
"!\"#$%&'()*+-./0123456789:;<>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~",
|
||||
),
|
||||
},
|
||||
expectedTraceState: TraceState{[]attribute.KeyValue{
|
||||
attribute.String(
|
||||
"0123456789_-*/abcdefghijklmnopqrstuvwxyz@a-z0-9_-*/",
|
||||
"!\"#$%&'()*+-./0123456789:;<>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~",
|
||||
),
|
||||
}},
|
||||
},
|
||||
{
|
||||
name: "One field",
|
||||
kvs: []attribute.KeyValue{
|
||||
attribute.String("foo", "1"),
|
||||
},
|
||||
expectedTraceState: TraceState{[]attribute.KeyValue{
|
||||
attribute.String("foo", "1"),
|
||||
}},
|
||||
},
|
||||
{
|
||||
name: "Two fields",
|
||||
kvs: []attribute.KeyValue{
|
||||
attribute.String("foo", "1"),
|
||||
attribute.String("bar", "2"),
|
||||
},
|
||||
expectedTraceState: TraceState{[]attribute.KeyValue{
|
||||
attribute.String("foo", "1"),
|
||||
attribute.String("bar", "2"),
|
||||
}},
|
||||
},
|
||||
{
|
||||
name: "Long field key",
|
||||
kvs: []attribute.KeyValue{
|
||||
attribute.String("foo", "1"),
|
||||
attribute.String(
|
||||
"zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz",
|
||||
"1",
|
||||
),
|
||||
},
|
||||
expectedTraceState: TraceState{[]attribute.KeyValue{
|
||||
attribute.String("foo", "1"),
|
||||
attribute.String(
|
||||
"zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz",
|
||||
"1",
|
||||
),
|
||||
}},
|
||||
},
|
||||
{
|
||||
name: "Long field key with vendor",
|
||||
kvs: []attribute.KeyValue{
|
||||
attribute.String("foo", "1"),
|
||||
attribute.String(
|
||||
"ttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttt@vvvvvvvvvvvvvv",
|
||||
"1",
|
||||
),
|
||||
},
|
||||
expectedTraceState: TraceState{[]attribute.KeyValue{
|
||||
attribute.String("foo", "1"),
|
||||
attribute.String(
|
||||
"ttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttt@vvvvvvvvvvvvvv",
|
||||
"1",
|
||||
),
|
||||
}},
|
||||
},
|
||||
{
|
||||
name: "Invalid whitespace value",
|
||||
kvs: []attribute.KeyValue{
|
||||
attribute.String("foo", "1 \t "),
|
||||
},
|
||||
expectedTraceState: TraceState{},
|
||||
expectedErr: errInvalidTraceStateKeyValue,
|
||||
},
|
||||
{
|
||||
name: "Invalid whitespace key",
|
||||
kvs: []attribute.KeyValue{
|
||||
attribute.String(" \t bar", "2"),
|
||||
},
|
||||
expectedTraceState: TraceState{},
|
||||
expectedErr: errInvalidTraceStateKeyValue,
|
||||
},
|
||||
{
|
||||
name: "Empty header value",
|
||||
kvs: []attribute.KeyValue{
|
||||
attribute.String("", ""),
|
||||
},
|
||||
expectedTraceState: TraceState{},
|
||||
expectedErr: errInvalidTraceStateKeyValue,
|
||||
},
|
||||
{
|
||||
name: "Space in key",
|
||||
kvs: []attribute.KeyValue{
|
||||
attribute.String("foo ", "1"),
|
||||
},
|
||||
expectedTraceState: TraceState{},
|
||||
expectedErr: errInvalidTraceStateKeyValue,
|
||||
},
|
||||
{
|
||||
name: "Capitalized key",
|
||||
kvs: []attribute.KeyValue{
|
||||
attribute.String("FOO", "1"),
|
||||
},
|
||||
expectedTraceState: TraceState{},
|
||||
expectedErr: errInvalidTraceStateKeyValue,
|
||||
},
|
||||
{
|
||||
name: "Period in key",
|
||||
kvs: []attribute.KeyValue{
|
||||
attribute.String("foo.bar", "1"),
|
||||
},
|
||||
expectedTraceState: TraceState{},
|
||||
expectedErr: errInvalidTraceStateKeyValue,
|
||||
},
|
||||
{
|
||||
name: "Empty vendor",
|
||||
kvs: []attribute.KeyValue{
|
||||
attribute.String("foo@", "1"),
|
||||
attribute.String("bar", "2"),
|
||||
},
|
||||
expectedTraceState: TraceState{},
|
||||
expectedErr: errInvalidTraceStateKeyValue,
|
||||
},
|
||||
{
|
||||
name: "Empty key for vendor",
|
||||
kvs: []attribute.KeyValue{
|
||||
attribute.String("@foo", "1"),
|
||||
attribute.String("bar", "2"),
|
||||
},
|
||||
expectedTraceState: TraceState{},
|
||||
expectedErr: errInvalidTraceStateKeyValue,
|
||||
},
|
||||
{
|
||||
name: "Double @",
|
||||
kvs: []attribute.KeyValue{
|
||||
attribute.String("foo@@bar", "1"),
|
||||
attribute.String("bar", "2"),
|
||||
},
|
||||
expectedTraceState: TraceState{},
|
||||
expectedErr: errInvalidTraceStateKeyValue,
|
||||
},
|
||||
{
|
||||
name: "Compound vendor",
|
||||
kvs: []attribute.KeyValue{
|
||||
attribute.String("foo@bar@baz", "1"),
|
||||
attribute.String("bar", "2"),
|
||||
},
|
||||
expectedTraceState: TraceState{},
|
||||
expectedErr: errInvalidTraceStateKeyValue,
|
||||
},
|
||||
{
|
||||
name: "Key too long",
|
||||
kvs: []attribute.KeyValue{
|
||||
attribute.String("foo", "1"),
|
||||
attribute.String("zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz", "1"),
|
||||
},
|
||||
expectedTraceState: TraceState{},
|
||||
expectedErr: errInvalidTraceStateKeyValue,
|
||||
},
|
||||
{
|
||||
name: "Key too long with vendor",
|
||||
kvs: []attribute.KeyValue{
|
||||
attribute.String("foo", "1"),
|
||||
attribute.String("tttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttt@v", "1"),
|
||||
},
|
||||
expectedTraceState: TraceState{},
|
||||
expectedErr: errInvalidTraceStateKeyValue,
|
||||
},
|
||||
{
|
||||
name: "Vendor too long",
|
||||
kvs: []attribute.KeyValue{
|
||||
attribute.String("foo", "1"),
|
||||
attribute.String("t@vvvvvvvvvvvvvvv", "1"),
|
||||
},
|
||||
expectedTraceState: TraceState{},
|
||||
expectedErr: errInvalidTraceStateKeyValue,
|
||||
},
|
||||
{
|
||||
name: "Equal sign in value",
|
||||
kvs: []attribute.KeyValue{
|
||||
attribute.String("foo", "bar=baz"),
|
||||
},
|
||||
expectedTraceState: TraceState{},
|
||||
expectedErr: errInvalidTraceStateKeyValue,
|
||||
},
|
||||
{
|
||||
name: "Empty value",
|
||||
kvs: []attribute.KeyValue{
|
||||
attribute.String("foo", ""),
|
||||
attribute.String("bar", "3"),
|
||||
},
|
||||
expectedTraceState: TraceState{},
|
||||
expectedErr: errInvalidTraceStateKeyValue,
|
||||
},
|
||||
}
|
||||
|
||||
messageFunc := func(kvs []attribute.KeyValue) []string {
|
||||
var out []string
|
||||
for _, kv := range kvs {
|
||||
out = append(out, fmt.Sprintf("%s=%s", kv.Key, kv.Value.AsString()))
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
result, err := TraceStateFromKeyValues(tc.kvs...)
|
||||
if tc.expectedErr != nil {
|
||||
require.Error(t, err, messageFunc(tc.kvs))
|
||||
assert.Equal(t, TraceState{}, result)
|
||||
assert.Equal(t, tc.expectedErr, err)
|
||||
} else {
|
||||
require.NoError(t, err, messageFunc(tc.kvs))
|
||||
assert.NotNil(t, tc.expectedTraceState)
|
||||
assert.Equal(t, tc.expectedTraceState, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func assertSpanContextEqual(got SpanContext, want SpanContext) bool {
|
||||
return got.spanID == want.spanID &&
|
||||
got.traceID == want.traceID &&
|
||||
got.traceFlags == want.traceFlags &&
|
||||
got.remote == want.remote &&
|
||||
assertTraceStateEqual(got.traceState, want.traceState)
|
||||
got.traceState.String() == want.traceState.String()
|
||||
}
|
||||
|
||||
func assertTraceStateEqual(got TraceState, want TraceState) bool {
|
||||
if len(got.kvs) != len(want.kvs) {
|
||||
return false
|
||||
}
|
||||
|
||||
for i, kv := range got.kvs {
|
||||
if kv != want.kvs[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
var kvsWithMaxMembers = func() []attribute.KeyValue {
|
||||
kvs := make([]attribute.KeyValue, traceStateMaxListMembers)
|
||||
for i := 0; i < traceStateMaxListMembers; i++ {
|
||||
kvs[i] = attribute.String(fmt.Sprintf("key%d", i+1),
|
||||
fmt.Sprintf("value%d", i+1))
|
||||
}
|
||||
return kvs
|
||||
}()
|
||||
|
||||
func TestNewSpanContext(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
@ -947,16 +385,16 @@ func TestNewSpanContext(t *testing.T) {
|
||||
TraceID: TraceID([16]byte{1}),
|
||||
SpanID: SpanID([8]byte{42}),
|
||||
TraceFlags: 0x1,
|
||||
TraceState: TraceState{kvs: []attribute.KeyValue{
|
||||
attribute.String("foo", "bar"),
|
||||
TraceState: TraceState{list: []member{
|
||||
{"foo", "bar"},
|
||||
}},
|
||||
},
|
||||
expectedSpanContext: SpanContext{
|
||||
traceID: TraceID([16]byte{1}),
|
||||
spanID: SpanID([8]byte{42}),
|
||||
traceFlags: 0x1,
|
||||
traceState: TraceState{kvs: []attribute.KeyValue{
|
||||
attribute.String("foo", "bar"),
|
||||
traceState: TraceState{list: []member{
|
||||
{"foo", "bar"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
@ -1016,7 +454,7 @@ func TestSpanContextDerivation(t *testing.T) {
|
||||
}
|
||||
|
||||
from = to
|
||||
to.traceState = TraceState{kvs: []attribute.KeyValue{attribute.String("foo", "bar")}}
|
||||
to.traceState = TraceState{list: []member{{"foo", "bar"}}}
|
||||
|
||||
modified = from.WithTraceState(to.TraceState())
|
||||
if !assertSpanContextEqual(modified, to) {
|
||||
|
217
trace/tracestate.go
Normal file
217
trace/tracestate.go
Normal file
@ -0,0 +1,217 @@
|
||||
// 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 trace
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
maxListMembers = 32
|
||||
|
||||
listDelimiter = ","
|
||||
|
||||
// based on the W3C Trace Context specification, see
|
||||
// https://www.w3.org/TR/trace-context-1/#tracestate-header
|
||||
noTenantKeyFormat = `[a-z][_0-9a-z\-\*\/]{0,255}`
|
||||
withTenantKeyFormat = `[a-z0-9][_0-9a-z\-\*\/]{0,240}@[a-z][_0-9a-z\-\*\/]{0,13}`
|
||||
valueFormat = `[\x20-\x2b\x2d-\x3c\x3e-\x7e]{0,255}[\x21-\x2b\x2d-\x3c\x3e-\x7e]`
|
||||
|
||||
keyRe = regexp.MustCompile(`^((` + noTenantKeyFormat + `)|(` + withTenantKeyFormat + `))$`)
|
||||
valueRe = regexp.MustCompile(`^(` + valueFormat + `)$`)
|
||||
memberRe = regexp.MustCompile(`^\s*((` + noTenantKeyFormat + `)|(` + withTenantKeyFormat + `))=(` + valueFormat + `)\s*$`)
|
||||
|
||||
errInvalidKey errorConst = "invalid tracestate key"
|
||||
errInvalidValue errorConst = "invalid tracestate value"
|
||||
errInvalidMember errorConst = "invalid tracestate list-member"
|
||||
errMemberNumber errorConst = "too many list-members in tracestate"
|
||||
errDuplicate errorConst = "duplicate list-member in tracestate"
|
||||
)
|
||||
|
||||
type member struct {
|
||||
Key string
|
||||
Value string
|
||||
}
|
||||
|
||||
func newMember(key, value string) (member, error) {
|
||||
if !keyRe.MatchString(key) {
|
||||
return member{}, fmt.Errorf("%w: %s", errInvalidKey, key)
|
||||
}
|
||||
if !valueRe.MatchString(value) {
|
||||
return member{}, fmt.Errorf("%w: %s", errInvalidValue, value)
|
||||
}
|
||||
return member{Key: key, Value: value}, nil
|
||||
}
|
||||
|
||||
func parseMemeber(m string) (member, error) {
|
||||
matches := memberRe.FindStringSubmatch(m)
|
||||
if len(matches) != 5 {
|
||||
return member{}, fmt.Errorf("%w: %s", errInvalidMember, m)
|
||||
}
|
||||
|
||||
return member{
|
||||
Key: matches[1],
|
||||
Value: matches[4],
|
||||
}, nil
|
||||
|
||||
}
|
||||
|
||||
// String encodes member into a string compliant with the W3C Trace Context
|
||||
// specification.
|
||||
func (m member) String() string {
|
||||
return fmt.Sprintf("%s=%s", m.Key, m.Value)
|
||||
}
|
||||
|
||||
// TraceState provides additional vendor-specific trace identification
|
||||
// information across different distributed tracing systems. It represents an
|
||||
// immutable list consisting of key/value pairs, each pair is referred to as a
|
||||
// list-member.
|
||||
//
|
||||
// TraceState conforms to the W3C Trace Context specification
|
||||
// (https://www.w3.org/TR/trace-context-1). All operations that create or copy
|
||||
// a TraceState do so by validating all input and will only produce TraceState
|
||||
// that conform to the specification. Specifically, this means that all
|
||||
// list-member's key/value pairs are valid, no duplicate list-members exist,
|
||||
// and the maximum number of list-members (32) is not exceeded.
|
||||
type TraceState struct { //nolint:golint
|
||||
// list is the members in order.
|
||||
list []member
|
||||
}
|
||||
|
||||
var _ json.Marshaler = TraceState{}
|
||||
|
||||
// ParseTraceState attempts to decode a TraceState from the passed
|
||||
// string. It returns an error if the input is invalid according to the W3C
|
||||
// Trace Context specification.
|
||||
func ParseTraceState(tracestate string) (TraceState, error) {
|
||||
if tracestate == "" {
|
||||
return TraceState{}, nil
|
||||
}
|
||||
|
||||
wrapErr := func(err error) error {
|
||||
return fmt.Errorf("failed to parse tracestate: %w", err)
|
||||
}
|
||||
|
||||
var members []member
|
||||
found := make(map[string]struct{})
|
||||
for _, memberStr := range strings.Split(tracestate, listDelimiter) {
|
||||
if len(memberStr) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
m, err := parseMemeber(memberStr)
|
||||
if err != nil {
|
||||
return TraceState{}, wrapErr(err)
|
||||
}
|
||||
|
||||
if _, ok := found[m.Key]; ok {
|
||||
return TraceState{}, wrapErr(errDuplicate)
|
||||
}
|
||||
found[m.Key] = struct{}{}
|
||||
|
||||
members = append(members, m)
|
||||
if n := len(members); n > maxListMembers {
|
||||
return TraceState{}, wrapErr(errMemberNumber)
|
||||
}
|
||||
}
|
||||
|
||||
return TraceState{list: members}, nil
|
||||
}
|
||||
|
||||
// MarshalJSON marshals the TraceState into JSON.
|
||||
func (ts TraceState) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(ts.String())
|
||||
}
|
||||
|
||||
// String encodes the TraceState into a string compliant with the W3C
|
||||
// Trace Context specification. The returned string will be invalid if the
|
||||
// TraceState contains any invalid members.
|
||||
func (ts TraceState) String() string {
|
||||
members := make([]string, len(ts.list))
|
||||
for i, m := range ts.list {
|
||||
members[i] = m.String()
|
||||
}
|
||||
return strings.Join(members, listDelimiter)
|
||||
}
|
||||
|
||||
// Get returns the value paired with key from the corresponding TraceState
|
||||
// list-member if it exists, otherwise an empty string is returned.
|
||||
func (ts TraceState) Get(key string) string {
|
||||
for _, member := range ts.list {
|
||||
if member.Key == key {
|
||||
return member.Value
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// Insert adds a new list-member defined by the key/value pair to the
|
||||
// TraceState. If a list-member already exists for the given key, that
|
||||
// list-member's value is updated. The new or updated list-member is always
|
||||
// moved to the beginning of the TraceState as specified by the W3C Trace
|
||||
// Context specification.
|
||||
//
|
||||
// If key or value are invalid according to the W3C Trace Context
|
||||
// specification an error is returned with the original TraceState.
|
||||
//
|
||||
// If adding a new list-member means the TraceState would have more members
|
||||
// than is allowed an error is returned instead with the original TraceState.
|
||||
func (ts TraceState) Insert(key, value string) (TraceState, error) {
|
||||
m, err := newMember(key, value)
|
||||
if err != nil {
|
||||
return ts, err
|
||||
}
|
||||
|
||||
cTS := ts.Delete(key)
|
||||
if cTS.Len()+1 > maxListMembers {
|
||||
// TODO (MrAlias): When the second version of the Trace Context
|
||||
// specification is published this needs to not return an error.
|
||||
// Instead it should drop the "right-most" member and insert the new
|
||||
// member at the front.
|
||||
//
|
||||
// https://github.com/w3c/trace-context/pull/448
|
||||
return ts, fmt.Errorf("failed to insert: %w", errMemberNumber)
|
||||
}
|
||||
|
||||
cTS.list = append(cTS.list, member{})
|
||||
copy(cTS.list[1:], cTS.list)
|
||||
cTS.list[0] = m
|
||||
|
||||
return cTS, nil
|
||||
}
|
||||
|
||||
// Delete returns a copy of the TraceState with the list-member identified by
|
||||
// key removed.
|
||||
func (ts TraceState) Delete(key string) TraceState {
|
||||
members := make([]member, ts.Len())
|
||||
copy(members, ts.list)
|
||||
for i, member := range ts.list {
|
||||
if member.Key == key {
|
||||
members = append(members[:i], members[i+1:]...)
|
||||
// TraceState should contain no duplicate members.
|
||||
break
|
||||
}
|
||||
}
|
||||
return TraceState{list: members}
|
||||
}
|
||||
|
||||
// Len returns the number of list-members in the TraceState.
|
||||
func (ts TraceState) Len() int {
|
||||
return len(ts.list)
|
||||
}
|
506
trace/tracestate_test.go
Normal file
506
trace/tracestate_test.go
Normal file
@ -0,0 +1,506 @@
|
||||
// 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 trace
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// Taken from the W3C tests:
|
||||
// https://github.com/w3c/trace-context/blob/dcd3ad9b7d6ac36f70ff3739874b73c11b0302a1/test/test_data.json
|
||||
var testcases = []struct {
|
||||
in string
|
||||
tracestate TraceState
|
||||
out string
|
||||
err error
|
||||
}{
|
||||
{
|
||||
in: "foo=1,foo=1",
|
||||
err: errDuplicate,
|
||||
},
|
||||
{
|
||||
in: "foo=1,foo=2",
|
||||
err: errDuplicate,
|
||||
},
|
||||
{
|
||||
in: "foo =1",
|
||||
err: errInvalidMember,
|
||||
},
|
||||
{
|
||||
in: "FOO=1",
|
||||
err: errInvalidMember,
|
||||
},
|
||||
{
|
||||
in: "foo.bar=1",
|
||||
err: errInvalidMember,
|
||||
},
|
||||
{
|
||||
in: "foo@=1,bar=2",
|
||||
err: errInvalidMember,
|
||||
},
|
||||
{
|
||||
in: "@foo=1,bar=2",
|
||||
err: errInvalidMember,
|
||||
},
|
||||
{
|
||||
in: "foo@@bar=1,bar=2",
|
||||
err: errInvalidMember,
|
||||
},
|
||||
{
|
||||
in: "foo@bar@baz=1,bar=2",
|
||||
err: errInvalidMember,
|
||||
},
|
||||
{
|
||||
in: "foo=1,zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz=1",
|
||||
err: errInvalidMember,
|
||||
},
|
||||
{
|
||||
in: "foo=1,tttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttt@v=1",
|
||||
err: errInvalidMember,
|
||||
},
|
||||
{
|
||||
in: "foo=1,t@vvvvvvvvvvvvvvv=1",
|
||||
err: errInvalidMember,
|
||||
},
|
||||
{
|
||||
in: "foo=bar=baz",
|
||||
err: errInvalidMember,
|
||||
},
|
||||
{
|
||||
in: "foo=,bar=3",
|
||||
err: errInvalidMember,
|
||||
},
|
||||
{
|
||||
in: "bar01=01,bar02=02,bar03=03,bar04=04,bar05=05,bar06=06,bar07=07,bar08=08,bar09=09,bar10=10,bar11=11,bar12=12,bar13=13,bar14=14,bar15=15,bar16=16,bar17=17,bar18=18,bar19=19,bar20=20,bar21=21,bar22=22,bar23=23,bar24=24,bar25=25,bar26=26,bar27=27,bar28=28,bar29=29,bar30=30,bar31=31,bar32=32,bar33=33",
|
||||
err: errMemberNumber,
|
||||
},
|
||||
{
|
||||
in: "abcdefghijklmnopqrstuvwxyz0123456789_-*/= !\"#$%&'()*+-./0123456789:;<>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~",
|
||||
out: "abcdefghijklmnopqrstuvwxyz0123456789_-*/= !\"#$%&'()*+-./0123456789:;<>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~",
|
||||
tracestate: TraceState{list: []member{
|
||||
{
|
||||
Key: "abcdefghijklmnopqrstuvwxyz0123456789_-*/",
|
||||
Value: " !\"#$%&'()*+-./0123456789:;<>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~",
|
||||
},
|
||||
}},
|
||||
},
|
||||
{
|
||||
in: "abcdefghijklmnopqrstuvwxyz0123456789_-*/@a-z0-9_-*/= !\"#$%&'()*+-./0123456789:;<>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~",
|
||||
out: "abcdefghijklmnopqrstuvwxyz0123456789_-*/@a-z0-9_-*/= !\"#$%&'()*+-./0123456789:;<>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~",
|
||||
tracestate: TraceState{list: []member{
|
||||
{
|
||||
Key: "abcdefghijklmnopqrstuvwxyz0123456789_-*/@a-z0-9_-*/",
|
||||
Value: " !\"#$%&'()*+-./0123456789:;<>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~",
|
||||
},
|
||||
}},
|
||||
},
|
||||
{
|
||||
// Empty input should result in no error and a zero value
|
||||
// TraceState being returned, that TraceState should be encoded as an
|
||||
// empty string.
|
||||
},
|
||||
{
|
||||
in: "foo=1",
|
||||
out: "foo=1",
|
||||
tracestate: TraceState{list: []member{
|
||||
{Key: "foo", Value: "1"},
|
||||
}},
|
||||
},
|
||||
{
|
||||
in: "foo=1,",
|
||||
out: "foo=1",
|
||||
tracestate: TraceState{list: []member{
|
||||
{Key: "foo", Value: "1"},
|
||||
}},
|
||||
},
|
||||
{
|
||||
in: "foo=1,bar=2",
|
||||
out: "foo=1,bar=2",
|
||||
tracestate: TraceState{list: []member{
|
||||
{Key: "foo", Value: "1"},
|
||||
{Key: "bar", Value: "2"},
|
||||
}},
|
||||
},
|
||||
{
|
||||
in: "foo=1,zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz=1",
|
||||
out: "foo=1,zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz=1",
|
||||
tracestate: TraceState{list: []member{
|
||||
{
|
||||
Key: "foo",
|
||||
Value: "1",
|
||||
},
|
||||
{
|
||||
Key: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz",
|
||||
Value: "1",
|
||||
},
|
||||
}},
|
||||
},
|
||||
{
|
||||
in: "foo=1,ttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttt@vvvvvvvvvvvvvv=1",
|
||||
out: "foo=1,ttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttt@vvvvvvvvvvvvvv=1",
|
||||
tracestate: TraceState{list: []member{
|
||||
{
|
||||
Key: "foo",
|
||||
Value: "1",
|
||||
},
|
||||
{
|
||||
Key: "ttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttt@vvvvvvvvvvvvvv",
|
||||
Value: "1",
|
||||
},
|
||||
}},
|
||||
},
|
||||
{
|
||||
in: "bar01=01,bar02=02,bar03=03,bar04=04,bar05=05,bar06=06,bar07=07,bar08=08,bar09=09,bar10=10,bar11=11,bar12=12,bar13=13,bar14=14,bar15=15,bar16=16,bar17=17,bar18=18,bar19=19,bar20=20,bar21=21,bar22=22,bar23=23,bar24=24,bar25=25,bar26=26,bar27=27,bar28=28,bar29=29,bar30=30,bar31=31,bar32=32",
|
||||
out: "bar01=01,bar02=02,bar03=03,bar04=04,bar05=05,bar06=06,bar07=07,bar08=08,bar09=09,bar10=10,bar11=11,bar12=12,bar13=13,bar14=14,bar15=15,bar16=16,bar17=17,bar18=18,bar19=19,bar20=20,bar21=21,bar22=22,bar23=23,bar24=24,bar25=25,bar26=26,bar27=27,bar28=28,bar29=29,bar30=30,bar31=31,bar32=32",
|
||||
tracestate: TraceState{list: []member{
|
||||
{Key: "bar01", Value: "01"},
|
||||
{Key: "bar02", Value: "02"},
|
||||
{Key: "bar03", Value: "03"},
|
||||
{Key: "bar04", Value: "04"},
|
||||
{Key: "bar05", Value: "05"},
|
||||
{Key: "bar06", Value: "06"},
|
||||
{Key: "bar07", Value: "07"},
|
||||
{Key: "bar08", Value: "08"},
|
||||
{Key: "bar09", Value: "09"},
|
||||
{Key: "bar10", Value: "10"},
|
||||
{Key: "bar11", Value: "11"},
|
||||
{Key: "bar12", Value: "12"},
|
||||
{Key: "bar13", Value: "13"},
|
||||
{Key: "bar14", Value: "14"},
|
||||
{Key: "bar15", Value: "15"},
|
||||
{Key: "bar16", Value: "16"},
|
||||
{Key: "bar17", Value: "17"},
|
||||
{Key: "bar18", Value: "18"},
|
||||
{Key: "bar19", Value: "19"},
|
||||
{Key: "bar20", Value: "20"},
|
||||
{Key: "bar21", Value: "21"},
|
||||
{Key: "bar22", Value: "22"},
|
||||
{Key: "bar23", Value: "23"},
|
||||
{Key: "bar24", Value: "24"},
|
||||
{Key: "bar25", Value: "25"},
|
||||
{Key: "bar26", Value: "26"},
|
||||
{Key: "bar27", Value: "27"},
|
||||
{Key: "bar28", Value: "28"},
|
||||
{Key: "bar29", Value: "29"},
|
||||
{Key: "bar30", Value: "30"},
|
||||
{Key: "bar31", Value: "31"},
|
||||
{Key: "bar32", Value: "32"},
|
||||
}},
|
||||
},
|
||||
{
|
||||
in: "foo=1,bar=2,rojo=1,congo=2,baz=3",
|
||||
out: "foo=1,bar=2,rojo=1,congo=2,baz=3",
|
||||
tracestate: TraceState{list: []member{
|
||||
{Key: "foo", Value: "1"},
|
||||
{Key: "bar", Value: "2"},
|
||||
{Key: "rojo", Value: "1"},
|
||||
{Key: "congo", Value: "2"},
|
||||
{Key: "baz", Value: "3"},
|
||||
}},
|
||||
},
|
||||
{
|
||||
in: "foo=1 \t , \t bar=2, \t baz=3",
|
||||
out: "foo=1,bar=2,baz=3",
|
||||
tracestate: TraceState{list: []member{
|
||||
{Key: "foo", Value: "1"},
|
||||
{Key: "bar", Value: "2"},
|
||||
{Key: "baz", Value: "3"},
|
||||
}},
|
||||
},
|
||||
{
|
||||
in: "foo=1\t \t,\t \tbar=2,\t \tbaz=3",
|
||||
out: "foo=1,bar=2,baz=3",
|
||||
tracestate: TraceState{list: []member{
|
||||
{Key: "foo", Value: "1"},
|
||||
{Key: "bar", Value: "2"},
|
||||
{Key: "baz", Value: "3"},
|
||||
}},
|
||||
},
|
||||
{
|
||||
in: "foo=1 ",
|
||||
out: "foo=1",
|
||||
tracestate: TraceState{list: []member{
|
||||
{Key: "foo", Value: "1"},
|
||||
}},
|
||||
},
|
||||
{
|
||||
in: "foo=1\t",
|
||||
out: "foo=1",
|
||||
tracestate: TraceState{list: []member{
|
||||
{Key: "foo", Value: "1"},
|
||||
}},
|
||||
},
|
||||
{
|
||||
in: "foo=1 \t",
|
||||
out: "foo=1",
|
||||
tracestate: TraceState{list: []member{
|
||||
{Key: "foo", Value: "1"},
|
||||
}},
|
||||
},
|
||||
}
|
||||
|
||||
var maxMembers = func() TraceState {
|
||||
members := make([]member, maxListMembers)
|
||||
for i := 0; i < maxListMembers; i++ {
|
||||
members[i] = member{
|
||||
Key: fmt.Sprintf("key%d", i+1),
|
||||
Value: fmt.Sprintf("value%d", i+1),
|
||||
}
|
||||
}
|
||||
return TraceState{list: members}
|
||||
}()
|
||||
|
||||
func TestParseTraceState(t *testing.T) {
|
||||
for _, tc := range testcases {
|
||||
got, err := ParseTraceState(tc.in)
|
||||
assert.Equal(t, tc.tracestate, got)
|
||||
if tc.err != nil {
|
||||
assert.ErrorIs(t, err, tc.err, tc.in)
|
||||
} else {
|
||||
assert.NoError(t, err, tc.in)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTraceStateString(t *testing.T) {
|
||||
for _, tc := range testcases {
|
||||
if tc.err != nil {
|
||||
// Only test non-zero value TraceState.
|
||||
continue
|
||||
}
|
||||
|
||||
assert.Equal(t, tc.out, tc.tracestate.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestTraceStateMarshalJSON(t *testing.T) {
|
||||
for _, tc := range testcases {
|
||||
if tc.err != nil {
|
||||
// Only test non-zero value TraceState.
|
||||
continue
|
||||
}
|
||||
|
||||
// Encode UTF-8.
|
||||
expected, err := json.Marshal(tc.out)
|
||||
require.NoError(t, err)
|
||||
|
||||
actual, err := json.Marshal(tc.tracestate)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTraceStateGet(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
key string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "OK case",
|
||||
key: "key16",
|
||||
expected: "value16",
|
||||
},
|
||||
{
|
||||
name: "not found",
|
||||
key: "keyxx",
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
name: "invalid W3C key",
|
||||
key: "key!",
|
||||
expected: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
assert.Equal(t, tc.expected, maxMembers.Get(tc.key), tc.name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTraceStateDelete(t *testing.T) {
|
||||
ts := TraceState{list: []member{
|
||||
{Key: "key1", Value: "val1"},
|
||||
{Key: "key2", Value: "val2"},
|
||||
{Key: "key3", Value: "val3"},
|
||||
}}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
key string
|
||||
expected TraceState
|
||||
}{
|
||||
{
|
||||
name: "OK case",
|
||||
key: "key2",
|
||||
expected: TraceState{list: []member{
|
||||
{Key: "key1", Value: "val1"},
|
||||
{Key: "key3", Value: "val3"},
|
||||
}},
|
||||
},
|
||||
{
|
||||
name: "Non-existing key",
|
||||
key: "keyx",
|
||||
expected: TraceState{list: []member{
|
||||
{Key: "key1", Value: "val1"},
|
||||
{Key: "key2", Value: "val2"},
|
||||
{Key: "key3", Value: "val3"},
|
||||
}},
|
||||
},
|
||||
{
|
||||
name: "Invalid key",
|
||||
key: "in va lid",
|
||||
expected: TraceState{list: []member{
|
||||
{Key: "key1", Value: "val1"},
|
||||
{Key: "key2", Value: "val2"},
|
||||
{Key: "key3", Value: "val3"},
|
||||
}},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
assert.Equal(t, tc.expected, ts.Delete(tc.key), tc.name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTraceStateInsert(t *testing.T) {
|
||||
ts := TraceState{list: []member{
|
||||
{Key: "key1", Value: "val1"},
|
||||
{Key: "key2", Value: "val2"},
|
||||
{Key: "key3", Value: "val3"},
|
||||
}}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
tracestate TraceState
|
||||
key, value string
|
||||
expected TraceState
|
||||
err error
|
||||
}{
|
||||
{
|
||||
name: "add new",
|
||||
tracestate: ts,
|
||||
key: "key4@vendor",
|
||||
value: "val4",
|
||||
expected: TraceState{list: []member{
|
||||
{Key: "key4@vendor", Value: "val4"},
|
||||
{Key: "key1", Value: "val1"},
|
||||
{Key: "key2", Value: "val2"},
|
||||
{Key: "key3", Value: "val3"},
|
||||
}},
|
||||
},
|
||||
{
|
||||
name: "replace",
|
||||
tracestate: ts,
|
||||
key: "key2",
|
||||
value: "valX",
|
||||
expected: TraceState{list: []member{
|
||||
{Key: "key2", Value: "valX"},
|
||||
{Key: "key1", Value: "val1"},
|
||||
{Key: "key3", Value: "val3"},
|
||||
}},
|
||||
},
|
||||
{
|
||||
name: "invalid key",
|
||||
tracestate: ts,
|
||||
key: "key!",
|
||||
value: "val",
|
||||
expected: ts,
|
||||
err: errInvalidKey,
|
||||
},
|
||||
{
|
||||
name: "invalid value",
|
||||
tracestate: ts,
|
||||
key: "key",
|
||||
value: "v=l",
|
||||
expected: ts,
|
||||
err: errInvalidValue,
|
||||
},
|
||||
{
|
||||
name: "invalid key/value",
|
||||
tracestate: ts,
|
||||
key: "key!",
|
||||
value: "v=l",
|
||||
expected: ts,
|
||||
err: errInvalidKey,
|
||||
},
|
||||
{
|
||||
name: "too many entries",
|
||||
tracestate: maxMembers,
|
||||
key: "keyx",
|
||||
value: "valx",
|
||||
expected: maxMembers,
|
||||
err: errMemberNumber,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
actual, err := tc.tracestate.Insert(tc.key, tc.value)
|
||||
assert.ErrorIs(t, err, tc.err, tc.name)
|
||||
if tc.err != nil {
|
||||
assert.Equal(t, tc.tracestate, actual, tc.name)
|
||||
} else {
|
||||
assert.Equal(t, tc.expected, actual, tc.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTraceStateLen(t *testing.T) {
|
||||
ts := TraceState{}
|
||||
assert.Equal(t, 0, ts.Len(), "zero value TraceState is empty")
|
||||
|
||||
key := "key"
|
||||
ts = TraceState{list: []member{{key, "value"}}}
|
||||
assert.Equal(t, 1, ts.Len(), "TraceState with one value")
|
||||
}
|
||||
|
||||
func TestTraceStateImmutable(t *testing.T) {
|
||||
k0, v0 := "k0", "v0"
|
||||
ts0 := TraceState{list: []member{{k0, v0}}}
|
||||
assert.Equal(t, v0, ts0.Get(k0))
|
||||
|
||||
// Insert should not modify the original.
|
||||
k1, v1 := "k1", "v1"
|
||||
ts1, err := ts0.Insert(k1, v1)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, v0, ts0.Get(k0))
|
||||
assert.Equal(t, "", ts0.Get(k1))
|
||||
assert.Equal(t, v0, ts1.Get(k0))
|
||||
assert.Equal(t, v1, ts1.Get(k1))
|
||||
|
||||
// Update should not modify the original.
|
||||
v2 := "v2"
|
||||
ts2, err := ts1.Insert(k1, v2)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, v0, ts0.Get(k0))
|
||||
assert.Equal(t, "", ts0.Get(k1))
|
||||
assert.Equal(t, v0, ts1.Get(k0))
|
||||
assert.Equal(t, v1, ts1.Get(k1))
|
||||
assert.Equal(t, v0, ts2.Get(k0))
|
||||
assert.Equal(t, v2, ts2.Get(k1))
|
||||
|
||||
// Delete should not modify the original.
|
||||
ts3 := ts2.Delete(k0)
|
||||
assert.Equal(t, v0, ts0.Get(k0))
|
||||
assert.Equal(t, v0, ts1.Get(k0))
|
||||
assert.Equal(t, v0, ts2.Get(k0))
|
||||
assert.Equal(t, "", ts3.Get(k0))
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user