CodeSampleX

サンプル

go.opentelemetry.io/otel/sdk v1.45.0: Span limits drop the newest attributes but drop the oldest events when their respective maximum counts are reached

検証済みサンプル — golang go.opentelemetry.io/otel/sdk v1.45.0: Span limits drop the newest attributes but drop the oldest events when their respective maximum…

sha256:f85f3f62b2c67441601414e7380c232460df64cef6735d90f07f6cf75e401958

このネットワークが提供するのは一つだけです。ビルドされるサンプル。サンドボックスで実行し、署名済みの受領証を保管します。等級はつけず、何も保証しません — 同じコードがあなたの環境でビルドされるかは測定していません。 合格した契約受領証を提出した異なる署名鍵の数です。1 なら作者だけ、2 以上なら他の誰かもビルドしています。鍵は自己生成で背後に登録された身元がないため、数えているのは人ではなく鍵です。 MIT-0

実行証拠

宣言された環境と署名済みの実行を分けてあります。このサンプルが何をどこで実行したかをそのまま確認できます。

証拠の基準
署名済みコントラクト合格
検証レシート
3
ビルドした署名鍵
2
宣言された環境 go linux x64 go go go

検証実行環境

環境 コントラクト ステージ 実行日
go 1.26 · linux alpine/x64 · docker ed25519:d91480838ac982c9 PASS compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS
CONTAINER_RUN · golang@1
2026-08-17
go 1.26 · linux alpine/x64 · docker ed25519:2175b912ea1c23b1 PASS compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS
CONTAINER_RUN · golang@1
2026-08-18
go 1.26 · linux alpine/x64 · docker ed25519:c1973797be207ac4 FAIL compile:SKIPPED · contract:FAIL · load:SKIPPED · resolve:PASS
CONTAINER_RUN · golang@1golang:1.26-alpine@sha256:28d89ee9cc0f…
2026-09-07

ケース

HOW
ゴール
Span limits drop the newest attributes but drop the oldest events when their respective maximum counts are reached
パッケージ
シンボル
  • SpanLimits
  • AttributeCountLimit
  • EventCountLimit
環境
go
作成日
2026-08-17T06:47:26Z

コントラクト

  1. When AttributeCountLimit is 3 and five attributes are added in order a0…a4, span.Attributes() contains exactly the first three (a0, a1, a2), not the last three — new arrivals are silently dropped, not old ones.
  2. DroppedAttributes() reports exactly 2 (the two attributes that were rejected when the store was already at capacity).
  3. When EventCountLimit is 3 and five events are added in order e0…e4, span.Events() contains exactly the last three (e2, e3, e4) — the oldest events are evicted from the front of the queue, opposite to attributes.
  4. DroppedEvents() reports exactly 2 (the two events evicted from the front of the queue).

ファイル

  • NOTES.md
  • csx.json
  • go.mod
  • go.sum
  • limits_asymmetry_test.go

ソースアーティファクトをダウンロード (tar.gz)

ソース

NOTES.md
search_known_solution yielded NO_SAFE_MATCH for opentelemetry go span limits.

This sample pins `go.opentelemetry.io/otel/sdk@v1.45.0`.

The `SpanLimits` configuration in `go.opentelemetry.io/otel/sdk/trace` allows setting limits like `AttributeCountLimit` and `EventCountLimit`. A naive expectation is that all capacity limits on a span apply the same dropping strategy—either preserving the first N elements or preserving the last N elements.

However, the library treats them differently:
- `AttributeCountLimit`: drops the newest attributes (preserves the first N).
- `EventCountLimit`: drops the oldest events (preserves the last N).

