-
-
Notifications
You must be signed in to change notification settings - Fork 231
Expand file tree
/
Copy pathSentryGraphQLHttpMessageHandler.cs
More file actions
128 lines (115 loc) · 5.25 KB
/
SentryGraphQLHttpMessageHandler.cs
File metadata and controls
128 lines (115 loc) · 5.25 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
using Sentry.Extensibility;
using Sentry.Internal;
using Sentry.Internal.OpenTelemetry;
namespace Sentry;
/// <summary>
/// Special HTTP message handler that can be used to propagate Sentry headers and other contextual information.
/// </summary>
public class SentryGraphQLHttpMessageHandler : SentryMessageHandler
{
private readonly IHub _hub;
private readonly SentryOptions? _options;
private readonly ISentryFailedRequestHandler? _failedRequestHandler;
internal const string GraphQlOrigin = "auto.graphql";
/// <summary>
/// Constructs an instance of <see cref="SentryGraphQLHttpMessageHandler"/>.
/// </summary>
/// <param name="innerHandler">An inner message handler to delegate calls to.</param>
/// <param name="hub">The Sentry hub.</param>
public SentryGraphQLHttpMessageHandler(HttpMessageHandler? innerHandler = default, IHub? hub = default)
: this(hub, default, innerHandler)
{
}
internal SentryGraphQLHttpMessageHandler(IHub? hub, SentryOptions? options,
HttpMessageHandler? innerHandler = default,
ISentryFailedRequestHandler? failedRequestHandler = null)
: base(hub, options, innerHandler)
{
_hub = hub ?? HubAdapter.Instance;
_options = options ?? _hub.GetSentryOptions();
_failedRequestHandler = failedRequestHandler;
if (_options != null)
{
_failedRequestHandler ??= new SentryGraphQLHttpFailedRequestHandler(_hub, _options);
}
}
/// <inheritdoc />
protected internal override ISpan? ProcessRequest(HttpRequestMessage request, string method, string url)
{
var content = GraphQLContentExtractor.ExtractRequestContentAsync(request, _options).Result;
if (content is not { } graphQlRequestContent)
{
_options?.LogDebug("Unable to process non GraphQL request content");
return null;
}
request.SetFused(graphQlRequestContent);
if (_options?.Instrumenter == Instrumenter.OpenTelemetry)
{
_options.LogDebug("Skipping span creation in SentryGraphQLHttpMessageHandler because Instrumenter is set to OpenTelemetry");
return null;
}
// Start a span that tracks this request
// (may be null if transaction is not set on the scope)
var span = _hub.GetSpan()?.StartChild(
"http.client",
$"{method} {url}" // e.g. "GET https://example.com"
);
span?.SetOrigin(GraphQlOrigin);
span?.SetExtra(OtelSemanticConventions.AttributeHttpRequestMethod, method);
if (!string.IsNullOrWhiteSpace(request.RequestUri?.Host))
{
span?.SetExtra(OtelSemanticConventions.AttributeServerAddress, request.RequestUri!.Host);
}
return span;
}
/// <inheritdoc />
protected internal override void HandleResponse(HttpResponseMessage response, ISpan? span, string method, string url)
{
var graphqlInfo = response.RequestMessage?.GetFused<GraphQLRequestContent>();
var breadcrumbData = new Dictionary<string, string>
{
{"url", url},
{"method", method},
{"status_code", ((int) response.StatusCode).ToString()}
};
AddIfExists(breadcrumbData, "request_body_size", response.RequestMessage?.Content?.Headers.ContentLength?.ToString());
#if NET5_0_OR_GREATER
// Starting with .NET 5, the content and headers are guaranteed to not be null.
AddIfExists(breadcrumbData, "response_body_size", response.Content.Headers.ContentLength?.ToString());
#else
AddIfExists(breadcrumbData, "response_body_size", response.Content?.Headers.ContentLength?.ToString());
#endif
AddIfExists(breadcrumbData, "operation_name", graphqlInfo?.OperationName); // The GraphQL operation name
AddIfExists(breadcrumbData, "operation_type", graphqlInfo?.OperationType); // i.e. `query`, `mutation`, `subscription`
_hub.AddBreadcrumb(
string.Empty,
graphqlInfo?.OperationType ?? "graphql.operation",
"graphql",
breadcrumbData
);
// Create events for failed requests
_failedRequestHandler?.HandleResponse(response);
// This will handle unsuccessful status codes as well
if (span is not null)
{
span.SetExtra(OtelSemanticConventions.AttributeHttpResponseStatusCode, (int)response.StatusCode);
span.Description = GetSpanDescriptionOrDefault(graphqlInfo, response.StatusCode) ?? span.Description;
// TODO: See how we can determine the span status for a GraphQL request...
var status = SpanStatusConverter.FromHttpStatusCode(response.StatusCode); // TODO: Don't do this if the span is errored
span.Finish(status);
}
}
private string? GetSpanDescriptionOrDefault(GraphQLRequestContent? graphqlInfo, HttpStatusCode statusCode) =>
string.Join(" ",
graphqlInfo?.OperationNameOrFallback(),
graphqlInfo?.OperationTypeOrFallback(),
((int)statusCode).ToString()
);
private void AddIfExists(Dictionary<string, string> breadcrumbData, string key, string? value)
{
if (!string.IsNullOrEmpty(value))
{
breadcrumbData[key] = value;
}
}
}