Skip to content

Commit aedce04

Browse files
markushibitsandfoxesCopilot
authored
feat: Add support for w3c trace parent headers for outgoing requests (#4661)
* feat: Add support for w3c trace parent headers for outgoing requests * Add changelog * Verify & bindable options * Add more tests * Update src/Sentry/SentryOptions.cs --------- Co-authored-by: bitsandfoxes <[email protected]> Co-authored-by: Copilot <[email protected]>
1 parent a267d32 commit aedce04

17 files changed

+361
-0
lines changed

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,17 @@
1212
- Removed `SentrySdk.CaptureUserFeedback` and all associated members. Use the newer `SentrySdk.CaptureFeedback` instead.
1313
- Backpressure handling is now enabled by default, meaning that the SDK will monitor system health and reduce the sampling rate of events and transactions when the system is under load. When the system is determined to be healthy again, the sampling rates are returned to their original levels. ([#4615](https://github.com/getsentry/sentry-dotnet/pull/4615))
1414
- ScopeExtensions.Populate is now internal ([#4611](https://github.com/getsentry/sentry-dotnet/pull/4611))
15+
- Add support for W3C traceparent header for outgoing requests ([#4661](https://github.com/getsentry/sentry-dotnet/pull/4661))
16+
This feature is disabled by default. When enabled, outgoing requests will include the W3C traceparent header.
17+
```csharp
18+
SentrySdk.Init(options =>
19+
{
20+
// ...
21+
options.PropagateTraceparent = true;
22+
});
23+
```
24+
25+
See https://develop.sentry.dev/sdk/telemetry/traces/distributed-tracing/#w3c-trace-context-header for more details.
1526

1627
### Fixes
1728

src/Sentry/BindableSentryOptions.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ internal partial class BindableSentryOptions
4242
public bool? EnableTracing { get; set; }
4343
public double? TracesSampleRate { get; set; }
4444
public List<string>? TracePropagationTargets { get; set; }
45+
public bool? PropagateTraceparent { get; set; }
4546
public double? ProfilesSampleRate { get; set; }
4647
public StackTraceMode? StackTraceMode { get; set; }
4748
public long? MaxAttachmentSize { get; set; }
@@ -98,6 +99,7 @@ public void ApplyTo(SentryOptions options)
9899
options.TracesSampleRate = TracesSampleRate ?? options.TracesSampleRate;
99100
options.ProfilesSampleRate = ProfilesSampleRate ?? options.ProfilesSampleRate;
100101
options.TracePropagationTargets = TracePropagationTargets?.Select(s => new StringOrRegex(s)).ToList() ?? options.TracePropagationTargets;
102+
options.PropagateTraceparent = PropagateTraceparent ?? options.PropagateTraceparent;
101103
options.StackTraceMode = StackTraceMode ?? options.StackTraceMode;
102104
options.MaxAttachmentSize = MaxAttachmentSize ?? options.MaxAttachmentSize;
103105
options.DetectStartupTime = DetectStartupTime ?? options.DetectStartupTime;

src/Sentry/Extensibility/DisabledHub.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,11 @@ public void BindException(Exception exception, ISpan span)
9999
/// </summary>
100100
public BaggageHeader? GetBaggage() => null;
101101

102+
/// <summary>
103+
/// Returns null.
104+
/// </summary>
105+
public W3CTraceparentHeader? GetTraceparentHeader() => null;
106+
102107
/// <summary>
103108
/// Returns sampled out transaction context.
104109
/// </summary>

src/Sentry/Extensibility/HubAdapter.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,13 @@ public void BindException(Exception exception, ISpan span) =>
140140
public BaggageHeader? GetBaggage()
141141
=> SentrySdk.GetBaggage();
142142

143+
/// <summary>
144+
/// Forwards the call to <see cref="SentrySdk"/>.
145+
/// </summary>
146+
[DebuggerStepThrough]
147+
public W3CTraceparentHeader? GetTraceparentHeader()
148+
=> SentrySdk.GetTraceparentHeader();
149+
143150
/// <summary>
144151
/// Forwards the call to <see cref="SentrySdk"/>.
145152
/// </summary>

src/Sentry/IHub.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,11 @@ public ITransactionTracer StartTransaction(
5959
/// </summary>
6060
public BaggageHeader? GetBaggage();
6161

62+
/// <summary>
63+
/// Gets the W3C Trace Context traceparent header that allows tracing across services
64+
/// </summary>
65+
public W3CTraceparentHeader? GetTraceparentHeader();
66+
6267
/// <summary>
6368
/// Continues a trace based on HTTP header values provided as strings.
6469
/// </summary>

src/Sentry/Internal/Hub.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,18 @@ public BaggageHeader GetBaggage()
314314
return propagationContext.GetOrCreateDynamicSamplingContext(_options, _replaySession).ToBaggageHeader();
315315
}
316316

317+
public W3CTraceparentHeader? GetTraceparentHeader()
318+
{
319+
if (GetSpan()?.GetTraceHeader() is { } traceHeader)
320+
{
321+
return new W3CTraceparentHeader(traceHeader.TraceId, traceHeader.SpanId, traceHeader.IsSampled);
322+
}
323+
324+
// We fall back to the propagation context
325+
var propagationContext = CurrentScope.PropagationContext;
326+
return new W3CTraceparentHeader(propagationContext.TraceId, propagationContext.SpanId, null);
327+
}
328+
317329
public TransactionContext ContinueTrace(
318330
string? traceHeader,
319331
string? baggageHeader,

src/Sentry/SentryMessageHandler.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,10 @@ private void PropagateTraceHeaders(HttpRequestMessage request, string url, ISpan
137137
{
138138
AddSentryTraceHeader(request, parentSpan);
139139
AddBaggageHeader(request);
140+
if (_options?.PropagateTraceparent is true)
141+
{
142+
AddTraceparentHeader(request, parentSpan);
143+
}
140144
}
141145
}
142146

@@ -181,4 +185,16 @@ private void AddBaggageHeader(HttpRequestMessage request)
181185
// Set the baggage header
182186
request.Headers.Add(BaggageHeader.HttpHeaderName, baggage.ToString());
183187
}
188+
189+
private void AddTraceparentHeader(HttpRequestMessage request, ISpan? parentSpan)
190+
{
191+
// Set W3C traceparent header if it hasn't already been set
192+
if (!request.Headers.Contains(W3CTraceparentHeader.HttpHeaderName) &&
193+
// Use the span created by this integration as parent, instead of its own parent
194+
(parentSpan?.GetTraceHeader() ?? _hub.GetTraceHeader()) is { } traceHeader)
195+
{
196+
var traceparentHeader = new W3CTraceparentHeader(traceHeader.TraceId, traceHeader.SpanId, traceHeader.IsSampled);
197+
request.Headers.Add(W3CTraceparentHeader.HttpHeaderName, traceparentHeader.ToString());
198+
}
199+
}
184200
}

src/Sentry/SentryOptions.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -995,6 +995,18 @@ public IList<StringOrRegex> TracePropagationTargets
995995
set => _tracePropagationTargets = value.WithConfigBinding();
996996
}
997997

998+
/// <summary>
999+
/// Whether to send W3C Trace Context traceparent headers in outgoing HTTP requests for distributed tracing.
1000+
/// When enabled, the SDK will send the <c>traceparent</c> header in addition to the <c>sentry-trace</c> header
1001+
/// for requests matching <see cref="TracePropagationTargets"/>.
1002+
/// </summary>
1003+
/// <remarks>
1004+
/// The default value is <c>false</c>. Set to <c>true</c> to enable W3C Trace Context propagation
1005+
/// for interoperability with services that support OpenTelemetry standards.
1006+
/// </remarks>
1007+
/// <seealso href="https://develop.sentry.dev/sdk/telemetry/traces/#propagatetraceparent"/>
1008+
public bool PropagateTraceparent { get; set; }
1009+
9981010
internal ITransactionProfilerFactory? TransactionProfilerFactory { get; set; }
9991011

10001012
private StackTraceMode? _stackTraceMode;

src/Sentry/SentrySdk.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -716,6 +716,13 @@ public static void BindException(Exception exception, ISpan span)
716716
public static BaggageHeader? GetBaggage()
717717
=> CurrentHub.GetBaggage();
718718

719+
/// <summary>
720+
/// Gets the W3C Trace Context traceparent header that allows tracing across services
721+
/// </summary>
722+
[DebuggerStepThrough]
723+
public static W3CTraceparentHeader? GetTraceparentHeader()
724+
=> CurrentHub.GetTraceparentHeader();
725+
719726
/// <summary>
720727
/// Continues a trace based on HTTP header values provided as strings.
721728
/// </summary>

src/Sentry/W3CTraceparentHeader.cs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
namespace Sentry;
2+
3+
/// <summary>
4+
/// W3C Trace Context traceparent header.
5+
/// </summary>
6+
public class W3CTraceparentHeader
7+
{
8+
internal const string HttpHeaderName = "traceparent";
9+
10+
/// <summary>
11+
/// Trace ID.
12+
/// </summary>
13+
public SentryId TraceId { get; }
14+
15+
/// <summary>
16+
/// Span ID.
17+
/// </summary>
18+
public SpanId SpanId { get; }
19+
20+
/// <summary>
21+
/// Whether the trace is sampled.
22+
/// </summary>
23+
public bool? IsSampled { get; }
24+
25+
/// <summary>
26+
/// Initializes an instance of <see cref="W3CTraceparentHeader"/>.
27+
/// </summary>
28+
public W3CTraceparentHeader(SentryId traceId, SpanId spanId, bool? isSampled)
29+
{
30+
TraceId = traceId;
31+
SpanId = spanId;
32+
IsSampled = isSampled;
33+
}
34+
35+
/// <inheritdoc />
36+
public override string ToString() => IsSampled is { } isSampled
37+
? $"00-{TraceId}-{SpanId}-{(isSampled ? "01" : "00")}"
38+
: $"00-{TraceId}-{SpanId}-00";
39+
}

0 commit comments

Comments
 (0)