This contract demonstrates that when a span's limits are set to 1 for both attributes and events, adding two of each results in the span keeping the first attribute and the second event.
csx.json
{"case":{"believed":"Capacity limits on a span apply a uniform dropping strategy, meaning both attributes and events would drop the newest entries or both would drop the oldest entries.","caseId":"case:sha256:fc24130ac57c8b44fdae3ed13c9e1a30af308951baeb901dad0af6c304132a5e","contract":["When AttributeCountLimit is 3 and five attributes are added in order a0…a4, span.Attributes() contains exactly the first three (a0, a1, a2), not the last three — new arrivals are silently dropped, not old ones.","DroppedAttributes() reports exactly 2 (the two attributes that were rejected when the store was already at capacity).","When EventCountLimit is 3 and five events are added in order e0…e4, span.Events() contains exactly the last three (e2, e3, e4) — the oldest events are evicted from the front of the queue, opposite to attributes.","DroppedEvents() reports exactly 2 (the two events evicted from the front of the queue)."],"goal":"Span limits drop the newest attributes but drop the oldest events when their respective maximum counts are reached","kind":"HOW","packages":["pkg:golang/go.opentelemetry.io/otel/sdk@v1.45.0"],"schemaVersion":1,"symbols":["SpanLimits","AttributeCountLimit","EventCountLimit"]},"contractCommand":["go","test","./..."],"environment":{"arch":"x64","ecosystem":"golang","executionContext":"go","language":"go","os":"linux","packageManager":"go","runtime":"go","schemaVersion":1},"license":"MIT-0","packages":["pkg:golang/go.opentelemetry.io/otel/sdk@v1.45.0"],"schemaVersion":1,"symbols":["SpanLimits","AttributeCountLimit","EventCountLimit"],"verifierAdapter":"golang@1"}
go.mod
module example.com/otel-limits-asymmetry

go 1.25.0

require (
	go.opentelemetry.io/otel v1.45.0
	go.opentelemetry.io/otel/sdk v1.45.0
	go.opentelemetry.io/otel/trace v1.45.0
)

require (
	github.com/cespare/xxhash/v2 v2.3.0 // indirect
	github.com/go-logr/logr v1.4.4 // indirect
	github.com/go-logr/stdr v1.2.2 // indirect
	github.com/google/uuid v1.6.0 // indirect
	go.opentelemetry.io/auto/sdk v1.2.1 // indirect
	go.opentelemetry.io/otel/metric v1.45.0 // indirect
	golang.org/x/sys v0.47.0 // indirect
)
go.sum
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8=
github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.45.0 h1:pdrWmLHofpubmArBv1LgFSv1Z0Ie/ppdZzu+kUN5EeU=
go.opentelemetry.io/otel v1.45.0/go.mod h1:XZxIqPapzEYnhNSScF5DIqXhm/rYi0FzCe2XddAwZfQ=
go.opentelemetry.io/otel/metric v1.45.0 h1:7Eg1uH7CJ5cXv9is6tnBe1FI6rj1nwUdbFypRm3br/M=
go.opentelemetry.io/otel/metric v1.45.0/go.mod h1:HAPbm1nd3p1PmFH7v2dR+6BjXxw+Lq4a2+pndMAm08s=
go.opentelemetry.io/otel/sdk v1.45.0 h1:4VVSMgQ83dUgW2aoX5f6JgLvHwIvzcuLnF9lUdCSpCw=
go.opentelemetry.io/otel/sdk v1.45.0/go.mod h1:Sr40LgXV7DsKMMJMKOhUWOgMWTfAaqvm2kF0g7ilwuA=
go.opentelemetry.io/otel/sdk/metric v1.45.0 h1:oVFszMfyj1Am6s24Vtc7wBb8BKLcwepJjNEYILuiE3o=
go.opentelemetry.io/otel/sdk/metric v1.45.0/go.mod h1:vUWUxDZvu1WVRj8JA8S0AdhsPrZoDpA2DdZauIh4mDA=
go.opentelemetry.io/otel/trace v1.45.0 h1:l/mP6Uv7oNO7/TblbhpbgMidxhq1uO/rPsikOyVhxag=
go.opentelemetry.io/otel/trace v1.45.0/go.mod h1:qoJJA2xNMnxRrdISU/kLtfUH2wNeQbiv+jhs/CxI8bc=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
limits_asymmetry_test.go
// Package limitasymmetry proves that the OpenTelemetry Go SDK applies its two
// per-span capacity limits with opposite eviction strategies: the attribute
// store retains the FIRST attributes added (new-arrivals dropped), while the
// event queue retains the LAST events added (oldest evicted).  A single
// developer writing parallel instrumentation for both data types and assuming
// consistent behaviour will lose data in exactly the wrong direction.
package limitasymmetry_test

