-
-
Notifications
You must be signed in to change notification settings - Fork 203
/
HttpContextExtensions.cs
147 lines (119 loc) · 5.25 KB
/
HttpContextExtensions.cs
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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
using Sentry.Extensibility;
namespace Sentry.AspNet;
/// <summary>
/// Sentry extensions for <see cref="HttpContext"/>.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public static class HttpContextExtensions
{
private const string HttpContextTransactionItemName = "__SentryTransaction";
private static SentryTraceHeader? TryGetSentryTraceHeader(HttpContext context, SentryOptions? options)
{
var value = context.Request.Headers.Get(SentryTraceHeader.HttpHeaderName);
if (string.IsNullOrWhiteSpace(value))
{
return null;
}
options?.LogDebug("Received Sentry trace header '{0}'.", value);
try
{
return SentryTraceHeader.Parse(value);
}
catch (Exception ex)
{
options?.LogError(ex, "Invalid Sentry trace header '{0}'.", value);
return null;
}
}
private static BaggageHeader? TryGetBaggageHeader(HttpContext context, SentryOptions? options)
{
var value = context.Request.Headers.Get(BaggageHeader.HttpHeaderName);
if (string.IsNullOrWhiteSpace(value))
{
return null;
}
// Note: If there are multiple baggage headers, they will be joined with comma delimiters,
// and can thus be treated as a single baggage header.
options?.LogDebug("Received baggage header '{0}'.", value);
try
{
return BaggageHeader.TryParse(value, onlySentry: true);
}
catch (Exception ex)
{
options?.LogError(ex, "Invalid baggage header '{0}'.", value);
return null;
}
}
/// <summary>
/// Starts or continues a Sentry trace.
/// </summary>
public static void StartOrContinueTrace(this HttpContext httpContext)
{
var options = SentrySdk.CurrentOptions;
var traceHeader = TryGetSentryTraceHeader(httpContext, options);
var baggageHeader = TryGetBaggageHeader(httpContext, options);
var method = httpContext.Request.HttpMethod;
var path = httpContext.Request.Path;
var transactionName = $"{method} {path}";
const string operation = "http.server";
SentrySdk.ContinueTrace(traceHeader, baggageHeader, transactionName, operation);
}
/// <summary>
/// Starts a new Sentry transaction that encompasses the currently executing HTTP request.
/// </summary>
public static ITransaction StartSentryTransaction(this HttpContext httpContext)
{
var method = httpContext.Request.HttpMethod;
var path = httpContext.Request.Path;
var options = SentrySdk.CurrentOptions;
var traceHeader = TryGetSentryTraceHeader(httpContext, options);
var baggageHeader = TryGetBaggageHeader(httpContext, options);
var transactionName = $"{method} {path}";
const string transactionOperation = "http.server";
var transactionContext = SentrySdk.ContinueTrace(traceHeader, baggageHeader, transactionName, transactionOperation);
transactionContext.NameSource = TransactionNameSource.Url;
var customSamplingContext = new Dictionary<string, object?>(3, StringComparer.Ordinal)
{
["__HttpMethod"] = method,
["__HttpPath"] = path,
["__HttpContext"] = httpContext,
};
// Set the Dynamic Sampling Context from the baggage header, if it exists.
var dynamicSamplingContext = baggageHeader?.CreateDynamicSamplingContext();
if (traceHeader is not null && baggageHeader is null)
{
// We received a sentry-trace header without a baggage header, which indicates the request
// originated from an older SDK that doesn't support dynamic sampling.
// Set DynamicSamplingContext.Empty to "freeze" the DSC on the transaction.
// See:
// https://develop.sentry.dev/sdk/performance/dynamic-sampling-context/#freezing-dynamic-sampling-context
// https://develop.sentry.dev/sdk/performance/dynamic-sampling-context/#unified-propagation-mechanism
dynamicSamplingContext = DynamicSamplingContext.Empty;
}
var transaction = SentrySdk.StartTransaction(transactionContext, customSamplingContext, dynamicSamplingContext);
SentrySdk.ConfigureScope(scope => scope.Transaction = transaction);
httpContext.Items[HttpContextTransactionItemName] = transaction;
if (options?.SendDefaultPii is true)
{
transaction.Request.Cookies = string.Join("; ", httpContext.Request.Cookies.AllKeys.Select(x => $"{x}={httpContext.Request.Cookies[x]?.Value}"));
}
return transaction;
}
/// <summary>
/// Finishes an active Sentry transaction that encompasses the currently executing HTTP request (if present).
/// </summary>
public static void FinishSentryTransaction(this HttpContext httpContext)
{
if (!httpContext.Items.Contains(HttpContextTransactionItemName))
{
return;
}
if (httpContext.Items[HttpContextTransactionItemName] is not ISpan transaction)
{
return;
}
var status = SpanStatusConverter.FromHttpStatusCode(httpContext.Response.StatusCode);
transaction.Finish(status);
}
}