Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(graphql): Removed Application Insights SDK, added OpenTelemetry #1349

Merged
merged 3 commits into from
Nov 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

This file was deleted.

112 changes: 112 additions & 0 deletions src/Digdir.Domain.Dialogporten.GraphQL/OpenTelemetryEventListener.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
using System.Diagnostics;
using HotChocolate.Execution;
using HotChocolate.Execution.Instrumentation;
using Microsoft.AspNetCore.Http.Extensions;
using OpenTelemetry.Trace;

namespace Digdir.Domain.Dialogporten.GraphQL;

public sealed class OpenTelemetryEventListener : ExecutionDiagnosticEventListener
{
private static readonly ActivitySource ActivitySource = new("Dialogporten.GraphQL");

public override IDisposable ExecuteRequest(IRequestContext context)
{
var httpContext = GetHttpContextFrom(context);
if (httpContext == null)
return EmptyScope;

#if DEBUG
if (context.Request.OperationName == "IntrospectionQuery")
return EmptyScope;
#endif

var operationName = context.Request.OperationName ?? "UnknownOperation";
var operationPath = $"{operationName} - {context.Request.QueryHash}";

var activity = ActivitySource.StartActivity($"GraphQL {operationPath}", ActivityKind.Server);

if (activity == null)
return EmptyScope;

activity.SetTag("graphql.operation_name", operationName);
activity.SetTag("graphql.query_hash", context.Request.QueryHash);
activity.SetTag("http.url", httpContext.Request.GetDisplayUrl());
activity.SetTag("user.id", httpContext.User.Identity?.Name ?? "Not authenticated");
activity.SetTag("http.method", httpContext.Request.Method);
activity.SetTag("http.route", httpContext.Request.Path);

return new ScopeWithEndAction(() => OnEndRequest(context, activity));
}

public override void RequestError(IRequestContext context, Exception exception)
{
var currentActivity = Activity.Current;
if (currentActivity != null)
{
currentActivity.RecordException(exception);
currentActivity.SetStatus(ActivityStatusCode.Error, exception.Message);
}
base.RequestError(context, exception);
}

public override void ValidationErrors(IRequestContext context, IReadOnlyList<IError> errors)
{
foreach (var error in errors)
{
var currentActivity = Activity.Current;
currentActivity?.AddEvent(new ActivityEvent("ValidationError", default, new ActivityTagsCollection
{
{ "message", error.Message }
}));
}

base.ValidationErrors(context, errors);
}

private static HttpContext? GetHttpContextFrom(IRequestContext context) =>
context.ContextData.TryGetValue("HttpContext", out var value) ? value as HttpContext : null;

private static void OnEndRequest(IRequestContext context, Activity activity)
{
var httpContext = GetHttpContextFrom(context);
if (context.Exception != null)
{
activity.RecordException(context.Exception);
activity.SetStatus(ActivityStatusCode.Error, context.Exception.Message);
}

if (context.Result is QueryResult { Errors: not null } queryResult)
{
foreach (var error in queryResult.Errors)
{
if (error.Exception is null)
{
continue;
}

activity.RecordException(error.Exception);
activity.SetStatus(ActivityStatusCode.Error, error.Exception.Message);
}
}

if (httpContext != null)
{
activity.SetTag("http.status_code", httpContext.Response.StatusCode);
}

activity.Dispose();
knuhau marked this conversation as resolved.
Show resolved Hide resolved
}
}

internal sealed class ScopeWithEndAction : IDisposable
{
private readonly Action _disposeAction;

public ScopeWithEndAction(Action disposeAction)
{
_disposeAction = disposeAction;
}

public void Dispose() => _disposeAction.Invoke();
}
18 changes: 13 additions & 5 deletions src/Digdir.Domain.Dialogporten.GraphQL/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,20 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.Options;

const string DialogportenGraphQLSource = "Dialogporten.GraphQL";

var telemetryConfiguration = TelemetryConfiguration.CreateDefault();
// Using two-stage initialization to catch startup errors.
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Warning()
.Enrich.FromLogContext()
.WriteTo.Console(formatProvider: CultureInfo.InvariantCulture)
.WriteTo.ApplicationInsights(TelemetryConfiguration.CreateDefault(), TelemetryConverter.Traces)
.WriteTo.ApplicationInsights(telemetryConfiguration, TelemetryConverter.Traces)
.CreateBootstrapLogger();

try
{
BuildAndRun(args);
BuildAndRun(args, telemetryConfiguration);
}
catch (Exception ex) when (ex is not OperationCanceledException)
{
Expand All @@ -39,7 +42,7 @@
Log.CloseAndFlush();
}

static void BuildAndRun(string[] args)
static void BuildAndRun(string[] args, TelemetryConfiguration telemetryConfiguration)
{
var builder = WebApplication.CreateBuilder(args);

Expand All @@ -49,7 +52,7 @@ static void BuildAndRun(string[] args)
.ReadFrom.Services(services)
.Enrich.FromLogContext()
.WriteTo.Console(formatProvider: CultureInfo.InvariantCulture)
.WriteTo.ApplicationInsights(services.GetRequiredService<TelemetryConfiguration>(), TelemetryConverter.Traces));
.WriteTo.ApplicationInsights(telemetryConfiguration, TelemetryConverter.Traces));

builder.Configuration
.AddAzureConfiguration(builder.Environment.EnvironmentName)
Expand All @@ -64,6 +67,11 @@ static void BuildAndRun(string[] args)
var thisAssembly = Assembly.GetExecutingAssembly();

builder.ConfigureTelemetry();
builder.Services.AddOpenTelemetry()
.WithTracing(tracerProviderBuilder =>
{
tracerProviderBuilder.AddSource(DialogportenGraphQLSource);
});
knuhau marked this conversation as resolved.
Show resolved Hide resolved

builder.Services
// Options setup
Expand All @@ -75,7 +83,7 @@ static void BuildAndRun(string[] args)
.WithPubCapabilities()
.Build()
.AddAutoMapper(Assembly.GetExecutingAssembly())
.AddApplicationInsightsTelemetry()
.AddHttpContextAccessor()
.AddScoped<IUser, ApplicationUser>()
.AddValidatorsFromAssembly(thisAssembly, ServiceLifetime.Transient, includeInternalTypes: true)
.AddAzureAppConfiguration()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public static IServiceCollection AddDialogportenGraphQl(this IServiceCollection
.AddSubscriptionType<Subscriptions>()
.AddAuthorization()
.RegisterDbContext<DialogDbContext>()
.AddDiagnosticEventListener<ApplicationInsightEventListener>()
.AddDiagnosticEventListener<OpenTelemetryEventListener>()
knuhau marked this conversation as resolved.
Show resolved Hide resolved
.AddQueryType<Queries>()
.AddMutationType<Mutations>()
.AddType<DialogByIdDeleted>()
Expand Down
Loading