Last active
November 30, 2023 21:04
-
-
Save mattiasnordqvist/8956ee550b5c0e4ba8923542d782578b to your computer and use it in GitHub Desktop.
PersistedQueryNotFoundIsNotAnError
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Program.cs | |
app.UseMiddleware<PersistedQueryNotFoundIsNotAnError.ResponseBodyLoggingMiddleware>(); | |
... | |
builder.RegisterPersistedQueryNotFoundLogHandling(); | |
using Microsoft.ApplicationInsights.DataContracts; | |
using Microsoft.ApplicationInsights.Extensibility; | |
using Microsoft.ApplicationInsights.Channel; | |
/// <summary> | |
/// Services and middleware to make sure that PersistedQueryNotFound are not logged as failed requests in application insights. | |
/// They clog the logs and are not really errors, but expected due to usage of autopersisted queries. | |
/// This was not a problem until a recent update of graphql, which now returns a 400 instead of a 200 in the case of a missing persisted query. | |
/// | |
/// By the way, we tried prepersisting queries in backend at build time instead, but failed because apollo and graphql/codegen didn't generate the same sha256 hash for the same queries. We couldn't figure out why. :( | |
/// | |
/// Even though I set request.Success = true at multiple occasions, application insights in VS still logs the request as failed. | |
/// This is a problem with VS, in actual application insights,it works fine | |
/// </summary> | |
public static class PersistedQueryNotFoundIsNotAnError | |
{ | |
public static void RegisterPersistedQueryNotFoundLogHandling(this WebApplicationBuilder builder) | |
{ | |
builder.Services.AddSingleton<ITelemetryInitializer, PersistedQueryNotFoundCountsAsSuccess>(); | |
builder.Services.AddTransient<ResponseBodyLoggingMiddleware>(); | |
} | |
private const string _requestTelemetryPropertyKey = "PersistedQueryNotFound"; | |
private const string _hotChocolateErrorMessage = "{\"errors\":[{\"message\":\"PersistedQueryNotFound\",\"extensions\":{\"code\":\"HC0020\"}}]}"; | |
public class ResponseBodyLoggingMiddleware : IMiddleware | |
{ | |
private readonly char[] _buffer; | |
private readonly char[] _cache; | |
public ResponseBodyLoggingMiddleware() | |
{ | |
_buffer = new char[_hotChocolateErrorMessage.Length]; | |
_cache = _hotChocolateErrorMessage.ToArray(); | |
} | |
public async Task InvokeAsync(HttpContext context, RequestDelegate next) | |
{ | |
if (context.Request.Method == "POST" && (context.Request.Path.Value == "/graphql/")) | |
{ | |
var originalBodyStream = context.Response.Body; | |
try | |
{ | |
// Swap out stream with one that is buffered and suports seeking | |
using var memoryStream = new MemoryStream(); | |
context.Response.Body = memoryStream; | |
// hand over to the next middleware and wait for the call to return | |
await next(context); | |
// Read response body from memory stream | |
memoryStream.Position = 0; | |
var reader = new StreamReader(memoryStream); | |
var charactersRead = await reader.ReadBlockAsync(_buffer, 0, _buffer.Length); | |
// Copy body back to so its available to the user agent | |
memoryStream.Position = 0; | |
await memoryStream.CopyToAsync(originalBodyStream); | |
// Write response body to App Insights | |
var requestTelemetry = context.Features.Get<RequestTelemetry>(); | |
if (requestTelemetry != null | |
&& charactersRead == _hotChocolateErrorMessage.Length | |
&& context.Response.StatusCode == 400) | |
{ | |
if (_buffer.SequenceEqual(_cache)) | |
{ | |
requestTelemetry!.Properties[_requestTelemetryPropertyKey] = null; | |
} | |
} | |
} | |
finally | |
{ | |
context.Response.Body = originalBodyStream; | |
} | |
} | |
else | |
{ | |
await next(context); | |
} | |
} | |
} | |
public class PersistedQueryNotFoundCountsAsSuccess : ITelemetryInitializer | |
{ | |
public void Initialize(ITelemetry telemetry) | |
{ | |
if (telemetry is RequestTelemetry request | |
&& request.Properties.ContainsKey(_requestTelemetryPropertyKey)) | |
{ | |
request.Success = true; | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment