diff --git a/sdk/span.go b/sdk/span.go index fc143d0ad..707fc0fe6 100644 --- a/sdk/span.go +++ b/sdk/span.go @@ -367,10 +367,22 @@ func (s *span) AddLink(link trace.Link) { return } + l := maxSpan.Links + s.mu.Lock() defer s.mu.Unlock() - // TODO: handle link limits. + if l == 0 { + s.span.DroppedLinks++ + return + } + + if l > 0 && len(s.span.Links) == l { + // Drop head while avoiding allocation of more capacity. + copy(s.span.Links[:l-1], s.span.Links[1:]) + s.span.Links = s.span.Links[:l-1] + s.span.DroppedLinks++ + } s.span.Links = append(s.span.Links, convLink(link)) } @@ -384,13 +396,20 @@ func convLinks(links []trace.Link) []*telemetry.SpanLink { } func convLink(link trace.Link) *telemetry.SpanLink { - return &telemetry.SpanLink{ + l := &telemetry.SpanLink{ TraceID: telemetry.TraceID(link.SpanContext.TraceID()), SpanID: telemetry.SpanID(link.SpanContext.SpanID()), TraceState: link.SpanContext.TraceState().String(), - Attrs: convAttrs(link.Attributes), Flags: uint32(link.SpanContext.TraceFlags()), } + + limit := maxSpan.LinkAttrs + + var dropped int + l.Attrs, dropped = convCappedAttrs(limit, link.Attributes) + l.DroppedAttrs += uint32(dropped) + + return l } func (s *span) SetName(name string) { diff --git a/sdk/span_test.go b/sdk/span_test.go index 7090e3fd9..7098a876e 100644 --- a/sdk/span_test.go +++ b/sdk/span_test.go @@ -345,13 +345,41 @@ func TestSpanNilUnsampledGuards(t *testing.T) { } func TestSpanAddLink(t *testing.T) { + t.Setenv("OTEL_LINK_ATTRIBUTE_COUNT_LIMIT", "1") + t.Setenv("OTEL_SPAN_LINK_COUNT_LIMIT", "3") + t.Cleanup(resetMaxSpan()) + s := spanBuilder{ Options: []trace.SpanStartOption{trace.WithLinks(link0)}, }.Build() s.AddLink(link1) want := []*telemetry.SpanLink{tLink0, tLink1} - assert.Equal(t, want, s.span.Links) + if !assert.Equal(t, want, s.span.Links) { + // Reset for next part of test. + want = s.span.Links + } + + s.AddLink(trace.Link{ + Attributes: []attribute.KeyValue{ + attribute.Int("one", 1), + attribute.Int("two", 2), + }, + }) + + want = append(want, &telemetry.SpanLink{ + Attrs: []telemetry.Attr{telemetry.Int("one", 1)}, + DroppedAttrs: 1, + }) + if !assert.Equal(t, want, s.span.Links, "attribute limit") { + // Reset for next part of test. + want = s.span.Links + } + + s.AddLink(trace.Link{}) + want = append(want[1:], new(telemetry.SpanLink)) + assert.Equal(t, want, s.span.Links, "link limit") + assert.Equal(t, uint32(1), s.span.DroppedLinks) } func TestSpanIsRecording(t *testing.T) {