サンプル
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
コントラクト
- 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).
ファイル
- NOTES.md
- csx.json
- go.mod
- go.sum
- limits_asymmetry_test.go
ソース
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.
{"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"}
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
)
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=
// 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)
}
}