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

Add Fusion diagnostic events #7027

Merged
merged 3 commits into from
Mar 29, 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
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using HotChocolate.Execution.Configuration;
using HotChocolate.Execution.Pipeline;
using HotChocolate.Fusion.Clients;
using HotChocolate.Fusion.Execution.Diagnostic;
using HotChocolate.Fusion.Execution.Pipeline;
using HotChocolate.Fusion.Metadata;
using HotChocolate.Fusion.Planning;
Expand Down Expand Up @@ -73,12 +74,46 @@
sc.TryAddSingleton(fusionGraphConfig);
sc.TryAddSingleton<QueryPlanner>();
sc.TryAddSingleton<NodeIdParser, DefaultNodeIdParser>();
sc.TryAddFusionDiagnosticEvents();
});
});

return new FusionGatewayBuilder(builder);
}


public static FusionGatewayBuilder AddDiagnosticEventListener<T>(
this FusionGatewayBuilder builder)
where T : class, IFusionDiagnosticEventListener
{
if (builder is null)
{

Check warning on line 89 in src/HotChocolate/Fusion/src/Core/DependencyInjection/FusionRequestExecutorBuilderExtensions.cs

View check run for this annotation

Codecov / codecov/patch

src/HotChocolate/Fusion/src/Core/DependencyInjection/FusionRequestExecutorBuilderExtensions.cs#L88-L89

Added lines #L88 - L89 were not covered by tests
throw new ArgumentNullException(nameof(builder));
}

builder.Services.TryAddSingleton<T>();
builder.CoreBuilder.ConfigureSchemaServices(
s => s.AddSingleton(
sp => (IFusionDiagnosticEventListener)sp.GetApplicationService<T>()));

Check warning on line 97 in src/HotChocolate/Fusion/src/Core/DependencyInjection/FusionRequestExecutorBuilderExtensions.cs

View check run for this annotation

Codecov / codecov/patch

src/HotChocolate/Fusion/src/Core/DependencyInjection/FusionRequestExecutorBuilderExtensions.cs#L91-L97

Added lines #L91 - L97 were not covered by tests
return builder;
}

Check warning on line 100 in src/HotChocolate/Fusion/src/Core/DependencyInjection/FusionRequestExecutorBuilderExtensions.cs

View check run for this annotation

Codecov / codecov/patch

src/HotChocolate/Fusion/src/Core/DependencyInjection/FusionRequestExecutorBuilderExtensions.cs#L99-L100

Added lines #L99 - L100 were not covered by tests
internal static IServiceCollection TryAddFusionDiagnosticEvents(
this IServiceCollection services)
{
services.TryAddSingleton<IFusionDiagnosticEvents>(sp =>
{
var listeners = sp.GetServices<IFusionDiagnosticEventListener>().ToArray();
return listeners.Length switch
{
0 => new NoopFusionDiagnosticEvents(),
1 => listeners[0],
_ => new AggregateFusionDiagnosticEvents(listeners),
};
});
return services;
}

/// <summary>
/// Adds a custom ID parser to the gateway.
/// </summary>
Expand All @@ -95,7 +130,7 @@
sc.RemoveAll<NodeIdParser>();
sc.AddSingleton<NodeIdParser, DefaultNodeIdParser>();
}));

Check warning on line 133 in src/HotChocolate/Fusion/src/Core/DependencyInjection/FusionRequestExecutorBuilderExtensions.cs

View check run for this annotation

Codecov / codecov/patch

src/HotChocolate/Fusion/src/Core/DependencyInjection/FusionRequestExecutorBuilderExtensions.cs#L133

Added line #L133 was not covered by tests
return builder;
}

Expand Down Expand Up @@ -217,7 +252,7 @@
builder.CoreBuilder.AddTypeModule<GatewayConfigurationTypeModule>();
return builder;
}

/// <summary>
/// Rewrites the gateway configuration to use the service discovery for HTTP clients.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
using HotChocolate.Execution;

namespace HotChocolate.Fusion.Execution.Diagnostic;

