You've already forked opentelemetry-go
							
							
				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:
		
							
								
								
									
										23
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										23
									
								
								CHANGELOG.md
									
									
									
									
									
								
							| @@ -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 | ||||
|  | ||||
|   | ||||
							
								
								
									
										61
									
								
								sdk/internal/env/env.go
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										61
									
								
								sdk/internal/env/env.go
									
									
									
									
										vendored
									
									
								
							| @@ -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) | ||||
| } | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
| @@ -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 | ||||
| ) | ||||
| @@ -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] | ||||
|   | ||||
| @@ -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() | ||||
| 	} | ||||
|   | ||||
| @@ -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) | ||||
| } | ||||
|   | ||||
| @@ -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
									
								
							
							
						
						
									
										126
									
								
								sdk/trace/span_limits.go
									
									
									
									
									
										Normal 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), | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										283
									
								
								sdk/trace/span_limits_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										283
									
								
								sdk/trace/span_limits_test.go
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										145
									
								
								sdk/trace/span_test.go
									
									
									
									
									
										Normal 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)) | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
| @@ -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()), | ||||
| 	) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user