Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
OpenTelemetry.Trace.TelemetrySpan.AddLink(OpenTelemetry.Trace.SpanContext spanContext) -> OpenTelemetry.Trace.TelemetrySpan!
OpenTelemetry.Trace.TelemetrySpan.AddLink(OpenTelemetry.Trace.SpanContext spanContext, OpenTelemetry.Trace.SpanAttributes? attributes) -> OpenTelemetry.Trace.TelemetrySpan!
4 changes: 4 additions & 0 deletions src/OpenTelemetry.Api/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ Notes](../../RELEASENOTES.md).

## Unreleased

* Added `AddLink(SpanContext, SpanAttributes?)` to `TelemetrySpan` to support
linking spans and associating optional attributes for advanced trace relationships.
([#6305](https://github.com/open-telemetry/opentelemetry-dotnet/pull/6305))

## 1.12.0

Released 2025-Apr-29
Expand Down
33 changes: 33 additions & 0 deletions src/OpenTelemetry.Api/Trace/TelemetrySpan.cs
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,31 @@ public TelemetrySpan AddEvent(string name, DateTimeOffset timestamp, SpanAttribu
return this;
}

/// <summary>
/// Adds a link to another span.
/// </summary>
/// <param name="spanContext">Span context to be linked.</param>
/// <returns>The <see cref="TelemetrySpan"/> instance for chaining.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public TelemetrySpan AddLink(SpanContext spanContext)
{
this.AddLinkInternal(spanContext.ActivityContext);
return this;
}

/// <summary>
/// Adds a link to another span.
/// </summary>
/// <param name="spanContext">Span context to be linked.</param>
/// <param name="attributes">Attributes for the link.</param>
/// <returns>The <see cref="TelemetrySpan"/> instance for chaining.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public TelemetrySpan AddLink(SpanContext spanContext, SpanAttributes? attributes)
{
this.AddLinkInternal(spanContext.ActivityContext, attributes?.Attributes);
return this;
}

/// <summary>
/// End the span.
/// </summary>
Expand Down Expand Up @@ -342,4 +367,12 @@ private void AddEventInternal(string name, DateTimeOffset timestamp = default, A
this.Activity!.AddEvent(new ActivityEvent(name, timestamp, tags));
}
}

private void AddLinkInternal(ActivityContext context, ActivityTagsCollection? tags = null)
{
if (this.IsRecording)
{
this.Activity!.AddLink(new ActivityLink(context, tags));
}
}
}
61 changes: 61 additions & 0 deletions test/OpenTelemetry.Api.Tests/Trace/TelemetrySpanTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,65 @@ public void ParentIds()

Assert.Equal(parentSpan.Context.SpanId, childSpan.ParentSpanId);
}

[Fact]
public void CheckAddLinkData()
{
using var activity = new Activity("test-activity");
activity.Start();
using var span = new TelemetrySpan(activity);

var traceId = ActivityTraceId.CreateRandom();
var spanId = ActivitySpanId.CreateRandom();
var context = new SpanContext(traceId, spanId, ActivityTraceFlags.Recorded);

span.AddLink(context);

Assert.Single(activity.Links);
var link = activity.Links.First();
Assert.Equal(traceId, link.Context.TraceId);
Assert.Equal(spanId, link.Context.SpanId);
Assert.Null(link.Tags);
}

[Fact]
public void CheckAddLinkAttributes()
{
using var activity = new Activity("test-activity");
activity.Start();
using var span = new TelemetrySpan(activity);

var traceId = ActivityTraceId.CreateRandom();
var spanId = ActivitySpanId.CreateRandom();
var context = new SpanContext(traceId, spanId, ActivityTraceFlags.Recorded);

var attributes = new SpanAttributes();
attributes.Add("key1", "value1");

span.AddLink(context, attributes);

Assert.Single(activity.Links);
var link = activity.Links.First();
Assert.NotNull(link.Tags);
Assert.Single(link.Tags);
var tag = link.Tags.First();
Assert.Equal("key1", tag.Key);
Assert.Equal("value1", tag.Value);
}

[Fact]
public void CheckAddLinkNotRecording()
{
using var activity = new Activity("test-activity");

// Simulate not recording
activity.IsAllDataRequested = false;
using var span = new TelemetrySpan(activity);

var context = new SpanContext(ActivityTraceId.CreateRandom(), ActivitySpanId.CreateRandom(), ActivityTraceFlags.None);

span.AddLink(context, null);

Assert.Empty(activity.Links);
}
}