Author avatar

Benney Au

Configure Azure Application Insights SDK for .NET for GraphQL.NET Servers

Benney Au

  • Oct 19, 2020
  • 7 Min read
  • 229 Views
  • Oct 19, 2020
  • 7 Min read
  • 229 Views
Data
Microsoft Azure
Data Analytics
Cloud Platforms

Introduction

GraphQL is becoming an increasingly important technology. Many large companies such as GitHub, Coursea, and of course Facebook (GraphQL's creator) have adopted it. However, since GraphQL is designed to be transport agnostic, it does not rely on HTTP status codes to communicate errors to the client. This means that you will need to update your monitoring and logging code to cater to GraphQL. In this guide, you will learn how to configure a GraphQL.NET Server with Azure Application Insights to correctly report error codes and failed requests.

Application Insights Data Model

Application Insights offers a programming language agnostic data model for storing and sending telemetry data from your applications. Understanding the basics of the data model is important to properly communicate failures to the Application Insights UI in the Azure Portal.

For a GraphQL Server, there are three important types of telemetry:

  • Request Telemetry: An incoming request to your GraphQL Server. This could be a GraphQL query, mutation, or subscription.
  • Exception Telemetry: Errors that occur that may cause a request to fail
  • Dependency Telemetry: Your server may rely on third-party systems such as other REST APIs or databases. Dependency telemetry can record information like whether these dependencies succeeded or failed and how long they took to execute.

The telemetry described above can be grouped into an operation. For a GraphQL Server, this operation will begin as a client makes a mutation, query, or subscription request. Therefore, the root of operation will be a request telemetry that may contain nested telemetry or even operations inside it. This grouping makes it easy to find related logs in a request and understand failures, such as timeouts.

Logging GraphQL Operations with Request Telemetry

If you are using GraphQL.NET inside an ASP.NET Core Servers, the default middleware packaged in Microsoft.ApplicationInsights.AspetNetCore won't be sufficient to correctly report failed GraphQL operations. Further, this middleware is endpoint-based. This means all your requests will be grouped under /graphql, which makes it more difficult to understand the usage patterns of your GraphQL server.

One simple way to fix this is to subclass GraphQL.Server.Internal.DefaultGraphQLExecuter<TSchema> and map the GraphQL query and ExecutionResult to the Application Insights data model. The following code snippet demonstrates how to do this.

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
public class GraphQLExecutorWithDiagnostics<TSchema> : DefaultGraphQLExecuter<TSchema>
        where TSchema : GraphQL.Types.ISchema
    {
        private readonly TelemetryClient _telemetryClient;

        public GraphQLExecutorWithDiagnostics(
            TSchema schema,
            IDocumentExecuter documentExecuter,
            IOptions<GraphQLOptions> options,
            IEnumerable<IDocumentExecutionListener> listeners,
            IEnumerable<IValidationRule> validationRules,
            TelemetryClient telemetryClient)
            : base(schema, documentExecuter, options, listeners, validationRules)
        {
            _telemetryClient = telemetryClient;
        }

        public override async Task<ExecutionResult> ExecuteAsync(
            string operationName,
            string query,
            Inputs variables,
            IDictionary<string, object> context,
            CancellationToken cancellationToken = default)
        {
            using var operationHolder = _telemetryClient.StartOperation<RequestTelemetry>("GRAPHQL " + operationName);
            var telemetry = operationHolder.Telemetry;
            telemetry.Context.Operation.Name = operationHolder.Telemetry.Name;
            telemetry.Properties["Type"] = "GRAPHQL";

            var result = await base.ExecuteAsync(operationName, query, variables, context, cancellationToken);

            if (result.Errors?.Any() ?? false)
            {
                telemetry.Success = false;
                telemetry.ResponseCode = result.Errors.First().Code ?? "Faulted";
            }

            if (result.Extensions?.ContainsKey("tracing") == true)
            {
                foreach (var perf in result.Perf)
                {
                    if (perf.Subject is string perfMetricSubject)
                    {
                        operationHolder.Telemetry.Metrics[perfMetricSubject] = perf.Duration;
                    }
                }
            }

            return result;
        }
    }
csharp

You have created the GraphQLExecutorWithDiagnostics<TSchema> class to create a request telemetry. This class has the following responsibilities:

  • Update the request telemetry to success = false if an error is found in the execution result.
  • Log the operation name that is sent from the client. This is useful for differentiating types of GraphQL queries. In the Application Insights UI in the Azure Portal, you will be able to filter, group, and sort by operation name.
  • Set the response code on the request telemetry if a GraphQL operation fails. Normally this is the HTTP status code for the server. However, in the case of GraphQL, you need to manually set it to the GraphQL error.
  • The operationHolder automatically creates a stopwatch to measure how long your request took to execute.

Adding Custom Dimension to the Request Telemetry

So far, you have logged some basic details about a GraphQL operation, such as whether it has succeeded and any error state. You can extend this by adding custom dimensions and logging the entire GraphQL query text and error message details.

To do this, update the GraphQLExecutor you created earlier by adding:

1
2
3
4
5
6
if (result.Errors?.Any() ?? false)
{
    telemetry.Success = false;
    telemetry.ResponseCode = result.Errors.First().Code ?? "Faulted";
    telemetry.Properties["GraphQLQuery"] = result.Query;
    Telemetry.Properties["GraphErrorData"] = JsonSerializer.Serialize(result.Errors.First().Data);
csharp

Note: There is a size limit on custom dimensions, so if it is too large it may be truncated or removed by the Application Insights SDK.

Registering your GraphQLExecutor

Finally, to use your enhanced GraphQLExecutor, add a few lines in your Startup.cs to tell your IoC container how to construct your service.

1
2
3
4
5
6
7
8
9
10
services.AddApplicationInsightsTelemetry();
services.AddGraphQL(_ =>
{
    _.ExposeExceptions = false;
})
.AddSystemTextJson(deserializerSettings => { }, serializerSettings => { })
.AddDataLoader();

services.RemoveAll(typeof(IGraphQLExecuter<>));
services.AddTransient(typeof(IGraphQLExecuter<>), typeof(GraphQLExecutorWithDiagnostics<>));
csharp

You have replaced the DefaultGraphQLExecuter with your own GraphQLExecutorWithDiagnostics.

Conclusion

Application Insights provides a rich set of capabilities for monitoring, diagnostics, and logging. In order to benefit from these features, you need to make sure your application sends useful telemetry. By understanding the basics of the Application Insights data model, you can get better observability into your application.

3