Log your Optimizely Graph queries

Log Graph calls from .NET in in a way where it's easy to copy the query and parameters to GraphiQL.

Using a DelegatingHandler is the recommended way of intercepting and logging HttpClient requests.

Add one that reads the outgoing request body into a JsonNode and extracts the query and variables.

using System;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Threading.Tasks;
using EPiServer.Logging;

public class GraphClientLoggerDelegatingHandler : DelegatingHandler
{
    private static readonly ILogger log = LogManager.GetLogger();

    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        // ReSharper disable once InvertIf
        if (log.IsInformationEnabled() && request.Content != null)
        {
            JsonNode? jsonNode = null;

            try
            {
                jsonNode = await JsonNode.ParseAsync(
                    await request.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false),
                    cancellationToken: cancellationToken)!.ConfigureAwait(false);
            }
            catch (JsonException e)
            {
                log.Error("Error parsing body", e);
            }

            if (jsonNode is null)
            {
                log.Error("jsonNode is null");
            }
            else
            {
                var options = new JsonSerializerOptions { WriteIndented = true };
                log.Information(jsonNode["query"]?.ToJsonString(options)?.Trim('"'));
                log.Information(jsonNode["variables"]?.ToJsonString(options));
            }
        }

        return await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
    }
}

Register it in the dependency injection container.

services.AddTransient<GraphClientLoggerDelegatingHandler>();

For use with Strawberry Shake.

var queryOptions = configuration
    .GetSection(Optimizely.Graph.Core.Configuration.GraphOptions.ConfigurationSectionName)
    .Get<Optimizely.Graph.Core.Configuration.GraphOptions>() ?? throw new InvalidOperationException("Optimizely:ContentGraph configuration is missing.");

var contentGraphUrl = new Uri(queryOptions.GatewayAddress, "content/v2?auth=" + queryOptions.SingleKey);
services
    .AddCustomGraphClient()
    .ConfigureHttpClient(
        client => client.BaseAddress = contentGraphUrl,
        configureClientBuilder => configureClientBuilder.AddHttpMessageHandler<GraphClientLoggerDelegatingHandler>());

For the Graph C# SDK package.

// Attach to the existing named HttpClient
services.Configure<HttpClientFactoryOptions>(
    "Optimizely.Graph.Core.Client.Authenticated",
    options =>
    {
        options.HttpMessageHandlerBuilderActions.Add(builder =>
        {
            var handler = builder.Services.GetRequiredService<GraphClientLoggerDelegatingHandler>();
            builder.AdditionalHandlers.Add(handler);
        });
    });

Now you will get the query logged, when pasting to GraphiQL you can use the format option there to prettify it.

The variables used are already logged in a readable and copy/paste-friendly format.

Comments?

Published and tagged with these categories: Optimizely