Skip to content

Commit cffea5c

Browse files
committed
refactor: Update W3CTraceHeader to improve parsing and error handling
1 parent e36a636 commit cffea5c

File tree

2 files changed

+69
-22
lines changed

2 files changed

+69
-22
lines changed

src/Sentry.AspNet/HttpContextExtensions.cs

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -33,27 +33,27 @@ public static class HttpContextExtensions
3333
}
3434
}
3535

36-
private static SentryTraceHeader? TryGetW3CTraceHeader(HttpContext context, SentryOptions? options)
36+
private static W3CTraceHeader? TryGetW3CTraceHeader(HttpContext context, SentryOptions? options)
37+
{
38+
var value = context.Request.Headers.Get(W3CTraceHeader.HttpHeaderName);
39+
if (string.IsNullOrWhiteSpace(value))
3740
{
38-
var value = context.Request.Headers.Get(SentryTraceHeaderExtensions.W3CTraceContextHeaderName);
39-
if (string.IsNullOrWhiteSpace(value))
40-
{
41-
return null;
42-
}
43-
44-
options?.LogDebug("Received Sentry trace header '{0}'.", value);
45-
46-
try
47-
{
48-
return SentryTraceHeader.Parse(value);
49-
}
50-
catch (Exception ex)
51-
{
52-
options?.LogError(ex, "Invalid Sentry trace header '{0}'.", value);
53-
return null;
54-
}
41+
return null;
5542
}
5643

44+
options?.LogDebug("Received Sentry trace header '{0}'.", value);
45+
46+
try
47+
{
48+
return W3CTraceHeader.Parse(value);
49+
}
50+
catch (Exception ex)
51+
{
52+
options?.LogError(ex, "Invalid Sentry trace header '{0}'.", value);
53+
return null;
54+
}
55+
}
56+
5757
private static BaggageHeader? TryGetBaggageHeader(HttpContext context, SentryOptions? options)
5858
{
5959
var value = context.Request.Headers.Get(BaggageHeader.HttpHeaderName);
@@ -86,6 +86,7 @@ public static void StartOrContinueTrace(this HttpContext httpContext)
8686
var options = SentrySdk.CurrentOptions;
8787

8888
var traceHeader = TryGetSentryTraceHeader(httpContext, options);
89+
traceHeader ??= TryGetW3CTraceHeader(httpContext, options)?.SentryTraceHeader;
8990
var baggageHeader = TryGetBaggageHeader(httpContext, options);
9091

9192
var method = httpContext.Request.HttpMethod;

src/Sentry/W3CTraceHeader.cs

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ namespace Sentry;
55
/// </summary>
66
internal class W3CTraceHeader
77
{
8+
private const string SupportedVersion = "00";
9+
810
/// <summary>
911
/// The name of the W3C trace context header used for distributed tracing.
1012
/// This field contains the value "traceparent" which is part of the W3C Trace Context specification.
@@ -18,7 +20,10 @@ internal class W3CTraceHeader
1820
/// <exception cref="ArgumentNullException">Thrown when <paramref name="source"/> is null.</exception>
1921
public W3CTraceHeader(SentryTraceHeader source)
2022
{
21-
ArgumentNullException.ThrowIfNull(source);
23+
if (source is null)
24+
{
25+
throw new ArgumentNullException(nameof(source), "Source Sentry trace header cannot be null.");
26+
}
2227

2328
SentryTraceHeader = source;
2429
}
@@ -31,17 +36,58 @@ public W3CTraceHeader(SentryTraceHeader source)
3136
/// </value>
3237
public SentryTraceHeader SentryTraceHeader { get; }
3338

39+
/// <summary>
40+
/// Parses a <see cref="SentryTraceHeader"/> from a string representation of the Sentry trace header.
41+
/// </summary>
42+
/// <param name="value">
43+
/// A string containing the Sentry trace header, expected to follow the format "traceId-spanId-sampled",
44+
/// where "sampled" is optional.
45+
/// </param>
46+
/// <returns>
47+
/// A <see cref="SentryTraceHeader"/> object if parsing succeeds, or <c>null</c> if the input string is null, empty, or whitespace.
48+
/// </returns>
49+
/// <exception cref="FormatException">
50+
/// Thrown if the input string does not contain a valid trace header format, specifically if it lacks required trace ID and span ID components.
51+
/// </exception>
52+
public static W3CTraceHeader? Parse(string value)
53+
{
54+
if (string.IsNullOrWhiteSpace(value))
55+
{
56+
return null;
57+
}
58+
59+
var components = value.Split('-', StringSplitOptions.RemoveEmptyEntries);
60+
if (components.Length < 2)
61+
{
62+
throw new FormatException($"Invalid W3C trace header: {value}.");
63+
}
64+
65+
var version = components[0];
66+
if (version != SupportedVersion)
67+
{
68+
throw new FormatException($"Invalid W3C trace header version: {version}.");
69+
}
70+
71+
var traceId = SentryId.Parse(components[1]);
72+
var spanId = SpanId.Parse(components[2]);
73+
74+
var isSampled = components.Length >= 4
75+
? string.Equals(components[3], "01", StringComparison.OrdinalIgnoreCase)
76+
: (bool?)null;
77+
78+
return new W3CTraceHeader(new SentryTraceHeader(traceId, spanId, isSampled));
79+
}
80+
3481
/// <inheritdoc/>
3582
public override string ToString()
3683
{
37-
const string version = "00";
3884
var traceFlags = ConvertSampledToTraceFlags(SentryTraceHeader.IsSampled);
3985
if (traceFlags is null)
4086
{
41-
return $"{version}-{SentryTraceHeader.TraceId}-{SentryTraceHeader.SpanId}";
87+
return $"{SupportedVersion}-{SentryTraceHeader.TraceId}-{SentryTraceHeader.SpanId}";
4288
}
4389

44-
return $"{version}-{SentryTraceHeader.TraceId}-{SentryTraceHeader.SpanId}-{traceFlags}";
90+
return $"{SupportedVersion}-{SentryTraceHeader.TraceId}-{SentryTraceHeader.SpanId}-{traceFlags}";
4591
}
4692

4793
/// <inheritdoc/>

0 commit comments

Comments
 (0)