diff --git a/CHANGELOG.md b/CHANGELOG.md index 14a4f446ac1..59d54dcca22 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Added +- Add `AddLink` method to the `Span` interface in `go.opentelemetry.io/otel/trace`. (#5032) - Add `WithProxy` option in `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp`. (#4906) - Add `WithProxy` option in `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlptracehttp`. (#4906) - The `Enabled` method is added to the `Logger` interface in `go.opentelemetry.io/otel/log`. diff --git a/bridge/opentracing/internal/mock.go b/bridge/opentracing/internal/mock.go index ba92206f419..434f6a7bc81 100644 --- a/bridge/opentracing/internal/mock.go +++ b/bridge/opentracing/internal/mock.go @@ -176,6 +176,11 @@ type MockEvent struct { Attributes []attribute.KeyValue } +type MockLink struct { + SpanContext trace.SpanContext + Attributes []attribute.KeyValue +} + type MockSpan struct { embedded.Span @@ -190,6 +195,7 @@ type MockSpan struct { EndTime time.Time ParentSpanID trace.SpanID Events []MockEvent + Links []MockLink } var ( @@ -286,6 +292,13 @@ func (s *MockSpan) AddEvent(name string, o ...trace.EventOption) { }) } +func (s *MockSpan) AddLink(link trace.Link) { + s.Links = append(s.Links, MockLink{ + SpanContext: link.SpanContext, + Attributes: link.Attributes, + }) +} + func (s *MockSpan) OverrideTracer(tracer trace.Tracer) { s.officialTracer = tracer } diff --git a/internal/global/trace.go b/internal/global/trace.go index 2e765c6d07e..596f716f40c 100644 --- a/internal/global/trace.go +++ b/internal/global/trace.go @@ -182,6 +182,9 @@ func (nonRecordingSpan) RecordError(error, ...trace.EventOption) {} // AddEvent does nothing. func (nonRecordingSpan) AddEvent(string, ...trace.EventOption) {} +// AddLink does nothing. +func (nonRecordingSpan) AddLink(trace.Link) {} + // SetName does nothing. func (nonRecordingSpan) SetName(string) {} diff --git a/sdk/trace/span.go b/sdk/trace/span.go index 7a1ff3a2ea5..c44f6b926aa 100644 --- a/sdk/trace/span.go +++ b/sdk/trace/span.go @@ -629,7 +629,7 @@ func (s *recordingSpan) Resource() *resource.Resource { return s.tracer.provider.resource } -func (s *recordingSpan) addLink(link trace.Link) { +func (s *recordingSpan) AddLink(link trace.Link) { if !s.IsRecording() || !link.SpanContext.IsValid() { return } @@ -803,6 +803,9 @@ func (nonRecordingSpan) RecordError(error, ...trace.EventOption) {} // AddEvent does nothing. func (nonRecordingSpan) AddEvent(string, ...trace.EventOption) {} +// AddLink does nothing. +func (nonRecordingSpan) AddLink(trace.Link) {} + // SetName does nothing. func (nonRecordingSpan) SetName(string) {} diff --git a/sdk/trace/trace_test.go b/sdk/trace/trace_test.go index 0918dadcd2a..615f4d58a1b 100644 --- a/sdk/trace/trace_test.go +++ b/sdk/trace/trace_test.go @@ -1976,3 +1976,81 @@ func TestEmptyRecordingSpanAttributes(t *testing.T) { func TestEmptyRecordingSpanDroppedAttributes(t *testing.T) { assert.Equal(t, 0, (&recordingSpan{}).DroppedAttributes()) } + +func TestAddLinkWithInvalidSpanContext(t *testing.T) { + te := NewTestExporter() + sl := NewSpanLimits() + tp := NewTracerProvider( + WithSpanLimits(sl), + WithSyncer(te), + WithResource(resource.Empty()), + ) + span := startSpan(tp, "AddSpanWithInvalidSpanContext") + inValidContext := trace.NewSpanContext(trace.SpanContextConfig{ + TraceID: trace.TraceID([16]byte{}), + SpanID: [8]byte{}, + }) + attrs := []attribute.KeyValue{{Key: "k", Value: attribute.StringValue("v")}} + span.AddLink(trace.Link{ + SpanContext: inValidContext, + Attributes: attrs, + }) + + want := &snapshot{ + name: "span0", + spanContext: trace.NewSpanContext(trace.SpanContextConfig{ + TraceID: tid, + TraceFlags: 0x1, + }), + parent: sc.WithRemote(true), + links: nil, + spanKind: trace.SpanKindInternal, + instrumentationScope: instrumentation.Scope{Name: "AddSpanWithInvalidSpanContext"}, + } + got, err := endSpan(te, span) + if err != nil { + t.Fatal(err) + } + if diff := cmpDiff(got, want); diff != "" { + t.Errorf("AddLinkWithInvalidSpanContext: -got +want %s", diff) + } +} + +func TestAddLink(t *testing.T) { + te := NewTestExporter() + sl := NewSpanLimits() + tp := NewTracerProvider( + WithSpanLimits(sl), + WithSyncer(te), + WithResource(resource.Empty()), + ) + attrs := []attribute.KeyValue{{Key: "k", Value: attribute.StringValue("v")}} + span := startSpan(tp, "AddSpan") + + link := trace.Link{SpanContext: sc, Attributes: attrs} + span.AddLink(link) + + want := &snapshot{ + name: "span0", + spanContext: trace.NewSpanContext(trace.SpanContextConfig{ + TraceID: tid, + TraceFlags: 0x1, + }), + parent: sc.WithRemote(true), + links: []Link{ + { + SpanContext: sc, + Attributes: attrs, + }, + }, + spanKind: trace.SpanKindInternal, + instrumentationScope: instrumentation.Scope{Name: "AddSpan"}, + } + got, err := endSpan(te, span) + if err != nil { + t.Fatal(err) + } + if diff := cmpDiff(got, want); diff != "" { + t.Errorf("AddLink: -got +want %s", diff) + } +} diff --git a/sdk/trace/tracer.go b/sdk/trace/tracer.go index 3e9d7452d4c..3668b1387d0 100644 --- a/sdk/trace/tracer.go +++ b/sdk/trace/tracer.go @@ -138,7 +138,7 @@ func (tr *tracer) newRecordingSpan(psc, sc trace.SpanContext, name string, sr Sa } for _, l := range config.Links() { - s.addLink(l) + s.AddLink(l) } s.SetAttributes(sr.Attributes...) diff --git a/trace/noop.go b/trace/noop.go index 84c775492ba..ca20e9997ab 100644 --- a/trace/noop.go +++ b/trace/noop.go @@ -75,6 +75,9 @@ func (noopSpan) RecordError(error, ...EventOption) {} // AddEvent does nothing. func (noopSpan) AddEvent(string, ...EventOption) {} +// AddLink does nothing. +func (noopSpan) AddLink(Link) {} + // SetName does nothing. func (noopSpan) SetName(string) {} diff --git a/trace/noop/noop.go b/trace/noop/noop.go index a358993c1ee..1dfa52c5216 100644 --- a/trace/noop/noop.go +++ b/trace/noop/noop.go @@ -100,6 +100,9 @@ func (Span) RecordError(error, ...trace.EventOption) {} // AddEvent does nothing. func (Span) AddEvent(string, ...trace.EventOption) {} +// AddLink does nothing. +func (Span) AddLink(trace.Link) {} + // SetName does nothing. func (Span) SetName(string) {} diff --git a/trace/trace.go b/trace/trace.go index 019da4d7861..28877d4ab4d 100644 --- a/trace/trace.go +++ b/trace/trace.go @@ -350,6 +350,12 @@ type Span interface { // AddEvent adds an event with the provided name and options. AddEvent(name string, options ...EventOption) + // AddLink adds a link. + // Adding links at span creation using WithLinks is preferred to calling AddLink + // later, for contexts that are available during span creation, because head + // sampling decisions can only consider information present during span creation. + AddLink(link Link) + // IsRecording returns the recording state of the Span. It will return // true if the Span is active and events can be recorded. IsRecording() bool