-
-
Notifications
You must be signed in to change notification settings - Fork 239
Expand file tree
/
Copy pathW3CTraceHeader.cs
More file actions
131 lines (113 loc) · 5.21 KB
/
Copy pathW3CTraceHeader.cs
File metadata and controls
131 lines (113 loc) · 5.21 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
namespace Sentry;
/// <summary>
/// Extension methods for working with Sentry trace headers.
/// </summary>
internal class W3CTraceHeader
{
private const string SupportedVersion = "00";
/// <summary>
/// The name of the W3C trace context header used for distributed tracing.
/// This field contains the value "traceparent" which is part of the W3C Trace Context specification.
/// </summary>
public const string HttpHeaderName = "traceparent";
/// <summary>
/// Represents the sampled trace flags value ("01") in W3C Trace Context specification.
/// This flag indicates that the trace is part of the sampling set and should be recorded.
/// </summary>
public const string TraceFlagsSampled = "01";
/// <summary>
/// Represents the unsampled trace flags value ("00") in W3C Trace Context specification.
/// This flag indicates that the trace is not part of the sampling set and should not be recorded.
/// </summary>
public const string TraceFlagsNotSampled = "00";
/// <summary>
/// Initializes a new instance of the <see cref="W3CTraceHeader"/> class from a Sentry trace header.
/// </summary>
/// <param name="source">The source Sentry trace header to create the W3C trace header from.</param>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="source"/> is null.</exception>
public W3CTraceHeader(SentryTraceHeader source)
{
if (source is null)
{
throw new ArgumentNullException(nameof(source), "Source Sentry trace header cannot be null.");
}
SentryTraceHeader = source;
}
/// <summary>
/// Gets the Sentry trace header containing trace identification and sampling information.
/// </summary>
/// <value>
/// The Sentry trace header that contains the trace ID, span ID, and sampling decision.
/// </value>
public SentryTraceHeader SentryTraceHeader { get; }
/// <summary>
/// Parses a <see cref="SentryTraceHeader"/> from a string representation of the Sentry trace header.
/// </summary>
/// <param name="value">
/// A string containing the Sentry trace header, expected to follow the format "traceId-spanId-sampled",
/// where "sampled" is optional.
/// </param>
/// <returns>
/// A <see cref="SentryTraceHeader"/> object if parsing succeeds, or <c>null</c> if the input string is null, empty, or whitespace.
/// </returns>
/// <exception cref="FormatException">
/// Thrown if the input string does not contain a valid trace header format, specifically if it lacks required trace ID and span ID components.
/// </exception>
public static W3CTraceHeader? Parse(string value)
{
if (string.IsNullOrWhiteSpace(value))
{
return null;
}
var components = value.Split('-', StringSplitOptions.RemoveEmptyEntries);
if (components.Length < 4)
{
throw new FormatException($"Invalid W3C trace header: {value}.");
}
var version = components[0];
if (version != SupportedVersion)
{
throw new FormatException($"Invalid W3C trace header version: {version}.");
}
var traceId = SentryId.Parse(components[1]);
var spanId = SpanId.Parse(components[2]);
var isSampled = ConvertTraceFlagsToSampled(components[3]);
return new W3CTraceHeader(new SentryTraceHeader(traceId, spanId, isSampled));
}
/// <inheritdoc/>
public override string ToString()
{
var traceFlags = ConvertSampledToTraceFlags(SentryTraceHeader.IsSampled);
return $"{SupportedVersion}-{SentryTraceHeader.TraceId}-{SentryTraceHeader.SpanId}-{traceFlags}";
}
private static string? ConvertSampledToTraceFlags(bool? isSampled) => (isSampled ?? false) ? TraceFlagsSampled : TraceFlagsNotSampled;
private static bool? ConvertTraceFlagsToSampled(string? traceFlags)
{
if (string.IsNullOrWhiteSpace(traceFlags) || traceFlags.Length != 2)
{
return null;
}
// In version 00 of the W3C Trace Context specification, the trace flags field is 2 hex digits.
// Only the first bit is used. We use string comparison first to avoid parsing the hex value in
// the bulk of all cases.
// See https://github.com/getsentry/sentry-dotnet/pull/4084#discussion_r2035771628
if (string.Equals(traceFlags, TraceFlagsSampled, StringComparison.Ordinal))
{
return true;
}
else if (string.Equals(traceFlags, TraceFlagsNotSampled, StringComparison.Ordinal))
{
return false;
}
// If the trace flags field is not "01" or "00", we try to parse it as a hex number.
// This is a fallback for cases where the trace flags field is not in the expected format.
if (!byte.TryParse(traceFlags, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out byte traceFlagsBytes))
{
// If it's not a valid hex number, we can't parse it.
return null;
}
// The first bit of the trace flags field indicates whether the trace is sampled.
// We use bitwise AND to check if the first bit is set.
return (traceFlagsBytes & 0x01) == 1;
}
}