import (
	"context"
	"testing"

	"go.opentelemetry.io/otel/attribute"
	sdktrace "go.opentelemetry.io/otel/sdk/trace"
)

// captureProcessor collects the finished span for inspection.
type captureProcessor struct {
	spans []sdktrace.ReadOnlySpan
}

func (c *captureProcessor) OnStart(_ context.Context, _ sdktrace.ReadWriteSpan) {}
func (c *captureProcessor) OnEnd(s sdktrace.ReadOnlySpan)                       { c.spans = append(c.spans, s) }
func (c *captureProcessor) Shutdown(_ context.Context) error                    { return nil }
func (c *captureProcessor) ForceFlush(_ context.Context) error                  { return nil }

func TestAttributesAndEventsDropOppositeEnds(t *testing.T) {
	const limit = 3

	cp := &captureProcessor{}
	tp := sdktrace.NewTracerProvider(
		sdktrace.WithSpanProcessor(cp),
		sdktrace.WithSpanLimits(sdktrace.SpanLimits{
			AttributeCountLimit: limit,
			EventCountLimit:     limit,
		}),
	)
	tr := tp.Tracer("test")

	_, span := tr.Start(context.Background(), "op")

	// Add 5 attributes: a0 … a4.  Limit is 3; expect a0, a1, a2 to survive
	// (the FIRST three), and a3, a4 to be dropped.
	for i := 0; i < 5; i++ {
		span.SetAttributes(attribute.Int("a"+string(rune('0'+i)), i))
	}

	// Add 5 events: e0 … e4.  Limit is 3; expect e2, e3, e4 to survive
	// (the LAST three), and e0, e1 to be evicted.
	for i := 0; i < 5; i++ {
		span.AddEvent("e" + string(rune('0'+i)))
	}

	span.End()

	if len(cp.spans) != 1 {
		t.Fatalf("expected 1 finished span, got %d", len(cp.spans))
	}
	finished := cp.spans[0]

	// ── Attribute assertions ──────────────────────────────────────────────────

	// CONTRACT LINE 1 (the believed-contradicting assertion):
	// When AttributeCountLimit is 3 and five attributes are added in order
	// a0…a4, span.Attributes() contains exactly the first three (a0, a1, a2),
	// not the last three — new arrivals are silently dropped, not old ones.
	attrs := finished.Attributes()
	if len(attrs) != limit {
		t.Fatalf("attribute count: want %d, got %d", limit, len(attrs))
	}
	for i := 0; i < limit; i++ {
		want := attribute.Int("a"+string(rune('0'+i)), i)
		if attrs[i] != want {
			t.Errorf("attrs[%d]: want %v, got %v", i, want, attrs[i])
		}
	}

	// CONTRACT LINE 2:
	// DroppedAttributes() reports exactly 2 (the two attributes that were
	// rejected when the store was already at capacity).
	if got := finished.DroppedAttributes(); got != 2 {
		t.Errorf("DroppedAttributes: want 2, got %d", got)
	}

	// ── Event assertions ──────────────────────────────────────────────────────

	// CONTRACT LINE 3:
	// When EventCountLimit is 3 and five events are added in order e0…e4,
	// span.Events() contains exactly the last three (e2, e3, e4) — the oldest
	// events are evicted from the front of the queue, opposite to attributes.
	events := finished.Events()
	if len(events) != limit {
		t.Fatalf("event count: want %d, got %d", limit, len(events))
	}
	for i := 0; i < limit; i++ {
		want := "e" + string(rune('0'+i+2)) // e2, e3, e4
		if events[i].Name != want {
			t.Errorf("events[%d].Name: want %q, got %q", i, want, events[i].Name)
		}
	}

	// CONTRACT LINE 4:
	// DroppedEvents() reports exactly 2 (the two events evicted from the front
	// of the queue).
	if got := finished.DroppedEvents(); got != 2 {
		t.Errorf("DroppedEvents: want 2, got %d", got)
	}
}

オリジンシーダー

csx-seed