Skip to content

Commit c70cb29

Browse files
authored
add support for Link. (open-telemetry#80)
* add support for Link. * add AddLink to mockSpan * update api documentation.
1 parent 8af3bfc commit c70cb29

File tree

7 files changed

+203
-0
lines changed

7 files changed

+203
-0
lines changed

api/trace/api.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,13 @@ type Span interface {
7575
// IsRecordingEvents returns true if the span is active and recording events is enabled.
7676
IsRecordingEvents() bool
7777

78+
// AddLink adds a link to the span.
79+
AddLink(link Link)
80+
81+
// Link creates a link between this span and the other span specified by the SpanContext.
82+
// It then adds the newly created Link to the span.
83+
Link(sc core.SpanContext, attrs ...core.KeyValue)
84+
7885
// SpancContext returns span context of the span. Return SpanContext is usable
7986
// even after the span is finished.
8087
SpanContext() core.SpanContext
@@ -129,6 +136,22 @@ const (
129136
FollowsFromRelationship
130137
)
131138

139+
// Link is used to establish relationship between two spans within the same Trace or
140+
// across different Traces. Few examples of Link usage.
141+
// 1. Batch Processing: A batch of elements may contain elements associated with one
142+
// or more traces/spans. Since there can only be one parent SpanContext, Link is
143+
// used to keep reference to SpanContext of all elements in the batch.
144+
// 2. Public Endpoint: A SpanContext in incoming client request on a public endpoint
145+
// is untrusted from service provider perspective. In such case it is advisable to
146+
// start a new trace with appropriate sampling decision.
147+
// However, it is desirable to associate incoming SpanContext to new trace initiated
148+
// on service provider side so two traces (from Client and from Service Provider) can
149+
// be correlated.
150+
type Link struct {
151+
core.SpanContext
152+
Attributes []core.KeyValue
153+
}
154+
132155
// Start starts a new span using registered global tracer.
133156
func Start(ctx context.Context, name string, opts ...SpanOption) (context.Context, Span) {
134157
return GlobalTracer().Start(ctx, name, opts...)

api/trace/current_test.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,3 +107,11 @@ func (mockSpan) Tracer() trace.Tracer {
107107
// Event does nothing.
108108
func (mockSpan) AddEvent(ctx context.Context, msg string, attrs ...core.KeyValue) {
109109
}
110+
111+
// AddLink does nothing.
112+
func (mockSpan) AddLink(link trace.Link) {
113+
}
114+
115+
// Link does nothing.
116+
func (mockSpan) Link(sc core.SpanContext, attrs ...core.KeyValue) {
117+
}

api/trace/noop_span.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,3 +78,11 @@ func (NoopSpan) AddEvent(ctx context.Context, msg string, attrs ...core.KeyValue
7878
// SetName does nothing.
7979
func (NoopSpan) SetName(name string) {
8080
}
81+
82+
// AddLink does nothing.
83+
func (NoopSpan) AddLink(link Link) {
84+
}
85+
86+
// Link does nothing.
87+
func (NoopSpan) Link(sc core.SpanContext, attrs ...core.KeyValue) {
88+
}

experimental/streaming/sdk/span.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,3 +128,9 @@ func (sp *span) SetName(name string) {
128128
String: name,
129129
})
130130
}
131+
132+
func (sp *span) AddLink(link apitrace.Link) {
133+
}
134+
135+
func (sp *span) Link(sc core.SpanContext, attrs ...core.KeyValue) {
136+
}

sdk/trace/export.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"google.golang.org/grpc/codes"
2323

2424
"go.opentelemetry.io/api/core"
25+
apitrace "go.opentelemetry.io/api/trace"
2526
)
2627

2728
// Exporter is a type for functions that receive sampled trace spans.
@@ -87,6 +88,7 @@ type SpanData struct {
8788
// The values of Attributes each have type string, bool, or int64.
8889
Attributes map[string]interface{}
8990
MessageEvents []Event
91+
Links []apitrace.Link
9092
Status codes.Code
9193
HasRemoteParent bool
9294
DroppedAttributeCount int

sdk/trace/span.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,38 @@ func (s *span) SetName(name string) {
195195
makeSamplingDecision(data)
196196
}
197197

198+
// AddLink implements Span interface. Specified link is added to the span.
199+
// If the total number of links associated with the span exceeds the limit
200+
// then the oldest link is removed to create space for the link being added.
201+
func (s *span) AddLink(link apitrace.Link) {
202+
if !s.IsRecordingEvents() {
203+
return
204+
}
205+
s.addLink(link)
206+
}
207+
208+
// Link implements Span interface. It is similar to AddLink but it excepts
209+
// SpanContext and attributes as arguments instead of Link. It first creates
210+
// a Link object and then adds to the span.
211+
func (s *span) Link(sc core.SpanContext, attrs ...core.KeyValue) {
212+
if !s.IsRecordingEvents() {
213+
return
214+
}
215+
attrsCopy := attrs
216+
if attrs != nil {
217+
attrsCopy = make([]core.KeyValue, len(attrs))
218+
copy(attrsCopy, attrs)
219+
}
220+
link := apitrace.Link{SpanContext: sc, Attributes: attrsCopy}
221+
s.addLink(link)
222+
}
223+
224+
func (s *span) addLink(link apitrace.Link) {
225+
s.mu.Lock()
226+
defer s.mu.Unlock()
227+
s.links.add(link)
228+
}
229+
198230
// makeSpanData produces a SpanData representing the current state of the span.
199231
// It requires that s.data is non-nil.
200232
func (s *span) makeSpanData() *SpanData {
@@ -210,9 +242,21 @@ func (s *span) makeSpanData() *SpanData {
210242
sd.MessageEvents = s.interfaceArrayToMessageEventArray()
211243
sd.DroppedMessageEventCount = s.messageEvents.droppedCount
212244
}
245+
if len(s.links.queue) > 0 {
246+
sd.Links = s.interfaceArrayToLinksArray()
247+
sd.DroppedLinkCount = s.links.droppedCount
248+
}
213249
return &sd
214250
}
215251

252+
func (s *span) interfaceArrayToLinksArray() []apitrace.Link {
253+
linkArr := make([]apitrace.Link, 0)
254+
for _, value := range s.links.queue {
255+
linkArr = append(linkArr, value.(apitrace.Link))
256+
}
257+
return linkArr
258+
}
259+
216260
func (s *span) interfaceArrayToMessageEventArray() []Event {
217261
messageEventArr := make([]Event, 0)
218262
for _, value := range s.messageEvents.queue {

sdk/trace/trace_test.go

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,118 @@ func TestEventsOverLimit(t *testing.T) {
316316
}
317317
}
318318

319+
func TestAddLinks(t *testing.T) {
320+
span := startSpan()
321+
k1v1 := key.New("key1").String("value1")
322+
k2v2 := key.New("key2").String("value2")
323+
324+
sc1 := core.SpanContext{TraceID: core.TraceID{High: 0x1, Low: 0x1}, SpanID: 0x3}
325+
sc2 := core.SpanContext{TraceID: core.TraceID{High: 0x1, Low: 0x2}, SpanID: 0x3}
326+
327+
link1 := apitrace.Link{SpanContext: sc1, Attributes: []core.KeyValue{k1v1}}
328+
link2 := apitrace.Link{SpanContext: sc2, Attributes: []core.KeyValue{k2v2}}
329+
span.AddLink(link1)
330+
span.AddLink(link2)
331+
332+
got, err := endSpan(span)
333+
if err != nil {
334+
t.Fatal(err)
335+
}
336+
337+
want := &SpanData{
338+
SpanContext: core.SpanContext{
339+
TraceID: tid,
340+
TraceOptions: 0x1,
341+
},
342+
ParentSpanID: sid,
343+
Name: "span0",
344+
HasRemoteParent: true,
345+
Links: []apitrace.Link{
346+
{SpanContext: sc1, Attributes: []core.KeyValue{k1v1}},
347+
{SpanContext: sc2, Attributes: []core.KeyValue{k2v2}},
348+
},
349+
}
350+
if diff := cmp.Diff(got, want, cmp.AllowUnexported(Event{})); diff != "" {
351+
t.Errorf("AddLink: -got +want %s", diff)
352+
}
353+
}
354+
355+
func TestLinks(t *testing.T) {
356+
span := startSpan()
357+
k1v1 := key.New("key1").String("value1")
358+
k2v2 := key.New("key2").String("value2")
359+
k3v3 := key.New("key3").String("value3")
360+
361+
sc1 := core.SpanContext{TraceID: core.TraceID{High: 0x1, Low: 0x1}, SpanID: 0x3}
362+
sc2 := core.SpanContext{TraceID: core.TraceID{High: 0x1, Low: 0x2}, SpanID: 0x3}
363+
364+
span.Link(sc1, key.New("key1").String("value1"))
365+
span.Link(sc2,
366+
key.New("key2").String("value2"),
367+
key.New("key3").String("value3"),
368+
)
369+
got, err := endSpan(span)
370+
if err != nil {
371+
t.Fatal(err)
372+
}
373+
374+
want := &SpanData{
375+
SpanContext: core.SpanContext{
376+
TraceID: tid,
377+
TraceOptions: 0x1,
378+
},
379+
ParentSpanID: sid,
380+
Name: "span0",
381+
HasRemoteParent: true,
382+
Links: []apitrace.Link{
383+
{SpanContext: sc1, Attributes: []core.KeyValue{k1v1}},
384+
{SpanContext: sc2, Attributes: []core.KeyValue{k2v2, k3v3}},
385+
},
386+
}
387+
if diff := cmp.Diff(got, want, cmp.AllowUnexported(Event{})); diff != "" {
388+
t.Errorf("Link: -got +want %s", diff)
389+
}
390+
}
391+
392+
func TestLinksOverLimit(t *testing.T) {
393+
cfg := Config{MaxLinksPerSpan: 2}
394+
ApplyConfig(cfg)
395+
sc1 := core.SpanContext{TraceID: core.TraceID{High: 0x1, Low: 0x1}, SpanID: 0x3}
396+
sc2 := core.SpanContext{TraceID: core.TraceID{High: 0x1, Low: 0x2}, SpanID: 0x3}
397+
sc3 := core.SpanContext{TraceID: core.TraceID{High: 0x1, Low: 0x3}, SpanID: 0x3}
398+
399+
span := startSpan()
400+
k2v2 := key.New("key2").String("value2")
401+
k3v3 := key.New("key3").String("value3")
402+
403+
span.Link(sc1, key.New("key1").String("value1"))
404+
span.Link(sc2, key.New("key2").String("value2"))
405+
span.Link(sc3, key.New("key3").String("value3"))
406+
407+
got, err := endSpan(span)
408+
if err != nil {
409+
t.Fatal(err)
410+
}
411+
412+
want := &SpanData{
413+
SpanContext: core.SpanContext{
414+
TraceID: tid,
415+
TraceOptions: 0x1,
416+
},
417+
ParentSpanID: sid,
418+
Name: "span0",
419+
Links: []apitrace.Link{
420+
{SpanContext: sc2, Attributes: []core.KeyValue{k2v2}},
421+
{SpanContext: sc3, Attributes: []core.KeyValue{k3v3}},
422+
},
423+
DroppedLinkCount: 1,
424+
HasRemoteParent: true,
425+
}
426+
if diff := cmp.Diff(got, want, cmp.AllowUnexported(Event{})); diff != "" {
427+
t.Errorf("Link over limit: -got +want %s", diff)
428+
}
429+
}
430+
319431
func TestSetSpanName(t *testing.T) {
320432
want := "SpanName-1"
321433
_, span := apitrace.GlobalTracer().Start(context.Background(), want,

0 commit comments

Comments
 (0)