internal sealed class AggregateFusionDiagnosticEvents(IFusionDiagnosticEventListener[] listeners)
: IFusionDiagnosticEventListener
{
public IDisposable ExecuteFederatedQuery(IRequestContext context)
{
var scopes = new IDisposable[listeners.Length];

for (var i = 0; i < listeners.Length; i++)
{
scopes[i] = listeners[i].ExecuteFederatedQuery(context);
}

return new AggregateActivityScope(scopes);
}

public void QueryPlanExecutionError(Exception exception)
{
for (var i = 0; i < listeners.Length; i++)
{
listeners[i].QueryPlanExecutionError(exception);
}
}

public void ResolveError(Exception exception)
{
for (var i = 0; i < listeners.Length; i++)
{
listeners[i].ResolveError(exception);
}
}

public void ResolveByKeyBatchError(Exception exception)
{
for (var i = 0; i < listeners.Length; i++)
{
listeners[i].ResolveByKeyBatchError(exception);
}
}

private sealed class AggregateActivityScope(IDisposable[] scopes) : IDisposable
{
private bool _disposed;

public void Dispose()
{
if (!_disposed)
{
foreach (var scope in scopes)
{
scope.Dispose();
}

_disposed = true;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using HotChocolate.Execution;

namespace HotChocolate.Fusion.Execution.Diagnostic;

/// <summary>
/// This class can be used as a base class for <see cref="IFusionDiagnosticEventListener"/>
/// implementations, so that they only have to override the methods they
/// are interested in instead of having to provide implementations for all of them.
/// </summary>
public class FusionDiagnosticEventListener : IFusionDiagnosticEventListener
{
/// <summary>
/// A no-op activity scope that can be returned from
/// event methods that are not interested in when the scope is disposed.
/// </summary>
protected static IDisposable EmptyScope { get; } = new EmptyActivityScope();

/// <inheritdoc />
public virtual IDisposable ExecuteFederatedQuery(IRequestContext context)
=> EmptyScope;

/// <inheritdoc />
public virtual void QueryPlanExecutionError(Exception exception)
{
}

/// <inheritdoc />
public virtual void ResolveError(Exception exception)
{
}

/// <inheritdoc />
public virtual void ResolveByKeyBatchError(Exception exception)
{
}

private sealed class EmptyActivityScope : IDisposable
{
public void Dispose()
{
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace HotChocolate.Fusion.Execution.Diagnostic;

/// <seealso cref="FusionDiagnosticEventListener"/>
public interface IFusionDiagnosticEventListener : IFusionDiagnosticEvents
{

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using HotChocolate.Execution;

namespace HotChocolate.Fusion.Execution.Diagnostic;

public interface IFusionDiagnosticEvents
{
/// <summary>
/// Called when starting to execute a federated GraphQL request.
/// </summary>
/// <param name="context">
/// The request context encapsulates all GraphQL-specific information about an
/// individual GraphQL request.
/// </param>
/// <returns>
/// A scope that will be disposed when the execution has finished.
/// </returns>
IDisposable ExecuteFederatedQuery(IRequestContext context);

/// <summary>
/// Called when an exception occurred during the execution of a QueryPlan.
/// </summary>
/// <param name="exception">
/// The unhandled exception that occurred while executing a QueryPlan.
/// </param>
void QueryPlanExecutionError(Exception exception);

/// <summary>
/// Called when an exception occurred during the execution of a Resolve QueryPlan node.
/// </summary>
/// <param name="exception">
/// The exception that occurred while executing a Resolve QueryPlan node.
/// </param>
void ResolveError(Exception exception);

/// <summary>
/// Called when an exception occurred during the execution of a ResolveByKeyBatch QueryPlan node.
/// </summary>
/// <param name="exception">
/// The exception that occurred while executing a ResolveByKeyBatch QueryPlan node.
/// </param>
void ResolveByKeyBatchError(Exception exception);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using HotChocolate.Execution;

namespace HotChocolate.Fusion.Execution.Diagnostic;

internal sealed class NoopFusionDiagnosticEvents : IFusionDiagnosticEvents, IDisposable
{
public IDisposable ExecuteFederatedQuery(IRequestContext context)
=> this;

public void QueryPlanExecutionError(Exception exception)
{
}

public void ResolveError(Exception exception)
{
}

public void ResolveByKeyBatchError(Exception exception)
{
}

public void Dispose()
{
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using System.Runtime.CompilerServices;
using HotChocolate.Execution.Processing;
using HotChocolate.Fusion.Clients;
using HotChocolate.Fusion.Execution.Diagnostic;
using HotChocolate.Fusion.Execution.Nodes;
using HotChocolate.Fusion.Execution.Pipeline;
using HotChocolate.Fusion.Metadata;
using HotChocolate.Fusion.Utilities;
using HotChocolate.Types.Relay;
Expand All @@ -25,19 +27,22 @@ public FusionExecutionContext(
OperationContextOwner operationContextOwner,
GraphQLClientFactory clientFactory,
IIdSerializer idSerializer,
NodeIdParser nodeIdParser)
NodeIdParser nodeIdParser,
IFusionDiagnosticEvents diagnosticEvents)
{
Configuration = configuration ??
throw new ArgumentNullException(nameof(configuration));
QueryPlan = queryPlan ??
throw new ArgumentNullException(nameof(queryPlan));
DiagnosticEvents = diagnosticEvents ??
throw new ArgumentNullException(nameof(diagnosticEvents));
_operationContextOwner = operationContextOwner ??
throw new ArgumentNullException(nameof(operationContextOwner));
_clientFactory = clientFactory ??
throw new ArgumentNullException(nameof(clientFactory));
_idSerializer = idSerializer ??
throw new ArgumentNullException(nameof(idSerializer));
_nodeIdParser = nodeIdParser ??
_nodeIdParser = nodeIdParser ??
throw new ArgumentNullException(nameof(nodeIdParser));
_schemaName = Schema.Name;
}
Expand All @@ -57,6 +62,11 @@ public FusionExecutionContext(
/// </summary>
public QueryPlan QueryPlan { get; }

/// <summary>
/// Gets the diagnostic event reporter.
/// </summary>
public IFusionDiagnosticEvents DiagnosticEvents { get; }

/// <summary>
/// Gets the execution state.
/// </summary>
Expand Down Expand Up @@ -100,7 +110,7 @@ public bool NeedsMoreData(ISelectionSet selectionSet)
var typeName = Configuration.GetTypeName(subgraphName, id.TypeName);
return _idSerializer.Serialize(_schemaName, typeName, id.Value);
}

public string ParseTypeNameFromId(string id)
=> _nodeIdParser.ParseTypeName(id);

Expand Down Expand Up @@ -160,5 +170,6 @@ public static FusionExecutionContext CreateFrom(
operationContextOwner,
context._clientFactory,
context._idSerializer,
context._nodeIdParser);
context._nodeIdParser,
context.DiagnosticEvents);
}
2 changes: 2 additions & 0 deletions src/HotChocolate/Fusion/src/Core/Execution/Nodes/QueryPlan.cs
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,8 @@
}
catch (Exception ex)
{
context.DiagnosticEvents.QueryPlanExecutionError(ex);

Check warning on line 194 in src/HotChocolate/Fusion/src/Core/Execution/Nodes/QueryPlan.cs

View check run for this annotation

Codecov / codecov/patch

src/HotChocolate/Fusion/src/Core/Execution/Nodes/QueryPlan.cs#L193-L194

Added lines #L193 - L194 were not covered by tests
if (context.Result.Errors.Count == 0)
{
var errorHandler = context.OperationContext.ErrorHandler;
Expand Down
3 changes: 2 additions & 1 deletion src/HotChocolate/Fusion/src/Core/Execution/Nodes/Resolve.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
}
catch (Exception ex)
{
context.DiagnosticEvents.ResolveError(ex);

Check warning on line 73 in src/HotChocolate/Fusion/src/Core/Execution/Nodes/Resolve.cs

View check run for this annotation

Codecov / codecov/patch

src/HotChocolate/Fusion/src/Core/Execution/Nodes/Resolve.cs#L73

Added line #L73 was not covered by tests
var error = context.OperationContext.ErrorHandler.CreateUnexpectedError(ex);
context.Result.AddError(error.Build());
}
Expand Down Expand Up @@ -153,4 +154,4 @@
response = ref Unsafe.Add(ref response, 1)!;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@
}
catch (Exception ex)
{
context.DiagnosticEvents.ResolveByKeyBatchError(ex);

Check warning on line 93 in src/HotChocolate/Fusion/src/Core/Execution/Nodes/ResolveByKeyBatch.cs

View check run for this annotation

Codecov / codecov/patch

src/HotChocolate/Fusion/src/Core/Execution/Nodes/ResolveByKeyBatch.cs#L93

Added line #L93 was not covered by tests
var error = context.OperationContext.ErrorHandler.CreateUnexpectedError(ex);
context.Result.AddError(error.Build());
}
Expand Down Expand Up @@ -153,7 +154,7 @@
context.ShowDebugInfo);
first = false;
}

if (result.TryGetValue(batchState.Key, out var data))
{
ExtractSelectionResults(SelectionSet, SubgraphName, data, batchState.SelectionSetData);
Expand Down Expand Up @@ -417,4 +418,4 @@
/// </summary>
public SelectionData[] SelectionSetData { get; } = executionState.SelectionSetData;
}
}
}
Loading
Loading