1
0
mirror of https://github.com/open-telemetry/opentelemetry-go.git synced 2025-10-31 00:07:40 +02:00

Update span limits to comply with specification (#2637)

* PoC for span limit refactor

* Rename config.go to span_limits.go

* Add unit tests for truncateAttr

* Add unit tests for non-string attrs

* Add span limit benchmark tests

* Fix lint

* Isolate span limit tests

* Clean span limits test

* Test limits on exported spans

* Remove duplicate test code

* Fix lint

* Add WithRawSpanLimits option

* Add test for raw and orig span limits opts

* Add changes to changelog

* Add tests for span resource disabled

* Test unlimited instead of default limit

* Update docs

* Add fix to changelog

* Fix option docs

* Do no mutate attribute

* Fix truncateAttr comment

* Remake NewSpanLimits to be newEnvSpanLimits

Update and unify documentation accordingly.

* Update truncateAttr string slice update comment

* Update CHANGELOG.md

Co-authored-by: Anthony Mirabella <a9@aneurysm9.com>

Co-authored-by: Anthony Mirabella <a9@aneurysm9.com>
This commit is contained in:
Tyler Yahn
2022-03-03 07:56:07 -08:00
committed by GitHub
parent 24414b2455
commit 0d0a7320e6
12 changed files with 895 additions and 202 deletions

View File

@@ -14,15 +14,26 @@ This update is a breaking change of the unstable Metrics API. Code instrumented
### Added
- Log the Exporters configuration in the TracerProviders message. (#2578)
- Added support to configure the span limits with environment variables.
The following environment variables are used. (#2606)
The following environment variables are used. (#2606, #2637)
- `OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT`
- `OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT`
- `OTEL_SPAN_EVENT_COUNT_LIMIT`
- `OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT`
- `OTEL_SPAN_LINK_COUNT_LIMIT`
- `OTEL_LINK_ATTRIBUTE_COUNT_LIMIT`
If the provided environment variables are invalid (negative), the default values would be used.
- Rename the `gc` runtime name to `go` (#2560)
- Log the Exporters configuration in the TracerProviders message. (#2578)
- Add span attribute value length limit.
The new `AttributeValueLengthLimit` field is added to the `"go.opentelemetry.io/otel/sdk/trace".SpanLimits` type to configure this limit for a `TracerProvider`.
The default limit for this resource is "unlimited". (#2637)
- Add the `WithRawSpanLimits` option to `go.opentelemetry.io/otel/sdk/trace`.
This option replaces the `WithSpanLimits` option.
Zero or negative values will not be changed to the default value like `WithSpanLimits` does.
Setting a limit to zero will effectively disable the related resource it limits and setting to a negative value will mean that resource is unlimited.
Consequentially, limits should be constructed using `NewSpanLimits` and updated accordingly. (#2637)
### Changed
@@ -37,6 +48,14 @@ This update is a breaking change of the unstable Metrics API. Code instrumented
- Remove the OTLP trace exporter limit of SpanEvents when exporting. (#2616)
- Use port `4318` instead of `4317` for default for the `otlpmetrichttp` and `otlptracehttp` client. (#2614, #2625)
- Unlimited span limits are now supported (negative values). (#2636, #2637)
### Deprecated
- Deprecated `"go.opentelemetry.io/otel/sdk/trace".WithSpanLimits`.
Use `WithRawSpanLimits` instead.
That option allows setting unlimited and zero limits, this option does not.
This option will be kept until the next major version incremented release. (#2637)
## [1.4.1] - 2022-02-16

View File

@@ -41,20 +41,29 @@ const (
// i.e. 512
BatchSpanProcessorMaxExportBatchSizeKey = "OTEL_BSP_MAX_EXPORT_BATCH_SIZE"
// SpanAttributesCountKey
// SpanAttributeValueLengthKey
// Maximum allowed attribute value size.
SpanAttributeValueLengthKey = "OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT"
// SpanAttributeCountKey
// Maximum allowed span attribute count
// Default: 128
SpanAttributesCountKey = "OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT"
SpanAttributeCountKey = "OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT"
// SpanEventCountKey
// Maximum allowed span event count
// Default: 128
SpanEventCountKey = "OTEL_SPAN_EVENT_COUNT_LIMIT"
// SpanEventAttributeCountKey
// Maximum allowed attribute per span event count.
SpanEventAttributeCountKey = "OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT"
// SpanLinkCountKey
// Maximum allowed span link count
// Default: 128
SpanLinkCountKey = "OTEL_SPAN_LINK_COUNT_LIMIT"
// SpanLinkAttributeCountKey
// Maximum allowed attribute per span link count
SpanLinkAttributeCountKey = "OTEL_LINK_ATTRIBUTE_COUNT_LIMIT"
)
// IntEnvOr returns the int value of the environment variable with name key if
@@ -101,3 +110,45 @@ func BatchSpanProcessorMaxQueueSize(defaultValue int) int {
func BatchSpanProcessorMaxExportBatchSize(defaultValue int) int {
return IntEnvOr(BatchSpanProcessorMaxExportBatchSizeKey, defaultValue)
}
// SpanAttributeValueLength returns the environment variable value for the
// OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT key if it exists, otherwise
// defaultValue is returned.
func SpanAttributeValueLength(defaultValue int) int {
return IntEnvOr(SpanAttributeValueLengthKey, defaultValue)
}
// SpanAttributeCount returns the environment variable value for the
// OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT key if it exists, otherwise defaultValue is
// returned.
func SpanAttributeCount(defaultValue int) int {
return IntEnvOr(SpanAttributeCountKey, defaultValue)
}
// SpanEventCount returns the environment variable value for the
// OTEL_SPAN_EVENT_COUNT_LIMIT key if it exists, otherwise defaultValue is
// returned.
func SpanEventCount(defaultValue int) int {
return IntEnvOr(SpanEventCountKey, defaultValue)
}
// SpanEventAttributeCount returns the environment variable value for the
// OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT key if it exists, otherwise defaultValue
// is returned.
func SpanEventAttributeCount(defaultValue int) int {
return IntEnvOr(SpanEventAttributeCountKey, defaultValue)
}
// SpanLinkCount returns the environment variable value for the
// OTEL_SPAN_LINK_COUNT_LIMIT key if it exists, otherwise defaultValue is
// returned.
func SpanLinkCount(defaultValue int) int {
return IntEnvOr(SpanLinkCountKey, defaultValue)
}
// SpanLinkAttributeCount returns the environment variable value for the
// OTEL_LINK_ATTRIBUTE_COUNT_LIMIT key if it exists, otherwise defaultValue is
// returned.
func SpanLinkAttributeCount(defaultValue int) int {
return IntEnvOr(SpanLinkAttributeCountKey, defaultValue)
}

View File

@@ -25,10 +25,106 @@ import (
"go.opentelemetry.io/otel/trace"
)
func benchmarkSpanLimits(b *testing.B, limits sdktrace.SpanLimits) {
tp := sdktrace.NewTracerProvider(sdktrace.WithSpanLimits(limits))
tracer := tp.Tracer(b.Name())
ctx := context.Background()
const count = 8
attrs := []attribute.KeyValue{
attribute.Bool("bool", true),
attribute.BoolSlice("boolSlice", []bool{true, false}),
attribute.Int("int", 42),
attribute.IntSlice("intSlice", []int{42, -1}),
attribute.Int64("int64", 42),
attribute.Int64Slice("int64Slice", []int64{42, -1}),
attribute.Float64("float64", 42),
attribute.Float64Slice("float64Slice", []float64{42, -1}),
attribute.String("string", "value"),
attribute.StringSlice("stringSlice", []string{"value", "value-1"}),
}
links := make([]trace.Link, count)
for i := range links {
links[i] = trace.Link{
SpanContext: trace.NewSpanContext(trace.SpanContextConfig{
TraceID: [16]byte{0x01},
SpanID: [8]byte{0x01},
}),
Attributes: attrs,
}
}
events := make([]struct {
name string
attr []attribute.KeyValue
}, count)
for i := range events {
events[i] = struct {
name string
attr []attribute.KeyValue
}{
name: fmt.Sprintf("event-%d", i),
attr: attrs,
}
}
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, span := tracer.Start(ctx, "span-name", trace.WithLinks(links...))
span.SetAttributes(attrs...)
for _, e := range events {
span.AddEvent(e.name, trace.WithAttributes(e.attr...))
}
span.End()
}
}
func BenchmarkSpanLimits(b *testing.B) {
b.Run("AttributeValueLengthLimit", func(b *testing.B) {
limits := sdktrace.NewSpanLimits()
limits.AttributeValueLengthLimit = 2
benchmarkSpanLimits(b, limits)
})
b.Run("AttributeCountLimit", func(b *testing.B) {
limits := sdktrace.NewSpanLimits()
limits.AttributeCountLimit = 1
benchmarkSpanLimits(b, limits)
})
b.Run("EventCountLimit", func(b *testing.B) {
limits := sdktrace.NewSpanLimits()
limits.EventCountLimit = 1
benchmarkSpanLimits(b, limits)
})
b.Run("LinkCountLimit", func(b *testing.B) {
limits := sdktrace.NewSpanLimits()
limits.LinkCountLimit = 1
benchmarkSpanLimits(b, limits)
})
b.Run("AttributePerEventCountLimit", func(b *testing.B) {
limits := sdktrace.NewSpanLimits()
limits.AttributePerEventCountLimit = 1
benchmarkSpanLimits(b, limits)
})
b.Run("AttributePerLinkCountLimit", func(b *testing.B) {
limits := sdktrace.NewSpanLimits()
limits.AttributePerLinkCountLimit = 1
benchmarkSpanLimits(b, limits)
})
}
func BenchmarkSpanSetAttributesOverCapacity(b *testing.B) {
tp := sdktrace.NewTracerProvider(
sdktrace.WithSpanLimits(sdktrace.SpanLimits{AttributeCountLimit: 1}),
)
limits := sdktrace.NewSpanLimits()
limits.AttributeCountLimit = 1
tp := sdktrace.NewTracerProvider(sdktrace.WithSpanLimits(limits))
tracer := tp.Tracer("BenchmarkSpanSetAttributesOverCapacity")
ctx := context.Background()
attrs := make([]attribute.KeyValue, 128)

View File

@@ -1,84 +0,0 @@
// 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 "go.opentelemetry.io/otel/sdk/trace"
import "go.opentelemetry.io/otel/sdk/internal/env"
// SpanLimits represents the limits of a span.
type SpanLimits struct {
// AttributeCountLimit is the maximum allowed span attribute count.
AttributeCountLimit int
// EventCountLimit is the maximum allowed span event count.
EventCountLimit int
// LinkCountLimit is the maximum allowed span link count.
LinkCountLimit int
// AttributePerEventCountLimit is the maximum allowed attribute per span event count.
AttributePerEventCountLimit int
// AttributePerLinkCountLimit is the maximum allowed attribute per span link count.
AttributePerLinkCountLimit int
}
func (sl *SpanLimits) ensureDefault() {
if sl.EventCountLimit <= 0 {
sl.EventCountLimit = DefaultEventCountLimit
}
if sl.AttributeCountLimit <= 0 {
sl.AttributeCountLimit = DefaultAttributeCountLimit
}
if sl.LinkCountLimit <= 0 {
sl.LinkCountLimit = DefaultLinkCountLimit
}
if sl.AttributePerEventCountLimit <= 0 {
sl.AttributePerEventCountLimit = DefaultAttributePerEventCountLimit
}
if sl.AttributePerLinkCountLimit <= 0 {
sl.AttributePerLinkCountLimit = DefaultAttributePerLinkCountLimit
}
}
func (sl *SpanLimits) parsePotentialEnvConfigs() {
sl.AttributeCountLimit = env.IntEnvOr(env.SpanAttributesCountKey, sl.AttributeCountLimit)
sl.LinkCountLimit = env.IntEnvOr(env.SpanLinkCountKey, sl.LinkCountLimit)
sl.EventCountLimit = env.IntEnvOr(env.SpanEventCountKey, sl.EventCountLimit)
}
const (
// DefaultAttributeCountLimit is the default maximum allowed span attribute count.
// If not specified via WithSpanLimits, will try to retrieve the value from
// environment variable `OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT`.
// If Invalid value (negative or zero) is provided, the default value 128 will be used.
DefaultAttributeCountLimit = 128
// DefaultEventCountLimit is the default maximum allowed span event count.
// If not specified via WithSpanLimits, will try to retrieve the value from
// environment variable `OTEL_SPAN_EVENT_COUNT_LIMIT`.
// If Invalid value (negative or zero) is provided, the default value 128 will be used.
DefaultEventCountLimit = 128
// DefaultLinkCountLimit is the default maximum allowed span link count.
// If the value is not specified via WithSpanLimits, will try to retrieve the value from
// environment variable `OTEL_SPAN_LINK_COUNT_LIMIT`.
// If Invalid value (negative or zero) is provided, the default value 128 will be used.
DefaultLinkCountLimit = 128
// DefaultAttributePerEventCountLimit is the default maximum allowed attribute per span event count.
DefaultAttributePerEventCountLimit = 128
// DefaultAttributePerLinkCountLimit is the default maximum allowed attribute per span link count.
DefaultAttributePerLinkCountLimit = 128
)

View File

@@ -29,7 +29,12 @@ func newEvictedQueue(capacity int) evictedQueue {
// add adds value to the evictedQueue eq. If eq is at capacity, the oldest
// queued value will be discarded and the drop count incremented.
func (eq *evictedQueue) add(value interface{}) {
if len(eq.queue) == eq.capacity {
if eq.capacity == 0 {
eq.droppedCount++
return
}
if eq.capacity > 0 && len(eq.queue) == eq.capacity {
// Drop first-in while avoiding allocating more capacity to eq.queue.
copy(eq.queue[:eq.capacity-1], eq.queue[1:])
eq.queue = eq.queue[:eq.capacity-1]

View File

@@ -96,9 +96,10 @@ var _ trace.TracerProvider = &TracerProvider{}
// The passed opts are used to override these default values and configure the
// returned TracerProvider appropriately.
func NewTracerProvider(opts ...TracerProviderOption) *TracerProvider {
o := tracerProviderConfig{}
o := tracerProviderConfig{
spanLimits: NewSpanLimits(),
}
o.spanLimits.parsePotentialEnvConfigs()
for _, opt := range opts {
o = opt.apply(o)
}
@@ -345,20 +346,68 @@ func WithSampler(s Sampler) TracerProviderOption {
})
}
// WithSpanLimits returns a TracerProviderOption that will configure the
// SpanLimits sl as a TracerProvider's SpanLimits. The configured SpanLimits
// are used used by the Tracers the TracerProvider and the Spans they create
// to limit tracing resources used.
// WithSpanLimits returns a TracerProviderOption that configures a
// TracerProvider to use the SpanLimits sl. These SpanLimits bound any Span
// created by a Tracer from the TracerProvider.
//
// If this option is not used, the TracerProvider will use the default
// SpanLimits.
// If any field of sl is zero or negative it will be replaced with the default
// value for that field.
//
// If this or WithRawSpanLimits are not provided, the TracerProvider will use
// the limits defined by environment variables, or the defaults if unset.
// Refer to the NewSpanLimits documentation for information about this
// relationship.
//
// Deprecated: Use WithRawSpanLimits instead which allows setting unlimited
// and zero limits. This option will be kept until the next major version
// incremented release.
func WithSpanLimits(sl SpanLimits) TracerProviderOption {
if sl.AttributeValueLengthLimit <= 0 {
sl.AttributeValueLengthLimit = DefaultAttributeValueLengthLimit
}
if sl.AttributeCountLimit <= 0 {
sl.AttributeCountLimit = DefaultAttributeCountLimit
}
if sl.EventCountLimit <= 0 {
sl.EventCountLimit = DefaultEventCountLimit
}
if sl.AttributePerEventCountLimit <= 0 {
sl.AttributePerEventCountLimit = DefaultAttributePerEventCountLimit
}
if sl.LinkCountLimit <= 0 {
sl.LinkCountLimit = DefaultLinkCountLimit
}
if sl.AttributePerLinkCountLimit <= 0 {
sl.AttributePerLinkCountLimit = DefaultAttributePerLinkCountLimit
}
return traceProviderOptionFunc(func(cfg tracerProviderConfig) tracerProviderConfig {
cfg.spanLimits = sl
return cfg
})
}
// WithRawSpanLimits returns a TracerProviderOption that configures a
// TracerProvider to use these limits. These limits bound any Span created by
// a Tracer from the TracerProvider.
//
// The limits will be used as-is. Zero or negative values will not be changed
// to the default value like WithSpanLimits does. Setting a limit to zero will
// effectively disable the related resource it limits and setting to a
// negative value will mean that resource is unlimited. Consequentially, this
// means that the zero-value SpanLimits will disable all span resources.
// Because of this, limits should be constructed using NewSpanLimits and
// updated accordingly.
//
// If this or WithSpanLimits are not provided, the TracerProvider will use the
// limits defined by environment variables, or the defaults if unset. Refer to
// the NewSpanLimits documentation for information about this relationship.
func WithRawSpanLimits(limits SpanLimits) TracerProviderOption {
return traceProviderOptionFunc(func(cfg tracerProviderConfig) tracerProviderConfig {
cfg.spanLimits = limits
return cfg
})
}
// ensureValidTracerProviderConfig ensures that given TracerProviderConfig is valid.
func ensureValidTracerProviderConfig(cfg tracerProviderConfig) tracerProviderConfig {
if cfg.sampler == nil {
@@ -367,7 +416,6 @@ func ensureValidTracerProviderConfig(cfg tracerProviderConfig) tracerProviderCon
if cfg.idGenerator == nil {
cfg.idGenerator = defaultIDGenerator()
}
cfg.spanLimits.ensureDefault()
if cfg.resource == nil {
cfg.resource = resource.Default()
}

View File

@@ -17,14 +17,8 @@ package trace
import (
"context"
"errors"
"os"
"testing"
"github.com/stretchr/testify/require"
ottest "go.opentelemetry.io/otel/internal/internaltest"
"go.opentelemetry.io/otel/sdk/internal/env"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/otel/trace"
@@ -100,58 +94,3 @@ func TestSchemaURL(t *testing.T) {
tracerStruct := tracerIface.(*tracer)
assert.EqualValues(t, schemaURL, tracerStruct.instrumentationLibrary.SchemaURL)
}
func TestNewTraceProviderWithoutSpanLimitConfiguration(t *testing.T) {
envStore := ottest.NewEnvStore()
defer func() {
require.NoError(t, envStore.Restore())
}()
envStore.Record(env.SpanAttributesCountKey)
envStore.Record(env.SpanEventCountKey)
envStore.Record(env.SpanLinkCountKey)
require.NoError(t, os.Setenv(env.SpanEventCountKey, "111"))
require.NoError(t, os.Setenv(env.SpanAttributesCountKey, "222"))
require.NoError(t, os.Setenv(env.SpanLinkCountKey, "333"))
tp := NewTracerProvider()
assert.Equal(t, 111, tp.spanLimits.EventCountLimit)
assert.Equal(t, 222, tp.spanLimits.AttributeCountLimit)
assert.Equal(t, 333, tp.spanLimits.LinkCountLimit)
}
func TestNewTraceProviderWithSpanLimitConfigurationFromOptsAndEnvironmentVariable(t *testing.T) {
envStore := ottest.NewEnvStore()
defer func() {
require.NoError(t, envStore.Restore())
}()
envStore.Record(env.SpanAttributesCountKey)
envStore.Record(env.SpanEventCountKey)
envStore.Record(env.SpanLinkCountKey)
require.NoError(t, os.Setenv(env.SpanEventCountKey, "111"))
require.NoError(t, os.Setenv(env.SpanAttributesCountKey, "222"))
require.NoError(t, os.Setenv(env.SpanLinkCountKey, "333"))
tp := NewTracerProvider(WithSpanLimits(SpanLimits{
EventCountLimit: 1,
AttributeCountLimit: 2,
LinkCountLimit: 3,
}))
assert.Equal(t, 1, tp.spanLimits.EventCountLimit)
assert.Equal(t, 2, tp.spanLimits.AttributeCountLimit)
assert.Equal(t, 3, tp.spanLimits.LinkCountLimit)
}
func TestNewTraceProviderWithInvalidSpanLimitConfigurationFromEnvironmentVariable(t *testing.T) {
envStore := ottest.NewEnvStore()
defer func() {
require.NoError(t, envStore.Restore())
}()
envStore.Record(env.SpanAttributesCountKey)
envStore.Record(env.SpanEventCountKey)
envStore.Record(env.SpanLinkCountKey)
require.NoError(t, os.Setenv(env.SpanEventCountKey, "-111"))
require.NoError(t, os.Setenv(env.SpanAttributesCountKey, "-222"))
require.NoError(t, os.Setenv(env.SpanLinkCountKey, "-333"))
tp := NewTracerProvider()
assert.Equal(t, 128, tp.spanLimits.EventCountLimit)
assert.Equal(t, 128, tp.spanLimits.AttributeCountLimit)
assert.Equal(t, 128, tp.spanLimits.LinkCountLimit)
}

View File

@@ -212,10 +212,17 @@ func (s *recordingSpan) SetAttributes(attributes ...attribute.KeyValue) {
s.mu.Lock()
defer s.mu.Unlock()
limit := s.tracer.provider.spanLimits.AttributeCountLimit
if limit == 0 {
// No attributes allowed.
s.droppedAttributes += len(attributes)
return
}
// If adding these attributes could exceed the capacity of s perform a
// de-duplication and truncation while adding to avoid over allocation.
if len(s.attributes)+len(attributes) > s.tracer.provider.spanLimits.AttributeCountLimit {
s.addOverCapAttrs(attributes)
if limit > 0 && len(s.attributes)+len(attributes) > limit {
s.addOverCapAttrs(limit, attributes)
return
}
@@ -227,21 +234,25 @@ func (s *recordingSpan) SetAttributes(attributes ...attribute.KeyValue) {
s.droppedAttributes++
continue
}
a = truncateAttr(s.tracer.provider.spanLimits.AttributeValueLengthLimit, a)
s.attributes = append(s.attributes, a)
}
}
// addOverCapAttrs adds the attributes attrs to the span s while
// de-duplicating the attributes of s and attrs and dropping attributes that
// exceed the capacity of s.
// exceed the limit.
//
// This method assumes s.mu.Lock is held by the caller.
//
// This method should only be called when there is a possibility that adding
// attrs to s will exceed the capacity of s. Otherwise, attrs should be added
// to s without checking for duplicates and all retrieval methods of the
// attributes for s will de-duplicate as needed.
func (s *recordingSpan) addOverCapAttrs(attrs []attribute.KeyValue) {
// attrs to s will exceed the limit. Otherwise, attrs should be added to s
// without checking for duplicates and all retrieval methods of the attributes
// for s will de-duplicate as needed.
//
// This method assumes limit is a value > 0. The argument should be validated
// by the caller.
func (s *recordingSpan) addOverCapAttrs(limit int, attrs []attribute.KeyValue) {
// In order to not allocate more capacity to s.attributes than needed,
// prune and truncate this addition of attributes while adding.
@@ -265,17 +276,58 @@ func (s *recordingSpan) addOverCapAttrs(attrs []attribute.KeyValue) {
continue
}
if len(s.attributes) >= s.tracer.provider.spanLimits.AttributeCountLimit {
if len(s.attributes) >= limit {
// Do not just drop all of the remaining attributes, make sure
// updates are checked and performed.
s.droppedAttributes++
} else {
a = truncateAttr(s.tracer.provider.spanLimits.AttributeValueLengthLimit, a)
s.attributes = append(s.attributes, a)
exists[a.Key] = len(s.attributes) - 1
}
}
}
// truncateAttr returns a truncated version of attr. Only string and string
// slice attribute values are truncated. String values are truncated to at
// most a length of limit. Each string slice value is truncated in this fasion
// (the slice length itself is unaffected).
//
// No truncation is perfromed for a negative limit.
func truncateAttr(limit int, attr attribute.KeyValue) attribute.KeyValue {
if limit < 0 {
return attr
}
switch attr.Value.Type() {
case attribute.STRING:
if v := attr.Value.AsString(); len(v) > limit {
return attr.Key.String(v[:limit])
}
case attribute.STRINGSLICE:
// Do no mutate the original, make a copy.
trucated := attr.Key.StringSlice(attr.Value.AsStringSlice())
// Do not do this.
//
// v := trucated.Value.AsStringSlice()
// cp := make([]string, len(v))
// /* Copy and truncate values to cp ... */
// trucated.Value = attribute.StringSliceValue(cp)
//
// Copying the []string and then assigning it back as a new value with
// attribute.StringSliceValue will copy the data twice. Instead, we
// already made a copy above that only this function owns, update the
// underlying slice data of our copy.
v := trucated.Value.AsStringSlice()
for i := range v {
if len(v[i]) > limit {
v[i] = v[i][:limit]
}
}
return trucated
}
return attr
}
// End ends the span. This method does nothing if the span is already ended or
// is not being recorded.
//
@@ -396,22 +448,23 @@ func (s *recordingSpan) AddEvent(name string, o ...trace.EventOption) {
func (s *recordingSpan) addEvent(name string, o ...trace.EventOption) {
c := trace.NewEventConfig(o...)
e := Event{Name: name, Attributes: c.Attributes(), Time: c.Timestamp()}
// Discard over limited attributes
attributes := c.Attributes()
var discarded int
if len(attributes) > s.tracer.provider.spanLimits.AttributePerEventCountLimit {
discarded = len(attributes) - s.tracer.provider.spanLimits.AttributePerEventCountLimit
attributes = attributes[:s.tracer.provider.spanLimits.AttributePerEventCountLimit]
// Discard attributes over limit.
limit := s.tracer.provider.spanLimits.AttributePerEventCountLimit
if limit == 0 {
// Drop all attributes.
e.DroppedAttributeCount = len(e.Attributes)
e.Attributes = nil
} else if limit > 0 && len(e.Attributes) > limit {
// Drop over capacity.
e.DroppedAttributeCount = len(e.Attributes) - limit
e.Attributes = e.Attributes[:limit]
}
s.mu.Lock()
defer s.mu.Unlock()
s.events.add(Event{
Name: name,
Attributes: attributes,
DroppedAttributeCount: discarded,
Time: c.Timestamp(),
})
s.events.add(e)
s.mu.Unlock()
}
// SetName sets the name of this span. If this span is not being recorded than
@@ -551,18 +604,23 @@ func (s *recordingSpan) addLink(link trace.Link) {
if !s.IsRecording() || !link.SpanContext.IsValid() {
return
}
s.mu.Lock()
defer s.mu.Unlock()
var droppedAttributeCount int
l := Link{SpanContext: link.SpanContext, Attributes: link.Attributes}
// Discard over limited attributes
if len(link.Attributes) > s.tracer.provider.spanLimits.AttributePerLinkCountLimit {
droppedAttributeCount = len(link.Attributes) - s.tracer.provider.spanLimits.AttributePerLinkCountLimit
link.Attributes = link.Attributes[:s.tracer.provider.spanLimits.AttributePerLinkCountLimit]
// Discard attributes over limit.
limit := s.tracer.provider.spanLimits.AttributePerLinkCountLimit
if limit == 0 {
// Drop all attributes.
l.DroppedAttributeCount = len(l.Attributes)
l.Attributes = nil
} else if limit > 0 && len(l.Attributes) > limit {
l.DroppedAttributeCount = len(l.Attributes) - limit
l.Attributes = l.Attributes[:limit]
}
s.links.add(Link{link.SpanContext, link.Attributes, droppedAttributeCount})
s.mu.Lock()
s.links.add(l)
s.mu.Unlock()
}
// DroppedAttributes returns the number of attributes dropped by the span

126
sdk/trace/span_limits.go Normal file
View File

@@ -0,0 +1,126 @@
// 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 "go.opentelemetry.io/otel/sdk/trace"
import "go.opentelemetry.io/otel/sdk/internal/env"
const (
// DefaultAttributeValueLengthLimit is the default maximum allowed
// attribute value length, unlimited.
DefaultAttributeValueLengthLimit = -1
// DefaultAttributeCountLimit is the default maximum number of attributes
// a span can have.
DefaultAttributeCountLimit = 128
// DefaultEventCountLimit is the default maximum number of events a span
// can have.
DefaultEventCountLimit = 128
// DefaultLinkCountLimit is the default maximum number of links a span can
// have.
DefaultLinkCountLimit = 128
// DefaultAttributePerEventCountLimit is the default maximum number of
// attributes a span event can have.
DefaultAttributePerEventCountLimit = 128
// DefaultAttributePerLinkCountLimit is the default maximum number of
// attributes a span link can have.
DefaultAttributePerLinkCountLimit = 128
)
// SpanLimits represents the limits of a span.
type SpanLimits struct {
// AttributeValueLengthLimit is the maximum allowed attribute value length.
//
// This limit only applies to string and string slice attribute values.
// Any string longer than this value will be truncated to this length.
//
// Setting this to a negative value means no limit is applied.
AttributeValueLengthLimit int
// AttributeCountLimit is the maximum allowed span attribute count. Any
// attribute added to a span once this limit is reached will be dropped.
//
// Setting this to zero means no attributes will be recorded.
//
// Setting this to a negative value means no limit is applied.
AttributeCountLimit int
// EventCountLimit is the maximum allowed span event count. Any event
// added to a span once this limit is reached means it will be added but
// the oldest event will be dropped.
//
// Setting this to zero means no events we be recorded.
//
// Setting this to a negative value means no limit is applied.
EventCountLimit int
// LinkCountLimit is the maximum allowed span link count. Any link added
// to a span once this limit is reached means it will be added but the
// oldest link will be dropped.
//
// Setting this to zero means no links we be recorded.
//
// Setting this to a negative value means no limit is applied.
LinkCountLimit int
// AttributePerEventCountLimit is the maximum number of attributes allowed
// per span event. Any attribute added after this limit reached will be
// dropped.
//
// Setting this to zero means no attributes will be recorded for events.
//
// Setting this to a negative value means no limit is applied.
AttributePerEventCountLimit int
// AttributePerLinkCountLimit is the maximum number of attributes allowed
// per span link. Any attribute added after this limit reached will be
// dropped.
//
// Setting this to zero means no attributes will be recorded for links.
//
// Setting this to a negative value means no limit is applied.
AttributePerLinkCountLimit int
}
// NewSpanLimits returns a SpanLimits with all limits set to the value their
// corresponding environment variable holds, or the default if unset.
//
// • AttributeValueLengthLimit: OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT
// (default: unlimited)
//
// • AttributeCountLimit: OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT (default: 128)
//
// • EventCountLimit: OTEL_SPAN_EVENT_COUNT_LIMIT (default: 128)
//
// • AttributePerEventCountLimit: OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT (default:
// 128)
//
// • LinkCountLimit: OTEL_SPAN_LINK_COUNT_LIMIT (default: 128)
//
// • AttributePerLinkCountLimit: OTEL_LINK_ATTRIBUTE_COUNT_LIMIT (default:
// 128)
func NewSpanLimits() SpanLimits {
return SpanLimits{
AttributeValueLengthLimit: env.SpanAttributeValueLength(DefaultAttributeValueLengthLimit),
AttributeCountLimit: env.SpanAttributeCount(DefaultAttributeCountLimit),
EventCountLimit: env.SpanEventCount(DefaultEventCountLimit),
LinkCountLimit: env.SpanLinkCount(DefaultLinkCountLimit),
AttributePerEventCountLimit: env.SpanEventAttributeCount(DefaultAttributePerEventCountLimit),
AttributePerLinkCountLimit: env.SpanLinkAttributeCount(DefaultAttributePerLinkCountLimit),
}
}

View File

@@ -0,0 +1,283 @@
// 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 (
"context"
"os"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/attribute"
ottest "go.opentelemetry.io/otel/internal/internaltest"
"go.opentelemetry.io/otel/sdk/internal/env"
"go.opentelemetry.io/otel/trace"
)
func TestSettingSpanLimits(t *testing.T) {
envLimits := func(val string) map[string]string {
return map[string]string{
env.SpanAttributeValueLengthKey: val,
env.SpanEventCountKey: val,
env.SpanAttributeCountKey: val,
env.SpanLinkCountKey: val,
env.SpanEventAttributeCountKey: val,
env.SpanLinkAttributeCountKey: val,
}
}
limits := func(n int) *SpanLimits {
lims := NewSpanLimits()
lims.AttributeValueLengthLimit = n
lims.AttributeCountLimit = n
lims.EventCountLimit = n
lims.LinkCountLimit = n
lims.AttributePerEventCountLimit = n
lims.AttributePerLinkCountLimit = n
return &lims
}
tests := []struct {
name string
env map[string]string
opt *SpanLimits
rawOpt *SpanLimits
want SpanLimits
}{
{
name: "defaults",
want: NewSpanLimits(),
},
{
name: "env",
env: envLimits("42"),
want: *(limits(42)),
},
{
name: "opt",
opt: limits(42),
want: *(limits(42)),
},
{
name: "raw-opt",
rawOpt: limits(42),
want: *(limits(42)),
},
{
name: "opt-override",
env: envLimits("-2"),
// Option take priority.
opt: limits(43),
want: *(limits(43)),
},
{
name: "raw-opt-override",
env: envLimits("-2"),
// Option take priority.
rawOpt: limits(43),
want: *(limits(43)),
},
{
name: "last-opt-wins",
opt: limits(-2),
rawOpt: limits(-3),
want: *(limits(-3)),
},
{
name: "env(unlimited)",
// OTel spec says negative SpanLinkAttributeCountKey is invalid,
// but since we will revert to the default (unlimited) which uses
// negative values to signal this than this value is expected to
// pass through.
env: envLimits("-1"),
want: *(limits(-1)),
},
{
name: "opt(unlimited)",
// Corrects to defaults.
opt: limits(-1),
want: NewSpanLimits(),
},
{
name: "raw-opt(unlimited)",
rawOpt: limits(-1),
want: *(limits(-1)),
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
if test.env != nil {
es := ottest.NewEnvStore()
t.Cleanup(func() { require.NoError(t, es.Restore()) })
for k, v := range test.env {
es.Record(k)
require.NoError(t, os.Setenv(k, v))
}
}
var opts []TracerProviderOption
if test.opt != nil {
opts = append(opts, WithSpanLimits(*test.opt))
}
if test.rawOpt != nil {
opts = append(opts, WithRawSpanLimits(*test.rawOpt))
}
assert.Equal(t, test.want, NewTracerProvider(opts...).spanLimits)
})
}
}
type recorder []ReadOnlySpan
func (r *recorder) OnStart(context.Context, ReadWriteSpan) {}
func (r *recorder) OnEnd(s ReadOnlySpan) { *r = append(*r, s) }
func (r *recorder) ForceFlush(context.Context) error { return nil }
func (r *recorder) Shutdown(context.Context) error { return nil }
func testSpanLimits(t *testing.T, limits SpanLimits) ReadOnlySpan {
rec := new(recorder)
tp := NewTracerProvider(WithRawSpanLimits(limits), WithSpanProcessor(rec))
tracer := tp.Tracer("testSpanLimits")
ctx := context.Background()
a := []attribute.KeyValue{attribute.Bool("one", true), attribute.Bool("two", true)}
l := trace.Link{
SpanContext: trace.NewSpanContext(trace.SpanContextConfig{
TraceID: [16]byte{0x01},
SpanID: [8]byte{0x01},
}),
Attributes: a,
}
_, span := tracer.Start(ctx, "span-name", trace.WithLinks(l, l))
span.SetAttributes(
attribute.String("string", "abc"),
attribute.StringSlice("stringSlice", []string{"abc", "def"}),
)
span.AddEvent("event 1", trace.WithAttributes(a...))
span.AddEvent("event 2", trace.WithAttributes(a...))
span.End()
require.NoError(t, tp.Shutdown(ctx))
require.Len(t, *rec, 1, "exported spans")
return (*rec)[0]
}
func TestSpanLimits(t *testing.T) {
t.Run("AttributeValueLengthLimit", func(t *testing.T) {
limits := NewSpanLimits()
// Unlimited.
limits.AttributeValueLengthLimit = -1
attrs := testSpanLimits(t, limits).Attributes()
assert.Contains(t, attrs, attribute.String("string", "abc"))
assert.Contains(t, attrs, attribute.StringSlice("stringSlice", []string{"abc", "def"}))
limits.AttributeValueLengthLimit = 2
attrs = testSpanLimits(t, limits).Attributes()
// Ensure string and string slice attributes are truncated.
assert.Contains(t, attrs, attribute.String("string", "ab"))
assert.Contains(t, attrs, attribute.StringSlice("stringSlice", []string{"ab", "de"}))
limits.AttributeValueLengthLimit = 0
attrs = testSpanLimits(t, limits).Attributes()
assert.Contains(t, attrs, attribute.String("string", ""))
assert.Contains(t, attrs, attribute.StringSlice("stringSlice", []string{"", ""}))
})
t.Run("AttributeCountLimit", func(t *testing.T) {
limits := NewSpanLimits()
// Unlimited.
limits.AttributeCountLimit = -1
assert.Len(t, testSpanLimits(t, limits).Attributes(), 2)
limits.AttributeCountLimit = 1
assert.Len(t, testSpanLimits(t, limits).Attributes(), 1)
// Ensure this can be disabled.
limits.AttributeCountLimit = 0
assert.Len(t, testSpanLimits(t, limits).Attributes(), 0)
})
t.Run("EventCountLimit", func(t *testing.T) {
limits := NewSpanLimits()
// Unlimited.
limits.EventCountLimit = -1
assert.Len(t, testSpanLimits(t, limits).Events(), 2)
limits.EventCountLimit = 1
assert.Len(t, testSpanLimits(t, limits).Events(), 1)
// Ensure this can be disabled.
limits.EventCountLimit = 0
assert.Len(t, testSpanLimits(t, limits).Events(), 0)
})
t.Run("AttributePerEventCountLimit", func(t *testing.T) {
limits := NewSpanLimits()
// Unlimited.
limits.AttributePerEventCountLimit = -1
for _, e := range testSpanLimits(t, limits).Events() {
assert.Len(t, e.Attributes, 2)
}
limits.AttributePerEventCountLimit = 1
for _, e := range testSpanLimits(t, limits).Events() {
assert.Len(t, e.Attributes, 1)
}
// Ensure this can be disabled.
limits.AttributePerEventCountLimit = 0
for _, e := range testSpanLimits(t, limits).Events() {
assert.Len(t, e.Attributes, 0)
}
})
t.Run("LinkCountLimit", func(t *testing.T) {
limits := NewSpanLimits()
// Unlimited.
limits.LinkCountLimit = -1
assert.Len(t, testSpanLimits(t, limits).Links(), 2)
limits.LinkCountLimit = 1
assert.Len(t, testSpanLimits(t, limits).Links(), 1)
// Ensure this can be disabled.
limits.LinkCountLimit = 0
assert.Len(t, testSpanLimits(t, limits).Links(), 0)
})
t.Run("AttributePerLinkCountLimit", func(t *testing.T) {
limits := NewSpanLimits()
// Unlimited.
limits.AttributePerLinkCountLimit = -1
for _, l := range testSpanLimits(t, limits).Links() {
assert.Len(t, l.Attributes, 2)
}
limits.AttributePerLinkCountLimit = 1
for _, l := range testSpanLimits(t, limits).Links() {
assert.Len(t, l.Attributes, 1)
}
// Ensure this can be disabled.
limits.AttributePerLinkCountLimit = 0
for _, l := range testSpanLimits(t, limits).Links() {
assert.Len(t, l.Attributes, 0)
}
})
}

145
sdk/trace/span_test.go Normal file
View File

@@ -0,0 +1,145 @@
// 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 (
"bytes"
"fmt"
"testing"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/otel/attribute"
)
func TestTruncateAttr(t *testing.T) {
const key = "key"
strAttr := attribute.String(key, "value")
strSliceAttr := attribute.StringSlice(key, []string{"value-0", "value-1"})
tests := []struct {
limit int
attr, want attribute.KeyValue
}{
{
limit: -1,
attr: strAttr,
want: strAttr,
},
{
limit: -1,
attr: strSliceAttr,
want: strSliceAttr,
},
{
limit: 0,
attr: attribute.Bool(key, true),
want: attribute.Bool(key, true),
},
{
limit: 0,
attr: attribute.BoolSlice(key, []bool{true, false}),
want: attribute.BoolSlice(key, []bool{true, false}),
},
{
limit: 0,
attr: attribute.Int(key, 42),
want: attribute.Int(key, 42),
},
{
limit: 0,
attr: attribute.IntSlice(key, []int{42, -1}),
want: attribute.IntSlice(key, []int{42, -1}),
},
{
limit: 0,
attr: attribute.Int64(key, 42),
want: attribute.Int64(key, 42),
},
{
limit: 0,
attr: attribute.Int64Slice(key, []int64{42, -1}),
want: attribute.Int64Slice(key, []int64{42, -1}),
},
{
limit: 0,
attr: attribute.Float64(key, 42),
want: attribute.Float64(key, 42),
},
{
limit: 0,
attr: attribute.Float64Slice(key, []float64{42, -1}),
want: attribute.Float64Slice(key, []float64{42, -1}),
},
{
limit: 0,
attr: strAttr,
want: attribute.String(key, ""),
},
{
limit: 0,
attr: strSliceAttr,
want: attribute.StringSlice(key, []string{"", ""}),
},
{
limit: 0,
attr: attribute.Stringer(key, bytes.NewBufferString("value")),
want: attribute.String(key, ""),
},
{
limit: 1,
attr: strAttr,
want: attribute.String(key, "v"),
},
{
limit: 1,
attr: strSliceAttr,
want: attribute.StringSlice(key, []string{"v", "v"}),
},
{
limit: 5,
attr: strAttr,
want: strAttr,
},
{
limit: 7,
attr: strSliceAttr,
want: strSliceAttr,
},
{
limit: 6,
attr: attribute.StringSlice(key, []string{"value", "value-1"}),
want: attribute.StringSlice(key, []string{"value", "value-"}),
},
{
limit: 128,
attr: strAttr,
want: strAttr,
},
{
limit: 128,
attr: strSliceAttr,
want: strSliceAttr,
},
}
for _, test := range tests {
name := fmt.Sprintf("%s->%s(limit:%d)", test.attr.Key, test.attr.Value.Emit(), test.limit)
t.Run(name, func(t *testing.T) {
assert.Equal(t, test.want, truncateAttr(test.limit, test.attr))
})
}
}

View File

@@ -604,10 +604,9 @@ func TestSpanSetAttributes(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
te := NewTestExporter()
tp := NewTracerProvider(
WithSyncer(te),
WithSpanLimits(SpanLimits{AttributeCountLimit: capacity}),
)
sl := NewSpanLimits()
sl.AttributeCountLimit = capacity
tp := NewTracerProvider(WithSyncer(te), WithSpanLimits(sl))
_, span := tp.Tracer(instName).Start(context.Background(), spanName)
for _, a := range test.input {
span.SetAttributes(a...)
@@ -677,7 +676,9 @@ func TestEvents(t *testing.T) {
func TestEventsOverLimit(t *testing.T) {
te := NewTestExporter()
tp := NewTracerProvider(WithSpanLimits(SpanLimits{EventCountLimit: 2}), WithSyncer(te), WithResource(resource.Empty()))
sl := NewSpanLimits()
sl.EventCountLimit = 2
tp := NewTracerProvider(WithSpanLimits(sl), WithSyncer(te), WithResource(resource.Empty()))
span := startSpan(tp, "EventsOverLimit")
k1v1 := attribute.String("key1", "value1")
@@ -779,7 +780,9 @@ func TestLinksOverLimit(t *testing.T) {
sc2 := trace.NewSpanContext(trace.SpanContextConfig{TraceID: trace.TraceID([16]byte{1, 1}), SpanID: trace.SpanID{3}})
sc3 := trace.NewSpanContext(trace.SpanContextConfig{TraceID: trace.TraceID([16]byte{1, 1}), SpanID: trace.SpanID{3}})
tp := NewTracerProvider(WithSpanLimits(SpanLimits{LinkCountLimit: 2}), WithSyncer(te), WithResource(resource.Empty()))
sl := NewSpanLimits()
sl.LinkCountLimit = 2
tp := NewTracerProvider(WithSpanLimits(sl), WithSyncer(te), WithResource(resource.Empty()))
span := startSpan(tp, "LinksOverLimit",
trace.WithLinks(
@@ -1637,8 +1640,10 @@ func TestReadWriteSpan(t *testing.T) {
func TestAddEventsWithMoreAttributesThanLimit(t *testing.T) {
te := NewTestExporter()
sl := NewSpanLimits()
sl.AttributePerEventCountLimit = 2
tp := NewTracerProvider(
WithSpanLimits(SpanLimits{AttributePerEventCountLimit: 2}),
WithSpanLimits(sl),
WithSyncer(te),
WithResource(resource.Empty()),
)
@@ -1701,8 +1706,10 @@ func TestAddEventsWithMoreAttributesThanLimit(t *testing.T) {
func TestAddLinksWithMoreAttributesThanLimit(t *testing.T) {
te := NewTestExporter()
sl := NewSpanLimits()
sl.AttributePerLinkCountLimit = 1
tp := NewTracerProvider(
WithSpanLimits(SpanLimits{AttributePerLinkCountLimit: 1}),
WithSpanLimits(sl),
WithSyncer(te),
WithResource(resource.Empty()),
)