diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Instrumentation/AggregateServerDiagnosticEventListener.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Instrumentation/AggregateServerDiagnosticEventListener.cs index 3fd66e60177..2e0a6110b91 100644 --- a/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Instrumentation/AggregateServerDiagnosticEventListener.cs +++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Instrumentation/AggregateServerDiagnosticEventListener.cs @@ -1,4 +1,6 @@ using Microsoft.AspNetCore.Http; +using HotChocolate.AspNetCore.Subscriptions; +using HotChocolate.AspNetCore.Subscriptions.Protocols; using HotChocolate.Language; namespace HotChocolate.AspNetCore.Instrumentation; @@ -111,6 +113,16 @@ public IDisposable WebSocketSession(HttpContext context) return new AggregateActivityScope(scopes); } + public void WebSocketConnectionInitialized( + ISocketSession session, + IOperationMessagePayload connectionInitMessage) + { + for (var i = 0; i < _listeners.Length; i++) + { + _listeners[i].WebSocketConnectionInitialized(session, connectionInitMessage); + } + } + public void WebSocketSessionError(HttpContext context, Exception exception) { for (var i = 0; i < _listeners.Length; i++) diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Instrumentation/IServerDiagnosticEvents.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Instrumentation/IServerDiagnosticEvents.cs index 781460bcdb1..52ea3bc9006 100644 --- a/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Instrumentation/IServerDiagnosticEvents.cs +++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Instrumentation/IServerDiagnosticEvents.cs @@ -1,4 +1,6 @@ using Microsoft.AspNetCore.Http; +using HotChocolate.AspNetCore.Subscriptions; +using HotChocolate.AspNetCore.Subscriptions.Protocols; using HotChocolate.Language; namespace HotChocolate.AspNetCore.Instrumentation; @@ -138,6 +140,21 @@ void StartOperationBatchRequest( /// IDisposable WebSocketSession(HttpContext context); + /// + /// Called within the scope once the client's connection + /// initialization message has been accepted. + /// + /// + /// The GraphQL socket session. + /// + /// + /// The connection initialization message sent by the client. Its payload is only valid + /// for the duration of this call. + /// + void WebSocketConnectionInitialized( + ISocketSession session, + IOperationMessagePayload connectionInitMessage); + /// /// Called within the scope and signals /// that an error occurred that terminated the session. diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Instrumentation/NoopServerDiagnosticEventListener.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Instrumentation/NoopServerDiagnosticEventListener.cs index 2270cb61880..72c9df2d215 100644 --- a/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Instrumentation/NoopServerDiagnosticEventListener.cs +++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Instrumentation/NoopServerDiagnosticEventListener.cs @@ -1,4 +1,6 @@ using Microsoft.AspNetCore.Http; +using HotChocolate.AspNetCore.Subscriptions; +using HotChocolate.AspNetCore.Subscriptions.Protocols; using HotChocolate.Language; using static HotChocolate.AspNetCore.Instrumentation.ServerDiagnosticEventListener; @@ -41,6 +43,12 @@ public void ParserErrors(HttpContext context, IReadOnlyList errors) public IDisposable WebSocketSession(HttpContext context) => EmptyScope; + public void WebSocketConnectionInitialized( + ISocketSession session, + IOperationMessagePayload connectionInitMessage) + { + } + public void WebSocketSessionError(HttpContext context, Exception exception) { } diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Instrumentation/ServerDiagnosticEventListener.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Instrumentation/ServerDiagnosticEventListener.cs index ee6e9db0de0..5f1772cd182 100644 --- a/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Instrumentation/ServerDiagnosticEventListener.cs +++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Instrumentation/ServerDiagnosticEventListener.cs @@ -1,4 +1,6 @@ using Microsoft.AspNetCore.Http; +using HotChocolate.AspNetCore.Subscriptions; +using HotChocolate.AspNetCore.Subscriptions.Protocols; using HotChocolate.Language; namespace HotChocolate.AspNetCore.Instrumentation; @@ -64,6 +66,13 @@ public virtual IDisposable FormatHttpResponse(HttpContext context, OperationResu public virtual IDisposable WebSocketSession(HttpContext context) => EmptyScope; + /// + public virtual void WebSocketConnectionInitialized( + ISocketSession session, + IOperationMessagePayload connectionInitMessage) + { + } + /// public virtual void WebSocketSessionError(HttpContext context, Exception exception) { diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/Protocols/Apollo/ApolloSubscriptionProtocolHandler.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/Protocols/Apollo/ApolloSubscriptionProtocolHandler.cs index 8a5372ff28b..1d54c295e30 100644 --- a/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/Protocols/Apollo/ApolloSubscriptionProtocolHandler.cs +++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/Protocols/Apollo/ApolloSubscriptionProtocolHandler.cs @@ -3,6 +3,7 @@ using System.Runtime.InteropServices; using System.Text.Json; using HotChocolate.AspNetCore.Formatters; +using HotChocolate.AspNetCore.Instrumentation; using HotChocolate.Buffers; using HotChocolate.Language; using HotChocolate.Text.Json; @@ -18,13 +19,16 @@ internal sealed class ApolloSubscriptionProtocolHandler : IProtocolHandler { private readonly ISocketSessionInterceptor _interceptor; private readonly IWebSocketPayloadFormatter _formatter; + private readonly IServerDiagnosticEvents _diagnosticEvents; public ApolloSubscriptionProtocolHandler( ISocketSessionInterceptor interceptor, - IWebSocketPayloadFormatter formatter) + IWebSocketPayloadFormatter formatter, + IServerDiagnosticEvents diagnosticEvents) { _interceptor = interceptor; _formatter = formatter; + _diagnosticEvents = diagnosticEvents; } public string Name => GraphQL_WS; @@ -113,6 +117,7 @@ await _interceptor.OnConnectAsync( if (connectionStatus.Accepted) { ((WebSocketConnection)connection).IsConnected = true; + _diagnosticEvents.WebSocketConnectionInitialized(session, operationMessageObj); await SendConnectionAcceptMessage( session, connectionStatus.Extensions, diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/Protocols/GraphQLOverWebSocket/GraphQLOverWebSocketProtocolHandler.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/Protocols/GraphQLOverWebSocket/GraphQLOverWebSocketProtocolHandler.cs index 373364a99e1..09f029a6bd9 100644 --- a/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/Protocols/GraphQLOverWebSocket/GraphQLOverWebSocketProtocolHandler.cs +++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/Subscriptions/Protocols/GraphQLOverWebSocket/GraphQLOverWebSocketProtocolHandler.cs @@ -3,6 +3,7 @@ using System.Runtime.InteropServices; using System.Text.Json; using HotChocolate.AspNetCore.Formatters; +using HotChocolate.AspNetCore.Instrumentation; using HotChocolate.Buffers; using HotChocolate.Language; using HotChocolate.AspNetCore.Utilities; @@ -19,7 +20,8 @@ internal sealed class GraphQLOverWebSocketProtocolHandler( IWebSocketPayloadFormatter formatter, IDocumentCache documentCache, IDocumentHashProvider documentHashProvider, - ParserOptions parserOptions) + ParserOptions parserOptions, + IServerDiagnosticEvents diagnosticEvents) : IGraphQLOverWebSocketProtocolHandler { public string Name => GraphQL_Transport_WS; @@ -128,6 +130,7 @@ await interceptor.OnConnectAsync( if (connectionStatus.Accepted) { ((WebSocketConnection)connection).IsConnected = true; + diagnosticEvents.WebSocketConnectionInitialized(session, operationMessageObj); await SendConnectionAcceptMessage( session, connectionStatus.Extensions, diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/HotChocolateAspNetCoreServiceCollectionExtensions.Subscriptions.cs b/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/HotChocolateAspNetCoreServiceCollectionExtensions.Subscriptions.cs index 95aa367b0d4..cb06a512ce3 100644 --- a/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/HotChocolateAspNetCoreServiceCollectionExtensions.Subscriptions.cs +++ b/src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/HotChocolateAspNetCoreServiceCollectionExtensions.Subscriptions.cs @@ -2,6 +2,7 @@ using Microsoft.Extensions.DependencyInjection.Extensions; using HotChocolate.AspNetCore; using HotChocolate.AspNetCore.Formatters; +using HotChocolate.AspNetCore.Instrumentation; using HotChocolate.AspNetCore.Subscriptions.Protocols; using HotChocolate.AspNetCore.Subscriptions.Protocols.Apollo; using HotChocolate.AspNetCore.Subscriptions.Protocols.GraphQLOverWebSocket; @@ -86,7 +87,8 @@ private static IRequestExecutorBuilder AddApolloProtocol( s => s.AddSingleton( sp => new ApolloSubscriptionProtocolHandler( sp.GetRequiredService(), - sp.GetRequiredService()))); + sp.GetRequiredService(), + sp.GetRequiredService()))); private static IRequestExecutorBuilder AddGraphQLOverWebSocketProtocol( this IRequestExecutorBuilder builder) @@ -97,7 +99,8 @@ private static IRequestExecutorBuilder AddGraphQLOverWebSocketProtocol( sp.GetRequiredService(), sp.GetRequiredService(), sp.GetRequiredService(), - sp.GetRequiredService()))); + sp.GetRequiredService(), + sp.GetRequiredService()))); /// /// Adds a custom WebSocket payload formatter to the DI. diff --git a/src/HotChocolate/Core/src/Types/Types/Descriptors/Conventions/DescriptorContext.cs b/src/HotChocolate/Core/src/Types/Types/Descriptors/Conventions/DescriptorContext.cs index 8cb7f493d17..bcdf72682da 100644 --- a/src/HotChocolate/Core/src/Types/Types/Descriptors/Conventions/DescriptorContext.cs +++ b/src/HotChocolate/Core/src/Types/Types/Descriptors/Conventions/DescriptorContext.cs @@ -6,6 +6,7 @@ using HotChocolate.Resolvers; using HotChocolate.Types.Relay; using HotChocolate.Utilities; +using EmptyServiceProvider = HotChocolate.Utilities.EmptyServiceProvider; using ThrowHelper = HotChocolate.Utilities.ThrowHelper; namespace HotChocolate.Types.Descriptors; diff --git a/src/HotChocolate/Core/test/Types.Tests/Resolvers/ResolverCompilerTests.cs b/src/HotChocolate/Core/test/Types.Tests/Resolvers/ResolverCompilerTests.cs index 9b07b50af26..c67c7cb5715 100644 --- a/src/HotChocolate/Core/test/Types.Tests/Resolvers/ResolverCompilerTests.cs +++ b/src/HotChocolate/Core/test/Types.Tests/Resolvers/ResolverCompilerTests.cs @@ -9,8 +9,8 @@ using HotChocolate.Tests; using HotChocolate.Types; using HotChocolate.Types.Descriptors; -using HotChocolate.Utilities; using Moq; +using EmptyServiceProvider = HotChocolate.Utilities.EmptyServiceProvider; namespace HotChocolate.Resolvers; diff --git a/src/HotChocolate/Diagnostics/src/Diagnostics.Core/ActivityEnricherBase.cs b/src/HotChocolate/Diagnostics/src/Diagnostics.Core/ActivityEnricherBase.cs index 8f276a64d98..bb2e1e49a97 100644 --- a/src/HotChocolate/Diagnostics/src/Diagnostics.Core/ActivityEnricherBase.cs +++ b/src/HotChocolate/Diagnostics/src/Diagnostics.Core/ActivityEnricherBase.cs @@ -1,6 +1,8 @@ using System.Diagnostics; using Microsoft.AspNetCore.Http; using HotChocolate.AspNetCore.Instrumentation; +using HotChocolate.AspNetCore.Subscriptions; +using HotChocolate.AspNetCore.Subscriptions.Protocols; using HotChocolate.Execution; using HotChocolate.Language; @@ -12,6 +14,11 @@ namespace HotChocolate.Diagnostics; /// public abstract class ActivityEnricherBase { + public virtual void OnWebSocketConnectionInitialized( + ISocketSession session, + IOperationMessagePayload connectionInitMessage) + { } + public virtual void EnrichExecuteHttpRequest( HttpContext httpContext, HttpRequestKind kind, diff --git a/src/HotChocolate/Diagnostics/src/Diagnostics/Listeners/ActivityExecutionDiagnosticListener.cs b/src/HotChocolate/Diagnostics/src/Diagnostics/Listeners/ActivityExecutionDiagnosticListener.cs index e464a81128b..a4d945214ba 100644 --- a/src/HotChocolate/Diagnostics/src/Diagnostics/Listeners/ActivityExecutionDiagnosticListener.cs +++ b/src/HotChocolate/Diagnostics/src/Diagnostics/Listeners/ActivityExecutionDiagnosticListener.cs @@ -25,15 +25,18 @@ public override IDisposable ExecuteRequest(RequestContext context) if (options.SkipExecuteRequest) { - if (!options.SkipExecuteHttpRequest - && context.Features.TryGet(out var httpContext) - && httpContext.Features.Get() is { } httpRequestSpan) + if (options.SkipExecuteHttpRequest + || !context.Features.TryGet(out var httpContext)) { - httpContextActivity = httpRequestSpan.Activity; + return EmptyScope; } - else + + // A GraphQL over HTTP request reuses the transport span as the root request + // span instead of emitting a second one. A transport that has no span of its + // own, like a WebSocket session, falls through to a dedicated request span. + if (httpContext.Features.Get() is { } httpRequestSpan) { - return EmptyScope; + httpContextActivity = httpRequestSpan.Activity; } } diff --git a/src/HotChocolate/Diagnostics/src/Diagnostics/Listeners/ActivityServerDiagnosticListener.cs b/src/HotChocolate/Diagnostics/src/Diagnostics/Listeners/ActivityServerDiagnosticListener.cs index b10d244b12a..c81ef74843e 100644 --- a/src/HotChocolate/Diagnostics/src/Diagnostics/Listeners/ActivityServerDiagnosticListener.cs +++ b/src/HotChocolate/Diagnostics/src/Diagnostics/Listeners/ActivityServerDiagnosticListener.cs @@ -1,5 +1,7 @@ using Microsoft.AspNetCore.Http; using HotChocolate.AspNetCore.Instrumentation; +using HotChocolate.AspNetCore.Subscriptions; +using HotChocolate.AspNetCore.Subscriptions.Protocols; using HotChocolate.Execution; using HotChocolate.Language; using static HotChocolate.Diagnostics.HotChocolateActivitySource; @@ -103,6 +105,11 @@ public override void ParserErrors(HttpContext context, IReadOnlyList err } } + public override void WebSocketConnectionInitialized( + ISocketSession session, + IOperationMessagePayload connectionInitMessage) + => enricher.OnWebSocketConnectionInitialized(session, connectionInitMessage); + public override IDisposable FormatHttpResponse(HttpContext context, OperationResult result) { if (options.SkipFormatHttpResponse) diff --git a/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/ActivityServerDiagnosticListenerTests.cs b/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/ActivityServerDiagnosticListenerTests.cs index f120ccf54fa..f281240ff33 100644 --- a/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/ActivityServerDiagnosticListenerTests.cs +++ b/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/ActivityServerDiagnosticListenerTests.cs @@ -1,13 +1,23 @@ +using System.Diagnostics; using System.Net.Http.Headers; +using System.Net.WebSockets; using System.Text; +using System.Text.Json; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.DependencyInjection; +using HotChocolate.AspNetCore.Subscriptions; +using HotChocolate.AspNetCore.Subscriptions.Protocols; using HotChocolate.AspNetCore.Tests.Utilities; +using HotChocolate.AspNetCore.Tests.Utilities.Subscriptions.Apollo; using HotChocolate.Execution; using HotChocolate.Execution.Configuration; +using HotChocolate.Language; using HotChocolate.Resolvers; using HotChocolate.Subscriptions; using HotChocolate.Transport.Http; +using HotChocolate.Transport.Sockets; +using HotChocolate.Transport.Sockets.Client; using HotChocolate.Types; using static CookieCrumble.TestEnvironment; using static HotChocolate.Diagnostics.ActivityTestHelper; @@ -19,6 +29,7 @@ namespace HotChocolate.Diagnostics; public class ActivityServerDiagnosticListenerTests(TestServerFactory serverFactory) : ServerTestBase(serverFactory) { private static readonly Uri s_url = new("http://localhost:5000/graphql"); + private static readonly Uri s_webSocketUrl = new("ws://localhost:5000/graphql"); [Fact] public async Task Http_Post_SingleRequest_GetHeroName_Default() @@ -743,6 +754,482 @@ public async Task Http_Subscription_Event_Should_Be_Error_When_Timeout() } } + [Fact] + public async Task WebSocket_Subscription_Should_Be_Ok_When_Server_Completes_Default() + { + using var guard = new CancellationTokenSource(TimeSpan.FromSeconds(10)); + + using (CaptureActivities(out var activities)) + { + // arrange + var signal = new HttpSubscriptionSignal(); + using var server = CreateInstrumentedServer( + configureBuilder: b => b + .AddTypeExtension() + .Services.AddSingleton(signal)); + using var webSocket = await ConnectWebSocketAsync(server, guard.Token); + await using var client = await SocketClient.ConnectAsync(webSocket, guard.Token); + var sender = server.Services.GetRequiredService(); + + var request = new OperationRequest("subscription OnMessageSubscription { onMessage }"); + + using var result = await client.ExecuteAsync(request, guard.Token); + var results = result.ReadResultsAsync().GetAsyncEnumerator(guard.Token); + + // act + try + { + var moveNext = results.MoveNextAsync().AsTask(); + await signal.Subscribed.Task.WaitAsync(guard.Token); + await sender.SendAsync("OnMessage", "hello", guard.Token); + Assert.True(await moveNext); + await sender.CompleteAsync("OnMessage"); + Assert.False(await results.MoveNextAsync()); + } + finally + { + await IgnoreSocketTeardownAsync(results.DisposeAsync().AsTask()); + } + + // the session encloses every span, so close it before the trace is read + await CloseWebSocketAsync(webSocket, guard.Token); + + // assert + activities.MatchSnapshot(Postfix([NET11_0])); + } + } + + [Fact] + public async Task WebSocket_Subscription_Should_Be_Ok_When_Server_Completes() + { + using var guard = new CancellationTokenSource(TimeSpan.FromSeconds(10)); + + using (CaptureActivities(out var activities)) + { + // arrange + var signal = new HttpSubscriptionSignal(); + using var server = CreateInstrumentedServer( + o => o.Scopes = ActivityScopes.All, + b => b + .AddTypeExtension() + .Services.AddSingleton(signal)); + using var webSocket = await ConnectWebSocketAsync(server, guard.Token); + await using var client = await SocketClient.ConnectAsync(webSocket, guard.Token); + var sender = server.Services.GetRequiredService(); + + var request = new OperationRequest("subscription OnMessageSubscription { onMessage }"); + + using var result = await client.ExecuteAsync(request, guard.Token); + var results = result.ReadResultsAsync().GetAsyncEnumerator(guard.Token); + + // act + try + { + var moveNext = results.MoveNextAsync().AsTask(); + await signal.Subscribed.Task.WaitAsync(guard.Token); + await sender.SendAsync("OnMessage", "hello", guard.Token); + Assert.True(await moveNext); + await sender.CompleteAsync("OnMessage"); + Assert.False(await results.MoveNextAsync()); + } + finally + { + await IgnoreSocketTeardownAsync(results.DisposeAsync().AsTask()); + } + + // the session encloses every span, so close it before the trace is read + await CloseWebSocketAsync(webSocket, guard.Token); + + // assert + activities.MatchSnapshot(Postfix([NET11_0])); + } + } + + [Fact] + public async Task WebSocket_Subscription_Should_Be_Ok_When_Client_Closes_Connection() + { + using var guard = new CancellationTokenSource(TimeSpan.FromSeconds(10)); + + using (CaptureActivities(out var activities)) + { + // arrange + var signal = new HttpSubscriptionSignal(); + using var server = CreateInstrumentedServer( + o => o.Scopes = ActivityScopes.All, + b => b + .AddTypeExtension() + .Services.AddSingleton(signal)); + using var webSocket = await ConnectWebSocketAsync(server, guard.Token); + await using var client = await SocketClient.ConnectAsync(webSocket, guard.Token); + var sender = server.Services.GetRequiredService(); + + var request = new OperationRequest("subscription OnMessageSubscription { onMessage }"); + + using var result = await client.ExecuteAsync(request, guard.Token); + var results = result.ReadResultsAsync().GetAsyncEnumerator(guard.Token); + + var moveNext = results.MoveNextAsync().AsTask(); + await signal.Subscribed.Task.WaitAsync(guard.Token); + await sender.SendAsync("OnMessage", "hello", guard.Token); + Assert.True(await moveNext); + + // act + // close the connection without unsubscribing, so the server has to tear the + // still-running subscription down with the session + await CloseWebSocketAsync(webSocket, guard.Token); + await IgnoreSocketTeardownAsync(results.DisposeAsync().AsTask()); + + // assert + activities.MatchSnapshot(Postfix([NET11_0])); + } + } + + [Fact] + public async Task WebSocket_Subscription_Event_Should_Be_Error_When_Timeout() + { + using var guard = new CancellationTokenSource(TimeSpan.FromSeconds(10)); + + using (CaptureActivities(out var activities)) + { + // arrange + // a blocking resolver plus a tiny timeout forces a server-side event timeout + var signal = new HttpSubscriptionSignal(); + using var server = CreateInstrumentedServer( + o => o.Scopes = ActivityScopes.All, + b => b + .AddTypeExtension() + .ModifyRequestOptions(o => o.ExecutionTimeout = TimeSpan.FromMilliseconds(200)) + .Services.AddSingleton(signal)); + using var webSocket = await ConnectWebSocketAsync(server, guard.Token); + await using var client = await SocketClient.ConnectAsync(webSocket, guard.Token); + var sender = server.Services.GetRequiredService(); + + var request = new OperationRequest( + "subscription OnBlockingMessageSubscription { onBlockingMessage }"); + + using var result = await client.ExecuteAsync(request, guard.Token); + var results = result.ReadResultsAsync().GetAsyncEnumerator(guard.Token); + + // act + // the timeout tears the operation down, so no result is ever delivered + var first = results.MoveNextAsync().AsTask(); + await signal.Subscribed.Task.WaitAsync(guard.Token); + await sender.SendAsync("OnBlockingMessage", "hello", guard.Token); + await signal.Entered.Task.WaitAsync(guard.Token); + + Assert.False(await first); + + await IgnoreSocketTeardownAsync(results.DisposeAsync().AsTask()); + await CloseWebSocketAsync(webSocket, guard.Token); + + // assert + activities.MatchSnapshot(Postfix([NET11_0])); + } + } + + [Fact] + public async Task WebSocket_Header_Should_Be_Added_As_Tag_To_Request_And_Event_Spans() + { + using var guard = new CancellationTokenSource(TimeSpan.FromSeconds(10)); + + using (CaptureActivities(out var activities)) + { + // arrange + // non-browser clients can send the tenant as a handshake header instead + var signal = new HttpSubscriptionSignal(); + using var server = CreateInstrumentedServer( + o => o.Scopes = ActivityScopes.All, + b => b + .AddTypeExtension() + .AddApplicationService() + .Services + .AddSingleton(signal) + .AddSingleton()); + using var webSocket = await ConnectWebSocketAsync( + server, + guard.Token, + r => r.Headers[TenantHeaderName] = "acme-42"); + await using var client = await SocketClient.ConnectAsync(webSocket, guard.Token); + var sender = server.Services.GetRequiredService(); + + var request = new OperationRequest("subscription OnMessageSubscription { onMessage }"); + + using var result = await client.ExecuteAsync(request, guard.Token); + var results = result.ReadResultsAsync().GetAsyncEnumerator(guard.Token); + + // act + try + { + var moveNext = results.MoveNextAsync().AsTask(); + await signal.Subscribed.Task.WaitAsync(guard.Token); + await sender.SendAsync("OnMessage", "hello", guard.Token); + Assert.True(await moveNext); + await sender.CompleteAsync("OnMessage"); + Assert.False(await results.MoveNextAsync()); + } + finally + { + await IgnoreSocketTeardownAsync(results.DisposeAsync().AsTask()); + } + + await CloseWebSocketAsync(webSocket, guard.Token); + + // assert + activities.MatchSnapshot(Postfix([NET11_0])); + } + } + + [Fact] + public async Task WebSocket_ConnectionInit_Payload_Should_Be_Added_As_Tag_To_Request_And_Event_Spans() + { + using var guard = new CancellationTokenSource(TimeSpan.FromSeconds(10)); + + using (CaptureActivities(out var activities)) + { + // arrange + var signal = new HttpSubscriptionSignal(); + using var server = CreateInstrumentedServer( + o => o.Scopes = ActivityScopes.All, + b => b + .AddTypeExtension() + .AddApplicationService() + .Services + .AddSingleton(signal) + .AddSingleton()); + using var webSocket = await ConnectWebSocketAsync(server, guard.Token); + var payload = JsonSerializer.SerializeToElement(new { tenant = "acme-42" }); + await using var client = await SocketClient.ConnectAsync(webSocket, payload, guard.Token); + var sender = server.Services.GetRequiredService(); + + var request = new OperationRequest("subscription OnMessageSubscription { onMessage }"); + + using var result = await client.ExecuteAsync(request, guard.Token); + var results = result.ReadResultsAsync().GetAsyncEnumerator(guard.Token); + + // act + try + { + var moveNext = results.MoveNextAsync().AsTask(); + await signal.Subscribed.Task.WaitAsync(guard.Token); + await sender.SendAsync("OnMessage", "hello", guard.Token); + Assert.True(await moveNext); + await sender.CompleteAsync("OnMessage"); + Assert.False(await results.MoveNextAsync()); + } + finally + { + await IgnoreSocketTeardownAsync(results.DisposeAsync().AsTask()); + } + + await CloseWebSocketAsync(webSocket, guard.Token); + + // assert + activities.MatchSnapshot(Postfix([NET11_0])); + } + } + + [Fact] + public async Task WebSocket_Apollo_ConnectionInit_Payload_Should_Be_Added_As_Tag_To_Request_And_Event_Spans() + { + using var guard = new CancellationTokenSource(TimeSpan.FromSeconds(10)); + + using (CaptureActivities(out var activities)) + { + // arrange + // the legacy apollo protocol raises the event from its own protocol handler + var signal = new HttpSubscriptionSignal(); + using var server = CreateInstrumentedServer( + o => o.Scopes = ActivityScopes.All, + b => b + .AddTypeExtension() + .AddApplicationService() + .Services + .AddSingleton(signal) + .AddSingleton()); + var sender = server.Services.GetRequiredService(); + + var webSocketClient = server.CreateWebSocketClient(); + webSocketClient.ConfigureRequest = + r => r.Headers.SecWebSocketProtocol = WellKnownProtocols.GraphQL_WS; + using var webSocket = await webSocketClient.ConnectAsync(s_webSocketUrl, guard.Token); + + await webSocket.SendConnectionInitializeAsync( + new Dictionary { ["tenant"] = "acme-42" }, + guard.Token); + Assert.NotNull(await WaitForApolloMessageAsync(webSocket, "connection_ack", guard.Token)); + + // act + await webSocket.SendSubscriptionStartAsync( + "1", + new GraphQLRequest( + Utf8GraphQLParser.Parse("subscription OnMessageSubscription { onMessage }"))); + await signal.Subscribed.Task.WaitAsync(guard.Token); + await sender.SendAsync("OnMessage", "hello", guard.Token); + Assert.NotNull(await WaitForApolloMessageAsync(webSocket, "data", guard.Token)); + + await sender.CompleteAsync("OnMessage"); + Assert.NotNull(await WaitForApolloMessageAsync(webSocket, "complete", guard.Token)); + + await CloseWebSocketAsync(webSocket, guard.Token); + + // assert + activities.MatchSnapshot(Postfix([NET11_0])); + } + } + + private static async Task WaitForApolloMessageAsync( + WebSocket webSocket, + string type, + CancellationToken cancellationToken) + { + using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(5)); + using var combined = + CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeout.Token); + + try + { + while (!combined.Token.IsCancellationRequested) + { + var message = await webSocket.ReceiveServerMessageAsync(combined.Token); + + if (message is null) + { + await Task.Delay(5, combined.Token); + continue; + } + + if (message.RootElement.GetProperty("type").GetString() == type) + { + return message; + } + + message.Dispose(); + } + } + catch (OperationCanceledException) + { + // expected: no matching message arrived within the timeout + } + + return null; + } + + private const string TenantHeaderName = "X-Tenant"; + + public sealed record TenantFeature(string Tenant); + + public sealed class TenantActivityEnricher(InstrumentationOptions options) + : ActivityEnricher(options) + { + public override void OnWebSocketConnectionInitialized( + ISocketSession session, + IOperationMessagePayload connectionInitMessage) + { + if (connectionInitMessage.Payload is { ValueKind: JsonValueKind.Object } payload + && payload.TryGetProperty("tenant", out var tenant) + && tenant.ValueKind is JsonValueKind.String) + { + session.Connection.Features.Set(new TenantFeature(tenant.GetString()!)); + } + } + + public override void EnrichExecuteRequest(RequestContext context, Activity activity) + { + base.EnrichExecuteRequest(context, activity); + SetTenantTag(context, activity); + } + + public override void EnrichOnSubscriptionEvent( + RequestContext context, + ulong subscriptionId, + Activity activity) + { + base.EnrichOnSubscriptionEvent(context, subscriptionId, activity); + SetTenantTag(context, activity); + } + + private static void SetTenantTag(RequestContext context, Activity activity) + { + if (ResolveTenant(context) is { } tenant) + { + activity.SetTag("test.tenant", tenant); + } + } + + private static string? ResolveTenant(RequestContext context) + { + if (context.ContextData.TryGetValue(nameof(ISocketSession), out var value) + && value is ISocketSession session + && session.Connection.Features.Get() is { } tenant) + { + return tenant.Tenant; + } + + if (context.Features.Get() is { } httpContext + && httpContext.Request.Headers.TryGetValue(TenantHeaderName, out var header)) + { + return header.ToString(); + } + + return null; + } + } + + private static async Task ConnectWebSocketAsync( + TestServer server, + CancellationToken cancellationToken, + Action? configureRequest = null) + { + var webSocketClient = server.CreateWebSocketClient(); + webSocketClient.ConfigureRequest = r => + { + r.Headers.SecWebSocketProtocol = WellKnownProtocols.GraphQL_Transport_WS; + configureRequest?.Invoke(r); + }; + return await webSocketClient.ConnectAsync(s_webSocketUrl, cancellationToken); + } + + private static async Task CloseWebSocketAsync( + WebSocket webSocket, + CancellationToken cancellationToken) + { + try + { + await webSocket.CloseOutputAsync( + WebSocketCloseStatus.NormalClosure, + "done", + cancellationToken); + } + catch (Exception ex) when (ex is WebSocketException or IOException or ObjectDisposedException) + { + // the server can end the session before the close frame is sent + } + } + + private static async Task IgnoreSocketTeardownAsync(Task task) + { + try + { + await task; + } + catch (OperationCanceledException) + { + // expected: the streamed read was aborted by the client + } + catch (IOException) + { + // expected: aborting an in-flight read can surface as an I/O failure + } + catch (WebSocketException) + { + // expected: the socket was aborted while a read was in flight + } + catch (SocketClosedException) + { + // expected: the client dropped the connection without a close frame + } + } + private static async Task DrainAsync( IAsyncEnumerator results, CancellationToken cancellationToken) diff --git a/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/ActivityServerDiagnosticListenerTests.WebSocket_Apollo_ConnectionInit_Payload_Should_Be_Added_As_Tag_To_Request_And_Event_Spans.snap b/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/ActivityServerDiagnosticListenerTests.WebSocket_Apollo_ConnectionInit_Payload_Should_Be_Added_As_Tag_To_Request_And_Event_Spans.snap new file mode 100644 index 00000000000..a82c92e88be --- /dev/null +++ b/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/ActivityServerDiagnosticListenerTests.WebSocket_Apollo_ConnectionInit_Payload_Should_Be_Added_As_Tag_To_Request_And_Event_Spans.snap @@ -0,0 +1,256 @@ +{ + "source": { + "name": "HotChocolate.Diagnostics" + }, + "activities": [ + { + "OperationName": "Microsoft.AspNetCore.Hosting.HttpRequestIn", + "DisplayName": "GET /graphql/{**slug}", + "Kind": "Server", + "Status": "Unset", + "tags": [ + { + "Key": "server.address", + "Value": "localhost" + }, + { + "Key": "server.port", + "Value": 5000 + }, + { + "Key": "http.request.method", + "Value": "GET" + }, + { + "Key": "url.scheme", + "Value": "http" + }, + { + "Key": "url.path", + "Value": "/graphql" + }, + { + "Key": "network.protocol.version", + "Value": "1.1" + }, + { + "Key": "http.route", + "Value": "/graphql/{**slug}" + }, + { + "Key": "http.response.status_code", + "Value": 101 + } + ], + "event": [], + "activities": [ + { + "OperationName": "GraphQL Operation", + "DisplayName": "subscription", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "request" + }, + { + "Key": "server.address", + "Value": "localhost" + }, + { + "Key": "server.port", + "Value": 5000 + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + }, + { + "Key": "test.tenant", + "Value": "acme-42" + } + ], + "event": [ + { + "Name": "AddedOperationToCache", + "Tags": [] + }, + { + "Name": "AddedDocumentToCache", + "Tags": [] + } + ], + "activities": [ + { + "OperationName": "GraphQL Document Validation", + "DisplayName": "GraphQL Document Validation", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "validate" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + } + ], + "event": [] + }, + { + "OperationName": "GraphQL Complexity Analysis", + "DisplayName": "GraphQL Complexity Analysis", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + }, + { + "Key": "graphql.operation.fieldCost", + "Value": 0.0 + }, + { + "Key": "graphql.operation.typeCost", + "Value": 1.0 + } + ], + "event": [] + }, + { + "OperationName": "GraphQL Operation Planning", + "DisplayName": "GraphQL Operation Planning", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "plan" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + } + ], + "event": [] + }, + { + "OperationName": "GraphQL Operation Execution", + "DisplayName": "GraphQL Operation Execution", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "execute" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + } + ], + "event": [], + "activities": [ + { + "OperationName": "GraphQL Subscription Event", + "DisplayName": "GraphQL Subscription Event", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "subscription_event" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + }, + { + "Key": "graphql.subscription.id", + "Value": "1" + }, + { + "Key": "test.tenant", + "Value": "acme-42" + } + ], + "event": [], + "activities": [ + { + "OperationName": "Subscription.onMessage", + "DisplayName": "Subscription.onMessage", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "resolve" + }, + { + "Key": "graphql.field.alias", + "Value": "onMessage" + }, + { + "Key": "graphql.field.path", + "Value": "onMessage" + }, + { + "Key": "graphql.field.name", + "Value": "onMessage" + }, + { + "Key": "graphql.field.schema_coordinate", + "Value": "Subscription.onMessage" + }, + { + "Key": "graphql.field.parent_type", + "Value": "Subscription" + } + ], + "event": [] + } + ] + } + ] + } + ] + } + ] + } + ] +} diff --git a/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/ActivityServerDiagnosticListenerTests.WebSocket_Apollo_ConnectionInit_Payload_Should_Be_Added_As_Tag_To_Request_And_Event_Spans_NET11_0.snap b/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/ActivityServerDiagnosticListenerTests.WebSocket_Apollo_ConnectionInit_Payload_Should_Be_Added_As_Tag_To_Request_And_Event_Spans_NET11_0.snap new file mode 100644 index 00000000000..03affcdb64f --- /dev/null +++ b/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/ActivityServerDiagnosticListenerTests.WebSocket_Apollo_ConnectionInit_Payload_Should_Be_Added_As_Tag_To_Request_And_Event_Spans_NET11_0.snap @@ -0,0 +1,256 @@ +{ + "source": { + "name": "HotChocolate.Diagnostics" + }, + "activities": [ + { + "OperationName": "Microsoft.AspNetCore.Hosting.HttpRequestIn", + "DisplayName": "GET /graphql/{**slug}", + "Kind": "Server", + "Status": "Unset", + "tags": [ + { + "Key": "server.address", + "Value": "localhost" + }, + { + "Key": "server.port", + "Value": 5000 + }, + { + "Key": "http.request.method", + "Value": "GET" + }, + { + "Key": "url.scheme", + "Value": "http" + }, + { + "Key": "url.path", + "Value": "/graphql" + }, + { + "Key": "network.protocol.version", + "Value": "1.1" + }, + { + "Key": "http.response.status_code", + "Value": 101 + }, + { + "Key": "http.route", + "Value": "/graphql/{**slug}" + } + ], + "event": [], + "activities": [ + { + "OperationName": "GraphQL Operation", + "DisplayName": "subscription", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "request" + }, + { + "Key": "server.address", + "Value": "localhost" + }, + { + "Key": "server.port", + "Value": 5000 + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + }, + { + "Key": "test.tenant", + "Value": "acme-42" + } + ], + "event": [ + { + "Name": "AddedOperationToCache", + "Tags": [] + }, + { + "Name": "AddedDocumentToCache", + "Tags": [] + } + ], + "activities": [ + { + "OperationName": "GraphQL Document Validation", + "DisplayName": "GraphQL Document Validation", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "validate" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + } + ], + "event": [] + }, + { + "OperationName": "GraphQL Complexity Analysis", + "DisplayName": "GraphQL Complexity Analysis", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + }, + { + "Key": "graphql.operation.fieldCost", + "Value": 0.0 + }, + { + "Key": "graphql.operation.typeCost", + "Value": 1.0 + } + ], + "event": [] + }, + { + "OperationName": "GraphQL Operation Planning", + "DisplayName": "GraphQL Operation Planning", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "plan" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + } + ], + "event": [] + }, + { + "OperationName": "GraphQL Operation Execution", + "DisplayName": "GraphQL Operation Execution", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "execute" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + } + ], + "event": [], + "activities": [ + { + "OperationName": "GraphQL Subscription Event", + "DisplayName": "GraphQL Subscription Event", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "subscription_event" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + }, + { + "Key": "graphql.subscription.id", + "Value": "1" + }, + { + "Key": "test.tenant", + "Value": "acme-42" + } + ], + "event": [], + "activities": [ + { + "OperationName": "Subscription.onMessage", + "DisplayName": "Subscription.onMessage", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "resolve" + }, + { + "Key": "graphql.field.alias", + "Value": "onMessage" + }, + { + "Key": "graphql.field.path", + "Value": "onMessage" + }, + { + "Key": "graphql.field.name", + "Value": "onMessage" + }, + { + "Key": "graphql.field.schema_coordinate", + "Value": "Subscription.onMessage" + }, + { + "Key": "graphql.field.parent_type", + "Value": "Subscription" + } + ], + "event": [] + } + ] + } + ] + } + ] + } + ] + } + ] +} diff --git a/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/ActivityServerDiagnosticListenerTests.WebSocket_ConnectionInit_Payload_Should_Be_Added_As_Tag_To_Request_And_Event_Spans.snap b/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/ActivityServerDiagnosticListenerTests.WebSocket_ConnectionInit_Payload_Should_Be_Added_As_Tag_To_Request_And_Event_Spans.snap new file mode 100644 index 00000000000..a82c92e88be --- /dev/null +++ b/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/ActivityServerDiagnosticListenerTests.WebSocket_ConnectionInit_Payload_Should_Be_Added_As_Tag_To_Request_And_Event_Spans.snap @@ -0,0 +1,256 @@ +{ + "source": { + "name": "HotChocolate.Diagnostics" + }, + "activities": [ + { + "OperationName": "Microsoft.AspNetCore.Hosting.HttpRequestIn", + "DisplayName": "GET /graphql/{**slug}", + "Kind": "Server", + "Status": "Unset", + "tags": [ + { + "Key": "server.address", + "Value": "localhost" + }, + { + "Key": "server.port", + "Value": 5000 + }, + { + "Key": "http.request.method", + "Value": "GET" + }, + { + "Key": "url.scheme", + "Value": "http" + }, + { + "Key": "url.path", + "Value": "/graphql" + }, + { + "Key": "network.protocol.version", + "Value": "1.1" + }, + { + "Key": "http.route", + "Value": "/graphql/{**slug}" + }, + { + "Key": "http.response.status_code", + "Value": 101 + } + ], + "event": [], + "activities": [ + { + "OperationName": "GraphQL Operation", + "DisplayName": "subscription", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "request" + }, + { + "Key": "server.address", + "Value": "localhost" + }, + { + "Key": "server.port", + "Value": 5000 + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + }, + { + "Key": "test.tenant", + "Value": "acme-42" + } + ], + "event": [ + { + "Name": "AddedOperationToCache", + "Tags": [] + }, + { + "Name": "AddedDocumentToCache", + "Tags": [] + } + ], + "activities": [ + { + "OperationName": "GraphQL Document Validation", + "DisplayName": "GraphQL Document Validation", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "validate" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + } + ], + "event": [] + }, + { + "OperationName": "GraphQL Complexity Analysis", + "DisplayName": "GraphQL Complexity Analysis", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + }, + { + "Key": "graphql.operation.fieldCost", + "Value": 0.0 + }, + { + "Key": "graphql.operation.typeCost", + "Value": 1.0 + } + ], + "event": [] + }, + { + "OperationName": "GraphQL Operation Planning", + "DisplayName": "GraphQL Operation Planning", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "plan" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + } + ], + "event": [] + }, + { + "OperationName": "GraphQL Operation Execution", + "DisplayName": "GraphQL Operation Execution", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "execute" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + } + ], + "event": [], + "activities": [ + { + "OperationName": "GraphQL Subscription Event", + "DisplayName": "GraphQL Subscription Event", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "subscription_event" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + }, + { + "Key": "graphql.subscription.id", + "Value": "1" + }, + { + "Key": "test.tenant", + "Value": "acme-42" + } + ], + "event": [], + "activities": [ + { + "OperationName": "Subscription.onMessage", + "DisplayName": "Subscription.onMessage", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "resolve" + }, + { + "Key": "graphql.field.alias", + "Value": "onMessage" + }, + { + "Key": "graphql.field.path", + "Value": "onMessage" + }, + { + "Key": "graphql.field.name", + "Value": "onMessage" + }, + { + "Key": "graphql.field.schema_coordinate", + "Value": "Subscription.onMessage" + }, + { + "Key": "graphql.field.parent_type", + "Value": "Subscription" + } + ], + "event": [] + } + ] + } + ] + } + ] + } + ] + } + ] +} diff --git a/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/ActivityServerDiagnosticListenerTests.WebSocket_ConnectionInit_Payload_Should_Be_Added_As_Tag_To_Request_And_Event_Spans_NET11_0.snap b/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/ActivityServerDiagnosticListenerTests.WebSocket_ConnectionInit_Payload_Should_Be_Added_As_Tag_To_Request_And_Event_Spans_NET11_0.snap new file mode 100644 index 00000000000..03affcdb64f --- /dev/null +++ b/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/ActivityServerDiagnosticListenerTests.WebSocket_ConnectionInit_Payload_Should_Be_Added_As_Tag_To_Request_And_Event_Spans_NET11_0.snap @@ -0,0 +1,256 @@ +{ + "source": { + "name": "HotChocolate.Diagnostics" + }, + "activities": [ + { + "OperationName": "Microsoft.AspNetCore.Hosting.HttpRequestIn", + "DisplayName": "GET /graphql/{**slug}", + "Kind": "Server", + "Status": "Unset", + "tags": [ + { + "Key": "server.address", + "Value": "localhost" + }, + { + "Key": "server.port", + "Value": 5000 + }, + { + "Key": "http.request.method", + "Value": "GET" + }, + { + "Key": "url.scheme", + "Value": "http" + }, + { + "Key": "url.path", + "Value": "/graphql" + }, + { + "Key": "network.protocol.version", + "Value": "1.1" + }, + { + "Key": "http.response.status_code", + "Value": 101 + }, + { + "Key": "http.route", + "Value": "/graphql/{**slug}" + } + ], + "event": [], + "activities": [ + { + "OperationName": "GraphQL Operation", + "DisplayName": "subscription", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "request" + }, + { + "Key": "server.address", + "Value": "localhost" + }, + { + "Key": "server.port", + "Value": 5000 + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + }, + { + "Key": "test.tenant", + "Value": "acme-42" + } + ], + "event": [ + { + "Name": "AddedOperationToCache", + "Tags": [] + }, + { + "Name": "AddedDocumentToCache", + "Tags": [] + } + ], + "activities": [ + { + "OperationName": "GraphQL Document Validation", + "DisplayName": "GraphQL Document Validation", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "validate" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + } + ], + "event": [] + }, + { + "OperationName": "GraphQL Complexity Analysis", + "DisplayName": "GraphQL Complexity Analysis", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + }, + { + "Key": "graphql.operation.fieldCost", + "Value": 0.0 + }, + { + "Key": "graphql.operation.typeCost", + "Value": 1.0 + } + ], + "event": [] + }, + { + "OperationName": "GraphQL Operation Planning", + "DisplayName": "GraphQL Operation Planning", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "plan" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + } + ], + "event": [] + }, + { + "OperationName": "GraphQL Operation Execution", + "DisplayName": "GraphQL Operation Execution", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "execute" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + } + ], + "event": [], + "activities": [ + { + "OperationName": "GraphQL Subscription Event", + "DisplayName": "GraphQL Subscription Event", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "subscription_event" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + }, + { + "Key": "graphql.subscription.id", + "Value": "1" + }, + { + "Key": "test.tenant", + "Value": "acme-42" + } + ], + "event": [], + "activities": [ + { + "OperationName": "Subscription.onMessage", + "DisplayName": "Subscription.onMessage", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "resolve" + }, + { + "Key": "graphql.field.alias", + "Value": "onMessage" + }, + { + "Key": "graphql.field.path", + "Value": "onMessage" + }, + { + "Key": "graphql.field.name", + "Value": "onMessage" + }, + { + "Key": "graphql.field.schema_coordinate", + "Value": "Subscription.onMessage" + }, + { + "Key": "graphql.field.parent_type", + "Value": "Subscription" + } + ], + "event": [] + } + ] + } + ] + } + ] + } + ] + } + ] +} diff --git a/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/ActivityServerDiagnosticListenerTests.WebSocket_Header_Should_Be_Added_As_Tag_To_Request_And_Event_Spans.snap b/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/ActivityServerDiagnosticListenerTests.WebSocket_Header_Should_Be_Added_As_Tag_To_Request_And_Event_Spans.snap new file mode 100644 index 00000000000..a82c92e88be --- /dev/null +++ b/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/ActivityServerDiagnosticListenerTests.WebSocket_Header_Should_Be_Added_As_Tag_To_Request_And_Event_Spans.snap @@ -0,0 +1,256 @@ +{ + "source": { + "name": "HotChocolate.Diagnostics" + }, + "activities": [ + { + "OperationName": "Microsoft.AspNetCore.Hosting.HttpRequestIn", + "DisplayName": "GET /graphql/{**slug}", + "Kind": "Server", + "Status": "Unset", + "tags": [ + { + "Key": "server.address", + "Value": "localhost" + }, + { + "Key": "server.port", + "Value": 5000 + }, + { + "Key": "http.request.method", + "Value": "GET" + }, + { + "Key": "url.scheme", + "Value": "http" + }, + { + "Key": "url.path", + "Value": "/graphql" + }, + { + "Key": "network.protocol.version", + "Value": "1.1" + }, + { + "Key": "http.route", + "Value": "/graphql/{**slug}" + }, + { + "Key": "http.response.status_code", + "Value": 101 + } + ], + "event": [], + "activities": [ + { + "OperationName": "GraphQL Operation", + "DisplayName": "subscription", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "request" + }, + { + "Key": "server.address", + "Value": "localhost" + }, + { + "Key": "server.port", + "Value": 5000 + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + }, + { + "Key": "test.tenant", + "Value": "acme-42" + } + ], + "event": [ + { + "Name": "AddedOperationToCache", + "Tags": [] + }, + { + "Name": "AddedDocumentToCache", + "Tags": [] + } + ], + "activities": [ + { + "OperationName": "GraphQL Document Validation", + "DisplayName": "GraphQL Document Validation", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "validate" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + } + ], + "event": [] + }, + { + "OperationName": "GraphQL Complexity Analysis", + "DisplayName": "GraphQL Complexity Analysis", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + }, + { + "Key": "graphql.operation.fieldCost", + "Value": 0.0 + }, + { + "Key": "graphql.operation.typeCost", + "Value": 1.0 + } + ], + "event": [] + }, + { + "OperationName": "GraphQL Operation Planning", + "DisplayName": "GraphQL Operation Planning", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "plan" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + } + ], + "event": [] + }, + { + "OperationName": "GraphQL Operation Execution", + "DisplayName": "GraphQL Operation Execution", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "execute" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + } + ], + "event": [], + "activities": [ + { + "OperationName": "GraphQL Subscription Event", + "DisplayName": "GraphQL Subscription Event", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "subscription_event" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + }, + { + "Key": "graphql.subscription.id", + "Value": "1" + }, + { + "Key": "test.tenant", + "Value": "acme-42" + } + ], + "event": [], + "activities": [ + { + "OperationName": "Subscription.onMessage", + "DisplayName": "Subscription.onMessage", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "resolve" + }, + { + "Key": "graphql.field.alias", + "Value": "onMessage" + }, + { + "Key": "graphql.field.path", + "Value": "onMessage" + }, + { + "Key": "graphql.field.name", + "Value": "onMessage" + }, + { + "Key": "graphql.field.schema_coordinate", + "Value": "Subscription.onMessage" + }, + { + "Key": "graphql.field.parent_type", + "Value": "Subscription" + } + ], + "event": [] + } + ] + } + ] + } + ] + } + ] + } + ] +} diff --git a/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/ActivityServerDiagnosticListenerTests.WebSocket_Header_Should_Be_Added_As_Tag_To_Request_And_Event_Spans_NET11_0.snap b/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/ActivityServerDiagnosticListenerTests.WebSocket_Header_Should_Be_Added_As_Tag_To_Request_And_Event_Spans_NET11_0.snap new file mode 100644 index 00000000000..03affcdb64f --- /dev/null +++ b/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/ActivityServerDiagnosticListenerTests.WebSocket_Header_Should_Be_Added_As_Tag_To_Request_And_Event_Spans_NET11_0.snap @@ -0,0 +1,256 @@ +{ + "source": { + "name": "HotChocolate.Diagnostics" + }, + "activities": [ + { + "OperationName": "Microsoft.AspNetCore.Hosting.HttpRequestIn", + "DisplayName": "GET /graphql/{**slug}", + "Kind": "Server", + "Status": "Unset", + "tags": [ + { + "Key": "server.address", + "Value": "localhost" + }, + { + "Key": "server.port", + "Value": 5000 + }, + { + "Key": "http.request.method", + "Value": "GET" + }, + { + "Key": "url.scheme", + "Value": "http" + }, + { + "Key": "url.path", + "Value": "/graphql" + }, + { + "Key": "network.protocol.version", + "Value": "1.1" + }, + { + "Key": "http.response.status_code", + "Value": 101 + }, + { + "Key": "http.route", + "Value": "/graphql/{**slug}" + } + ], + "event": [], + "activities": [ + { + "OperationName": "GraphQL Operation", + "DisplayName": "subscription", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "request" + }, + { + "Key": "server.address", + "Value": "localhost" + }, + { + "Key": "server.port", + "Value": 5000 + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + }, + { + "Key": "test.tenant", + "Value": "acme-42" + } + ], + "event": [ + { + "Name": "AddedOperationToCache", + "Tags": [] + }, + { + "Name": "AddedDocumentToCache", + "Tags": [] + } + ], + "activities": [ + { + "OperationName": "GraphQL Document Validation", + "DisplayName": "GraphQL Document Validation", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "validate" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + } + ], + "event": [] + }, + { + "OperationName": "GraphQL Complexity Analysis", + "DisplayName": "GraphQL Complexity Analysis", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + }, + { + "Key": "graphql.operation.fieldCost", + "Value": 0.0 + }, + { + "Key": "graphql.operation.typeCost", + "Value": 1.0 + } + ], + "event": [] + }, + { + "OperationName": "GraphQL Operation Planning", + "DisplayName": "GraphQL Operation Planning", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "plan" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + } + ], + "event": [] + }, + { + "OperationName": "GraphQL Operation Execution", + "DisplayName": "GraphQL Operation Execution", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "execute" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + } + ], + "event": [], + "activities": [ + { + "OperationName": "GraphQL Subscription Event", + "DisplayName": "GraphQL Subscription Event", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "subscription_event" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + }, + { + "Key": "graphql.subscription.id", + "Value": "1" + }, + { + "Key": "test.tenant", + "Value": "acme-42" + } + ], + "event": [], + "activities": [ + { + "OperationName": "Subscription.onMessage", + "DisplayName": "Subscription.onMessage", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "resolve" + }, + { + "Key": "graphql.field.alias", + "Value": "onMessage" + }, + { + "Key": "graphql.field.path", + "Value": "onMessage" + }, + { + "Key": "graphql.field.name", + "Value": "onMessage" + }, + { + "Key": "graphql.field.schema_coordinate", + "Value": "Subscription.onMessage" + }, + { + "Key": "graphql.field.parent_type", + "Value": "Subscription" + } + ], + "event": [] + } + ] + } + ] + } + ] + } + ] + } + ] +} diff --git a/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/ActivityServerDiagnosticListenerTests.WebSocket_Subscription_Event_Should_Be_Error_When_Timeout.snap b/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/ActivityServerDiagnosticListenerTests.WebSocket_Subscription_Event_Should_Be_Error_When_Timeout.snap new file mode 100644 index 00000000000..5caa6a73a68 --- /dev/null +++ b/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/ActivityServerDiagnosticListenerTests.WebSocket_Subscription_Event_Should_Be_Error_When_Timeout.snap @@ -0,0 +1,270 @@ +{ + "source": { + "name": "HotChocolate.Diagnostics" + }, + "activities": [ + { + "OperationName": "Microsoft.AspNetCore.Hosting.HttpRequestIn", + "DisplayName": "GET /graphql/{**slug}", + "Kind": "Server", + "Status": "Unset", + "tags": [ + { + "Key": "server.address", + "Value": "localhost" + }, + { + "Key": "server.port", + "Value": 5000 + }, + { + "Key": "http.request.method", + "Value": "GET" + }, + { + "Key": "url.scheme", + "Value": "http" + }, + { + "Key": "url.path", + "Value": "/graphql" + }, + { + "Key": "network.protocol.version", + "Value": "1.1" + }, + { + "Key": "http.route", + "Value": "/graphql/{**slug}" + }, + { + "Key": "http.response.status_code", + "Value": 101 + } + ], + "event": [], + "activities": [ + { + "OperationName": "GraphQL Operation", + "DisplayName": "subscription", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "request" + }, + { + "Key": "server.address", + "Value": "localhost" + }, + { + "Key": "server.port", + "Value": 5000 + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnBlockingMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:41faa6dbd0de29d0cdf705c602b506bf" + } + ], + "event": [ + { + "Name": "AddedOperationToCache", + "Tags": [] + }, + { + "Name": "AddedDocumentToCache", + "Tags": [] + } + ], + "activities": [ + { + "OperationName": "GraphQL Document Validation", + "DisplayName": "GraphQL Document Validation", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "validate" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:41faa6dbd0de29d0cdf705c602b506bf" + } + ], + "event": [] + }, + { + "OperationName": "GraphQL Complexity Analysis", + "DisplayName": "GraphQL Complexity Analysis", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.document.hash", + "Value": "md5:41faa6dbd0de29d0cdf705c602b506bf" + }, + { + "Key": "graphql.operation.fieldCost", + "Value": 10.0 + }, + { + "Key": "graphql.operation.typeCost", + "Value": 1.0 + } + ], + "event": [] + }, + { + "OperationName": "GraphQL Operation Planning", + "DisplayName": "GraphQL Operation Planning", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "plan" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:41faa6dbd0de29d0cdf705c602b506bf" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnBlockingMessageSubscription" + } + ], + "event": [] + }, + { + "OperationName": "GraphQL Operation Execution", + "DisplayName": "GraphQL Operation Execution", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "execute" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnBlockingMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:41faa6dbd0de29d0cdf705c602b506bf" + } + ], + "event": [], + "activities": [ + { + "OperationName": "GraphQL Subscription Event", + "DisplayName": "GraphQL Subscription Event", + "Kind": "Internal", + "Status": "Error", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "subscription_event" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnBlockingMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:41faa6dbd0de29d0cdf705c602b506bf" + }, + { + "Key": "graphql.subscription.id", + "Value": "1" + }, + { + "Key": "error.type", + "Value": "System.OperationCanceledException" + } + ], + "event": [ + { + "Name": "exception", + "Tags": [ + { + "Key": "exception.message", + "Value": "The operation was canceled." + }, + { + "Key": "exception.stacktrace", + "Value": "System.OperationCanceledException: The operation was canceled.\n at System.Threading.CancellationToken.ThrowOperationCanceledException()\n at System.Threading.CancellationToken.ThrowIfCancellationRequested()\n at HotChocolate.Execution.Processing.WorkScheduler.ExecuteInternalAsync(IExecutionTask[] buffer) in WorkScheduler.Execute.cs\n at HotChocolate.Execution.Processing.WorkScheduler.ExecuteAsync1() in WorkScheduler.Execute.cs\n at HotChocolate.Execution.Processing.QueryExecutor.ExecuteNoIncrementalAsync(OperationContext operationContext, IImmutableDictionary`2 scopedContext) in QueryExecutor.cs\n at HotChocolate.Execution.Processing.SubscriptionExecutor.Subscription.OnEvent(Object payload) in SubscriptionExecutor.Subscription.cs" + }, + { + "Key": "exception.type", + "Value": "System.OperationCanceledException" + } + ] + } + ], + "activities": [ + { + "OperationName": "Subscription.onBlockingMessage", + "DisplayName": "Subscription.onBlockingMessage", + "Kind": "Internal", + "Status": "Unset", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "resolve" + }, + { + "Key": "graphql.field.alias", + "Value": "onBlockingMessage" + }, + { + "Key": "graphql.field.path", + "Value": "onBlockingMessage" + }, + { + "Key": "graphql.field.name", + "Value": "onBlockingMessage" + }, + { + "Key": "graphql.field.schema_coordinate", + "Value": "Subscription.onBlockingMessage" + }, + { + "Key": "graphql.field.parent_type", + "Value": "Subscription" + } + ], + "event": [] + } + ] + } + ] + } + ] + } + ] + } + ] +} diff --git a/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/ActivityServerDiagnosticListenerTests.WebSocket_Subscription_Event_Should_Be_Error_When_Timeout_NET11_0.snap b/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/ActivityServerDiagnosticListenerTests.WebSocket_Subscription_Event_Should_Be_Error_When_Timeout_NET11_0.snap new file mode 100644 index 00000000000..03fc8c10618 --- /dev/null +++ b/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/ActivityServerDiagnosticListenerTests.WebSocket_Subscription_Event_Should_Be_Error_When_Timeout_NET11_0.snap @@ -0,0 +1,270 @@ +{ + "source": { + "name": "HotChocolate.Diagnostics" + }, + "activities": [ + { + "OperationName": "Microsoft.AspNetCore.Hosting.HttpRequestIn", + "DisplayName": "GET /graphql/{**slug}", + "Kind": "Server", + "Status": "Unset", + "tags": [ + { + "Key": "server.address", + "Value": "localhost" + }, + { + "Key": "server.port", + "Value": 5000 + }, + { + "Key": "http.request.method", + "Value": "GET" + }, + { + "Key": "url.scheme", + "Value": "http" + }, + { + "Key": "url.path", + "Value": "/graphql" + }, + { + "Key": "network.protocol.version", + "Value": "1.1" + }, + { + "Key": "http.response.status_code", + "Value": 101 + }, + { + "Key": "http.route", + "Value": "/graphql/{**slug}" + } + ], + "event": [], + "activities": [ + { + "OperationName": "GraphQL Operation", + "DisplayName": "subscription", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "request" + }, + { + "Key": "server.address", + "Value": "localhost" + }, + { + "Key": "server.port", + "Value": 5000 + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnBlockingMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:41faa6dbd0de29d0cdf705c602b506bf" + } + ], + "event": [ + { + "Name": "AddedOperationToCache", + "Tags": [] + }, + { + "Name": "AddedDocumentToCache", + "Tags": [] + } + ], + "activities": [ + { + "OperationName": "GraphQL Document Validation", + "DisplayName": "GraphQL Document Validation", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "validate" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:41faa6dbd0de29d0cdf705c602b506bf" + } + ], + "event": [] + }, + { + "OperationName": "GraphQL Complexity Analysis", + "DisplayName": "GraphQL Complexity Analysis", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.document.hash", + "Value": "md5:41faa6dbd0de29d0cdf705c602b506bf" + }, + { + "Key": "graphql.operation.fieldCost", + "Value": 10.0 + }, + { + "Key": "graphql.operation.typeCost", + "Value": 1.0 + } + ], + "event": [] + }, + { + "OperationName": "GraphQL Operation Planning", + "DisplayName": "GraphQL Operation Planning", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "plan" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:41faa6dbd0de29d0cdf705c602b506bf" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnBlockingMessageSubscription" + } + ], + "event": [] + }, + { + "OperationName": "GraphQL Operation Execution", + "DisplayName": "GraphQL Operation Execution", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "execute" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnBlockingMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:41faa6dbd0de29d0cdf705c602b506bf" + } + ], + "event": [], + "activities": [ + { + "OperationName": "GraphQL Subscription Event", + "DisplayName": "GraphQL Subscription Event", + "Kind": "Internal", + "Status": "Error", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "subscription_event" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnBlockingMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:41faa6dbd0de29d0cdf705c602b506bf" + }, + { + "Key": "graphql.subscription.id", + "Value": "1" + }, + { + "Key": "error.type", + "Value": "System.OperationCanceledException" + } + ], + "event": [ + { + "Name": "exception", + "Tags": [ + { + "Key": "exception.message", + "Value": "The operation was canceled." + }, + { + "Key": "exception.stacktrace", + "Value": "System.OperationCanceledException: The operation was canceled.\n at System.Threading.CancellationToken.ThrowOperationCanceledException()\n at System.Threading.CancellationToken.ThrowIfCancellationRequested()\n at HotChocolate.Execution.Processing.WorkScheduler.ExecuteInternalAsync(IExecutionTask[] buffer) in WorkScheduler.Execute.cs\n at HotChocolate.Execution.Processing.WorkScheduler.ExecuteAsync1() in WorkScheduler.Execute.cs\n at HotChocolate.Execution.Processing.QueryExecutor.ExecuteNoIncrementalAsync(OperationContext operationContext, IImmutableDictionary`2 scopedContext) in QueryExecutor.cs\n at HotChocolate.Execution.Processing.SubscriptionExecutor.Subscription.OnEvent(Object payload) in SubscriptionExecutor.Subscription.cs" + }, + { + "Key": "exception.type", + "Value": "System.OperationCanceledException" + } + ] + } + ], + "activities": [ + { + "OperationName": "Subscription.onBlockingMessage", + "DisplayName": "Subscription.onBlockingMessage", + "Kind": "Internal", + "Status": "Unset", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "resolve" + }, + { + "Key": "graphql.field.alias", + "Value": "onBlockingMessage" + }, + { + "Key": "graphql.field.path", + "Value": "onBlockingMessage" + }, + { + "Key": "graphql.field.name", + "Value": "onBlockingMessage" + }, + { + "Key": "graphql.field.schema_coordinate", + "Value": "Subscription.onBlockingMessage" + }, + { + "Key": "graphql.field.parent_type", + "Value": "Subscription" + } + ], + "event": [] + } + ] + } + ] + } + ] + } + ] + } + ] +} diff --git a/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/ActivityServerDiagnosticListenerTests.WebSocket_Subscription_Should_Be_Ok_When_Client_Closes_Connection.snap b/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/ActivityServerDiagnosticListenerTests.WebSocket_Subscription_Should_Be_Ok_When_Client_Closes_Connection.snap new file mode 100644 index 00000000000..95bcd1d95d2 --- /dev/null +++ b/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/ActivityServerDiagnosticListenerTests.WebSocket_Subscription_Should_Be_Ok_When_Client_Closes_Connection.snap @@ -0,0 +1,248 @@ +{ + "source": { + "name": "HotChocolate.Diagnostics" + }, + "activities": [ + { + "OperationName": "Microsoft.AspNetCore.Hosting.HttpRequestIn", + "DisplayName": "GET /graphql/{**slug}", + "Kind": "Server", + "Status": "Unset", + "tags": [ + { + "Key": "server.address", + "Value": "localhost" + }, + { + "Key": "server.port", + "Value": 5000 + }, + { + "Key": "http.request.method", + "Value": "GET" + }, + { + "Key": "url.scheme", + "Value": "http" + }, + { + "Key": "url.path", + "Value": "/graphql" + }, + { + "Key": "network.protocol.version", + "Value": "1.1" + }, + { + "Key": "http.route", + "Value": "/graphql/{**slug}" + }, + { + "Key": "http.response.status_code", + "Value": 101 + } + ], + "event": [], + "activities": [ + { + "OperationName": "GraphQL Operation", + "DisplayName": "subscription", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "request" + }, + { + "Key": "server.address", + "Value": "localhost" + }, + { + "Key": "server.port", + "Value": 5000 + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + } + ], + "event": [ + { + "Name": "AddedOperationToCache", + "Tags": [] + }, + { + "Name": "AddedDocumentToCache", + "Tags": [] + } + ], + "activities": [ + { + "OperationName": "GraphQL Document Validation", + "DisplayName": "GraphQL Document Validation", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "validate" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + } + ], + "event": [] + }, + { + "OperationName": "GraphQL Complexity Analysis", + "DisplayName": "GraphQL Complexity Analysis", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + }, + { + "Key": "graphql.operation.fieldCost", + "Value": 0.0 + }, + { + "Key": "graphql.operation.typeCost", + "Value": 1.0 + } + ], + "event": [] + }, + { + "OperationName": "GraphQL Operation Planning", + "DisplayName": "GraphQL Operation Planning", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "plan" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + } + ], + "event": [] + }, + { + "OperationName": "GraphQL Operation Execution", + "DisplayName": "GraphQL Operation Execution", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "execute" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + } + ], + "event": [], + "activities": [ + { + "OperationName": "GraphQL Subscription Event", + "DisplayName": "GraphQL Subscription Event", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "subscription_event" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + }, + { + "Key": "graphql.subscription.id", + "Value": "1" + } + ], + "event": [], + "activities": [ + { + "OperationName": "Subscription.onMessage", + "DisplayName": "Subscription.onMessage", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "resolve" + }, + { + "Key": "graphql.field.alias", + "Value": "onMessage" + }, + { + "Key": "graphql.field.path", + "Value": "onMessage" + }, + { + "Key": "graphql.field.name", + "Value": "onMessage" + }, + { + "Key": "graphql.field.schema_coordinate", + "Value": "Subscription.onMessage" + }, + { + "Key": "graphql.field.parent_type", + "Value": "Subscription" + } + ], + "event": [] + } + ] + } + ] + } + ] + } + ] + } + ] +} diff --git a/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/ActivityServerDiagnosticListenerTests.WebSocket_Subscription_Should_Be_Ok_When_Client_Closes_Connection_NET11_0.snap b/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/ActivityServerDiagnosticListenerTests.WebSocket_Subscription_Should_Be_Ok_When_Client_Closes_Connection_NET11_0.snap new file mode 100644 index 00000000000..cd7dc12365c --- /dev/null +++ b/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/ActivityServerDiagnosticListenerTests.WebSocket_Subscription_Should_Be_Ok_When_Client_Closes_Connection_NET11_0.snap @@ -0,0 +1,248 @@ +{ + "source": { + "name": "HotChocolate.Diagnostics" + }, + "activities": [ + { + "OperationName": "Microsoft.AspNetCore.Hosting.HttpRequestIn", + "DisplayName": "GET /graphql/{**slug}", + "Kind": "Server", + "Status": "Unset", + "tags": [ + { + "Key": "server.address", + "Value": "localhost" + }, + { + "Key": "server.port", + "Value": 5000 + }, + { + "Key": "http.request.method", + "Value": "GET" + }, + { + "Key": "url.scheme", + "Value": "http" + }, + { + "Key": "url.path", + "Value": "/graphql" + }, + { + "Key": "network.protocol.version", + "Value": "1.1" + }, + { + "Key": "http.response.status_code", + "Value": 101 + }, + { + "Key": "http.route", + "Value": "/graphql/{**slug}" + } + ], + "event": [], + "activities": [ + { + "OperationName": "GraphQL Operation", + "DisplayName": "subscription", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "request" + }, + { + "Key": "server.address", + "Value": "localhost" + }, + { + "Key": "server.port", + "Value": 5000 + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + } + ], + "event": [ + { + "Name": "AddedOperationToCache", + "Tags": [] + }, + { + "Name": "AddedDocumentToCache", + "Tags": [] + } + ], + "activities": [ + { + "OperationName": "GraphQL Document Validation", + "DisplayName": "GraphQL Document Validation", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "validate" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + } + ], + "event": [] + }, + { + "OperationName": "GraphQL Complexity Analysis", + "DisplayName": "GraphQL Complexity Analysis", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + }, + { + "Key": "graphql.operation.fieldCost", + "Value": 0.0 + }, + { + "Key": "graphql.operation.typeCost", + "Value": 1.0 + } + ], + "event": [] + }, + { + "OperationName": "GraphQL Operation Planning", + "DisplayName": "GraphQL Operation Planning", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "plan" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + } + ], + "event": [] + }, + { + "OperationName": "GraphQL Operation Execution", + "DisplayName": "GraphQL Operation Execution", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "execute" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + } + ], + "event": [], + "activities": [ + { + "OperationName": "GraphQL Subscription Event", + "DisplayName": "GraphQL Subscription Event", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "subscription_event" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + }, + { + "Key": "graphql.subscription.id", + "Value": "1" + } + ], + "event": [], + "activities": [ + { + "OperationName": "Subscription.onMessage", + "DisplayName": "Subscription.onMessage", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "resolve" + }, + { + "Key": "graphql.field.alias", + "Value": "onMessage" + }, + { + "Key": "graphql.field.path", + "Value": "onMessage" + }, + { + "Key": "graphql.field.name", + "Value": "onMessage" + }, + { + "Key": "graphql.field.schema_coordinate", + "Value": "Subscription.onMessage" + }, + { + "Key": "graphql.field.parent_type", + "Value": "Subscription" + } + ], + "event": [] + } + ] + } + ] + } + ] + } + ] + } + ] +} diff --git a/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/ActivityServerDiagnosticListenerTests.WebSocket_Subscription_Should_Be_Ok_When_Server_Completes.snap b/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/ActivityServerDiagnosticListenerTests.WebSocket_Subscription_Should_Be_Ok_When_Server_Completes.snap new file mode 100644 index 00000000000..95bcd1d95d2 --- /dev/null +++ b/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/ActivityServerDiagnosticListenerTests.WebSocket_Subscription_Should_Be_Ok_When_Server_Completes.snap @@ -0,0 +1,248 @@ +{ + "source": { + "name": "HotChocolate.Diagnostics" + }, + "activities": [ + { + "OperationName": "Microsoft.AspNetCore.Hosting.HttpRequestIn", + "DisplayName": "GET /graphql/{**slug}", + "Kind": "Server", + "Status": "Unset", + "tags": [ + { + "Key": "server.address", + "Value": "localhost" + }, + { + "Key": "server.port", + "Value": 5000 + }, + { + "Key": "http.request.method", + "Value": "GET" + }, + { + "Key": "url.scheme", + "Value": "http" + }, + { + "Key": "url.path", + "Value": "/graphql" + }, + { + "Key": "network.protocol.version", + "Value": "1.1" + }, + { + "Key": "http.route", + "Value": "/graphql/{**slug}" + }, + { + "Key": "http.response.status_code", + "Value": 101 + } + ], + "event": [], + "activities": [ + { + "OperationName": "GraphQL Operation", + "DisplayName": "subscription", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "request" + }, + { + "Key": "server.address", + "Value": "localhost" + }, + { + "Key": "server.port", + "Value": 5000 + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + } + ], + "event": [ + { + "Name": "AddedOperationToCache", + "Tags": [] + }, + { + "Name": "AddedDocumentToCache", + "Tags": [] + } + ], + "activities": [ + { + "OperationName": "GraphQL Document Validation", + "DisplayName": "GraphQL Document Validation", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "validate" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + } + ], + "event": [] + }, + { + "OperationName": "GraphQL Complexity Analysis", + "DisplayName": "GraphQL Complexity Analysis", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + }, + { + "Key": "graphql.operation.fieldCost", + "Value": 0.0 + }, + { + "Key": "graphql.operation.typeCost", + "Value": 1.0 + } + ], + "event": [] + }, + { + "OperationName": "GraphQL Operation Planning", + "DisplayName": "GraphQL Operation Planning", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "plan" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + } + ], + "event": [] + }, + { + "OperationName": "GraphQL Operation Execution", + "DisplayName": "GraphQL Operation Execution", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "execute" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + } + ], + "event": [], + "activities": [ + { + "OperationName": "GraphQL Subscription Event", + "DisplayName": "GraphQL Subscription Event", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "subscription_event" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + }, + { + "Key": "graphql.subscription.id", + "Value": "1" + } + ], + "event": [], + "activities": [ + { + "OperationName": "Subscription.onMessage", + "DisplayName": "Subscription.onMessage", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "resolve" + }, + { + "Key": "graphql.field.alias", + "Value": "onMessage" + }, + { + "Key": "graphql.field.path", + "Value": "onMessage" + }, + { + "Key": "graphql.field.name", + "Value": "onMessage" + }, + { + "Key": "graphql.field.schema_coordinate", + "Value": "Subscription.onMessage" + }, + { + "Key": "graphql.field.parent_type", + "Value": "Subscription" + } + ], + "event": [] + } + ] + } + ] + } + ] + } + ] + } + ] +} diff --git a/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/ActivityServerDiagnosticListenerTests.WebSocket_Subscription_Should_Be_Ok_When_Server_Completes_Default.snap b/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/ActivityServerDiagnosticListenerTests.WebSocket_Subscription_Should_Be_Ok_When_Server_Completes_Default.snap new file mode 100644 index 00000000000..a360db43ff4 --- /dev/null +++ b/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/ActivityServerDiagnosticListenerTests.WebSocket_Subscription_Should_Be_Ok_When_Server_Completes_Default.snap @@ -0,0 +1,200 @@ +{ + "source": { + "name": "HotChocolate.Diagnostics" + }, + "activities": [ + { + "OperationName": "Microsoft.AspNetCore.Hosting.HttpRequestIn", + "DisplayName": "GET /graphql/{**slug}", + "Kind": "Server", + "Status": "Unset", + "tags": [ + { + "Key": "server.address", + "Value": "localhost" + }, + { + "Key": "server.port", + "Value": 5000 + }, + { + "Key": "http.request.method", + "Value": "GET" + }, + { + "Key": "url.scheme", + "Value": "http" + }, + { + "Key": "url.path", + "Value": "/graphql" + }, + { + "Key": "network.protocol.version", + "Value": "1.1" + }, + { + "Key": "http.route", + "Value": "/graphql/{**slug}" + }, + { + "Key": "http.response.status_code", + "Value": 101 + } + ], + "event": [], + "activities": [ + { + "OperationName": "GraphQL Operation", + "DisplayName": "subscription", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "request" + }, + { + "Key": "server.address", + "Value": "localhost" + }, + { + "Key": "server.port", + "Value": 5000 + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + } + ], + "event": [ + { + "Name": "AddedOperationToCache", + "Tags": [] + }, + { + "Name": "AddedDocumentToCache", + "Tags": [] + } + ], + "activities": [ + { + "OperationName": "GraphQL Document Validation", + "DisplayName": "GraphQL Document Validation", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "validate" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + } + ], + "event": [] + }, + { + "OperationName": "GraphQL Operation Planning", + "DisplayName": "GraphQL Operation Planning", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "plan" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + } + ], + "event": [] + }, + { + "OperationName": "GraphQL Subscription Event", + "DisplayName": "GraphQL Subscription Event", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "subscription_event" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + }, + { + "Key": "graphql.subscription.id", + "Value": "1" + } + ], + "event": [], + "activities": [ + { + "OperationName": "Subscription.onMessage", + "DisplayName": "Subscription.onMessage", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "resolve" + }, + { + "Key": "graphql.field.alias", + "Value": "onMessage" + }, + { + "Key": "graphql.field.path", + "Value": "onMessage" + }, + { + "Key": "graphql.field.name", + "Value": "onMessage" + }, + { + "Key": "graphql.field.schema_coordinate", + "Value": "Subscription.onMessage" + }, + { + "Key": "graphql.field.parent_type", + "Value": "Subscription" + } + ], + "event": [] + } + ] + } + ] + } + ] + } + ] +} diff --git a/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/ActivityServerDiagnosticListenerTests.WebSocket_Subscription_Should_Be_Ok_When_Server_Completes_Default_NET11_0.snap b/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/ActivityServerDiagnosticListenerTests.WebSocket_Subscription_Should_Be_Ok_When_Server_Completes_Default_NET11_0.snap new file mode 100644 index 00000000000..a5a0a47fc94 --- /dev/null +++ b/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/ActivityServerDiagnosticListenerTests.WebSocket_Subscription_Should_Be_Ok_When_Server_Completes_Default_NET11_0.snap @@ -0,0 +1,200 @@ +{ + "source": { + "name": "HotChocolate.Diagnostics" + }, + "activities": [ + { + "OperationName": "Microsoft.AspNetCore.Hosting.HttpRequestIn", + "DisplayName": "GET /graphql/{**slug}", + "Kind": "Server", + "Status": "Unset", + "tags": [ + { + "Key": "server.address", + "Value": "localhost" + }, + { + "Key": "server.port", + "Value": 5000 + }, + { + "Key": "http.request.method", + "Value": "GET" + }, + { + "Key": "url.scheme", + "Value": "http" + }, + { + "Key": "url.path", + "Value": "/graphql" + }, + { + "Key": "network.protocol.version", + "Value": "1.1" + }, + { + "Key": "http.response.status_code", + "Value": 101 + }, + { + "Key": "http.route", + "Value": "/graphql/{**slug}" + } + ], + "event": [], + "activities": [ + { + "OperationName": "GraphQL Operation", + "DisplayName": "subscription", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "request" + }, + { + "Key": "server.address", + "Value": "localhost" + }, + { + "Key": "server.port", + "Value": 5000 + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + } + ], + "event": [ + { + "Name": "AddedOperationToCache", + "Tags": [] + }, + { + "Name": "AddedDocumentToCache", + "Tags": [] + } + ], + "activities": [ + { + "OperationName": "GraphQL Document Validation", + "DisplayName": "GraphQL Document Validation", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "validate" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + } + ], + "event": [] + }, + { + "OperationName": "GraphQL Operation Planning", + "DisplayName": "GraphQL Operation Planning", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "plan" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + } + ], + "event": [] + }, + { + "OperationName": "GraphQL Subscription Event", + "DisplayName": "GraphQL Subscription Event", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "subscription_event" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + }, + { + "Key": "graphql.subscription.id", + "Value": "1" + } + ], + "event": [], + "activities": [ + { + "OperationName": "Subscription.onMessage", + "DisplayName": "Subscription.onMessage", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "resolve" + }, + { + "Key": "graphql.field.alias", + "Value": "onMessage" + }, + { + "Key": "graphql.field.path", + "Value": "onMessage" + }, + { + "Key": "graphql.field.name", + "Value": "onMessage" + }, + { + "Key": "graphql.field.schema_coordinate", + "Value": "Subscription.onMessage" + }, + { + "Key": "graphql.field.parent_type", + "Value": "Subscription" + } + ], + "event": [] + } + ] + } + ] + } + ] + } + ] +} diff --git a/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/ActivityServerDiagnosticListenerTests.WebSocket_Subscription_Should_Be_Ok_When_Server_Completes_NET11_0.snap b/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/ActivityServerDiagnosticListenerTests.WebSocket_Subscription_Should_Be_Ok_When_Server_Completes_NET11_0.snap new file mode 100644 index 00000000000..cd7dc12365c --- /dev/null +++ b/src/HotChocolate/Diagnostics/test/Diagnostics.Tests/__snapshots__/ActivityServerDiagnosticListenerTests.WebSocket_Subscription_Should_Be_Ok_When_Server_Completes_NET11_0.snap @@ -0,0 +1,248 @@ +{ + "source": { + "name": "HotChocolate.Diagnostics" + }, + "activities": [ + { + "OperationName": "Microsoft.AspNetCore.Hosting.HttpRequestIn", + "DisplayName": "GET /graphql/{**slug}", + "Kind": "Server", + "Status": "Unset", + "tags": [ + { + "Key": "server.address", + "Value": "localhost" + }, + { + "Key": "server.port", + "Value": 5000 + }, + { + "Key": "http.request.method", + "Value": "GET" + }, + { + "Key": "url.scheme", + "Value": "http" + }, + { + "Key": "url.path", + "Value": "/graphql" + }, + { + "Key": "network.protocol.version", + "Value": "1.1" + }, + { + "Key": "http.response.status_code", + "Value": 101 + }, + { + "Key": "http.route", + "Value": "/graphql/{**slug}" + } + ], + "event": [], + "activities": [ + { + "OperationName": "GraphQL Operation", + "DisplayName": "subscription", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "request" + }, + { + "Key": "server.address", + "Value": "localhost" + }, + { + "Key": "server.port", + "Value": 5000 + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + } + ], + "event": [ + { + "Name": "AddedOperationToCache", + "Tags": [] + }, + { + "Name": "AddedDocumentToCache", + "Tags": [] + } + ], + "activities": [ + { + "OperationName": "GraphQL Document Validation", + "DisplayName": "GraphQL Document Validation", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "validate" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + } + ], + "event": [] + }, + { + "OperationName": "GraphQL Complexity Analysis", + "DisplayName": "GraphQL Complexity Analysis", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + }, + { + "Key": "graphql.operation.fieldCost", + "Value": 0.0 + }, + { + "Key": "graphql.operation.typeCost", + "Value": 1.0 + } + ], + "event": [] + }, + { + "OperationName": "GraphQL Operation Planning", + "DisplayName": "GraphQL Operation Planning", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "plan" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + } + ], + "event": [] + }, + { + "OperationName": "GraphQL Operation Execution", + "DisplayName": "GraphQL Operation Execution", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "execute" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + } + ], + "event": [], + "activities": [ + { + "OperationName": "GraphQL Subscription Event", + "DisplayName": "GraphQL Subscription Event", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "subscription_event" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + }, + { + "Key": "graphql.subscription.id", + "Value": "1" + } + ], + "event": [], + "activities": [ + { + "OperationName": "Subscription.onMessage", + "DisplayName": "Subscription.onMessage", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "resolve" + }, + { + "Key": "graphql.field.alias", + "Value": "onMessage" + }, + { + "Key": "graphql.field.path", + "Value": "onMessage" + }, + { + "Key": "graphql.field.name", + "Value": "onMessage" + }, + { + "Key": "graphql.field.schema_coordinate", + "Value": "Subscription.onMessage" + }, + { + "Key": "graphql.field.parent_type", + "Value": "Subscription" + } + ], + "event": [] + } + ] + } + ] + } + ] + } + ] + } + ] +} diff --git a/src/HotChocolate/Fusion/src/Fusion.AspNetCore/DependencyInjection/FusionServerServiceCollectionExtensions.cs b/src/HotChocolate/Fusion/src/Fusion.AspNetCore/DependencyInjection/FusionServerServiceCollectionExtensions.cs index db4f19a125f..644c1374500 100644 --- a/src/HotChocolate/Fusion/src/Fusion.AspNetCore/DependencyInjection/FusionServerServiceCollectionExtensions.cs +++ b/src/HotChocolate/Fusion/src/Fusion.AspNetCore/DependencyInjection/FusionServerServiceCollectionExtensions.cs @@ -123,7 +123,8 @@ private static IFusionGatewayBuilder AddApolloProtocol( (_, s) => s.AddSingleton( sp => new ApolloSubscriptionProtocolHandler( sp.GetRequiredService(), - sp.GetRequiredService()))); + sp.GetRequiredService(), + sp.GetRequiredService()))); private static IFusionGatewayBuilder AddGraphQLOverWebSocketProtocol( this IFusionGatewayBuilder builder) @@ -134,5 +135,6 @@ private static IFusionGatewayBuilder AddGraphQLOverWebSocketProtocol( sp.GetRequiredService(), sp.GetRequiredService(), sp.GetRequiredService(), - sp.GetRequiredService()))); + sp.GetRequiredService(), + sp.GetRequiredService()))); } diff --git a/src/HotChocolate/Fusion/src/Fusion.Diagnostics/Listeners/FusionActivityExecutionDiagnosticEventListener.cs b/src/HotChocolate/Fusion/src/Fusion.Diagnostics/Listeners/FusionActivityExecutionDiagnosticEventListener.cs index 15ed77be47f..3c8e42d88b7 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Diagnostics/Listeners/FusionActivityExecutionDiagnosticEventListener.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Diagnostics/Listeners/FusionActivityExecutionDiagnosticEventListener.cs @@ -21,15 +21,18 @@ public override IDisposable ExecuteRequest(RequestContext context) if (options.SkipExecuteRequest) { - if (!options.SkipExecuteHttpRequest - && context.Features.TryGet(out var httpContext) - && httpContext.Features.Get() is { } httpRequestSpan) + if (options.SkipExecuteHttpRequest + || !context.Features.TryGet(out var httpContext)) { - httpContextActivity = httpRequestSpan.Activity; + return EmptyScope; } - else + + // A GraphQL over HTTP request reuses the transport span as the root request + // span instead of emitting a second one. A transport that has no span of its + // own, like a WebSocket session, falls through to a dedicated request span. + if (httpContext.Features.Get() is { } httpRequestSpan) { - return EmptyScope; + httpContextActivity = httpRequestSpan.Activity; } } diff --git a/src/HotChocolate/Fusion/src/Fusion.Diagnostics/Listeners/FusionActivityServerDiagnosticListener.cs b/src/HotChocolate/Fusion/src/Fusion.Diagnostics/Listeners/FusionActivityServerDiagnosticListener.cs index 2bfb286ed56..6ed4d2e2ce3 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Diagnostics/Listeners/FusionActivityServerDiagnosticListener.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Diagnostics/Listeners/FusionActivityServerDiagnosticListener.cs @@ -1,5 +1,7 @@ using Microsoft.AspNetCore.Http; using HotChocolate.AspNetCore.Instrumentation; +using HotChocolate.AspNetCore.Subscriptions; +using HotChocolate.AspNetCore.Subscriptions.Protocols; using HotChocolate.Diagnostics; using HotChocolate.Execution; using HotChocolate.Language; @@ -104,6 +106,11 @@ public override void ParserErrors(HttpContext context, IReadOnlyList err } } + public override void WebSocketConnectionInitialized( + ISocketSession session, + IOperationMessagePayload connectionInitMessage) + => enricher.OnWebSocketConnectionInitialized(session, connectionInitMessage); + public override IDisposable FormatHttpResponse(HttpContext context, OperationResult result) { if (options.SkipFormatHttpResponse) diff --git a/src/HotChocolate/Fusion/test/Fusion.Diagnostics.Tests/FusionActivityServerDiagnosticListenerTests.cs b/src/HotChocolate/Fusion/test/Fusion.Diagnostics.Tests/FusionActivityServerDiagnosticListenerTests.cs index b63be71e359..5a087f29163 100644 --- a/src/HotChocolate/Fusion/test/Fusion.Diagnostics.Tests/FusionActivityServerDiagnosticListenerTests.cs +++ b/src/HotChocolate/Fusion/test/Fusion.Diagnostics.Tests/FusionActivityServerDiagnosticListenerTests.cs @@ -1,15 +1,25 @@ +using System.Diagnostics; +using System.Net.WebSockets; using System.Runtime.CompilerServices; using System.Text; +using System.Text.Json; +using HotChocolate.AspNetCore.Subscriptions; +using HotChocolate.AspNetCore.Subscriptions.Protocols; using HotChocolate.Diagnostics; using HotChocolate.Execution; +using HotChocolate.Fusion.Execution; +using HotChocolate.Fusion.Execution.Nodes; using HotChocolate.Language; using HotChocolate.PersistedOperations; using HotChocolate.Resolvers; using HotChocolate.Transport.Http; +using HotChocolate.Transport.Sockets; +using HotChocolate.Transport.Sockets.Client; using HotChocolate.Types; using HotChocolate.Types.Composite; using HotChocolate.Types.Relay; using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using static CookieCrumble.TestEnvironment; using static HotChocolate.Fusion.Diagnostics.ActivityTestHelper; @@ -21,6 +31,7 @@ namespace HotChocolate.Fusion.Diagnostics; public class FusionActivityServerDiagnosticListenerTests : FusionTestBase { private static readonly Uri s_url = new("http://localhost:5000/graphql"); + private static readonly Uri s_webSocketUrl = new("ws://localhost:5000/graphql"); [Fact] public async Task Http_Post_Single_Request_Default() @@ -748,6 +759,484 @@ public async Task Http_Subscription_Should_Be_Unset_When_Client_Disconnects_Duri } } + [Fact] + public async Task WebSocket_Subscription_Should_Be_Ok_When_Server_Completes_Default() + { + using var guard = new CancellationTokenSource(TimeSpan.FromSeconds(10)); + + using (CaptureActivities(out var activities)) + { + // arrange + using var server1 = CreateSourceSchema( + "a", + b => b + .AddQueryType() + .AddSubscriptionType()); + + using var gateway = await CreateCompositeSchemaAsync( + [ + ("a", server1) + ], + configureGatewayBuilder: b => b.AddInstrumentation()); + + using var webSocket = await ConnectWebSocketAsync(gateway, guard.Token); + await using var client = await SocketClient.ConnectAsync(webSocket, guard.Token); + + var request = new OperationRequest("subscription OnMessageSubscription { onMessage }"); + + using var result = await client.ExecuteAsync(request, guard.Token); + var results = result.ReadResultsAsync().GetAsyncEnumerator(guard.Token); + + // act + // the subgraph emits one event then completes its stream + try + { + Assert.True(await results.MoveNextAsync()); + Assert.False(await results.MoveNextAsync()); + } + finally + { + await IgnoreSocketTeardownAsync(results.DisposeAsync().AsTask()); + } + + // the session encloses every span, so close it before the trace is read + await CloseWebSocketAsync(webSocket, guard.Token); + + // assert + activities.MatchSnapshot(Postfix([NET11_0])); + } + } + + [Fact] + public async Task WebSocket_Subscription_Should_Be_Ok_When_Server_Completes() + { + using var guard = new CancellationTokenSource(TimeSpan.FromSeconds(10)); + + using (CaptureActivities(out var activities)) + { + // arrange + using var server1 = CreateSourceSchema( + "a", + b => b + .AddQueryType() + .AddSubscriptionType()); + + using var gateway = await CreateCompositeSchemaAsync( + [ + ("a", server1) + ], + configureGatewayBuilder: b => b.AddInstrumentation(o => + o.Scopes = FusionActivityScopes.All)); + + using var webSocket = await ConnectWebSocketAsync(gateway, guard.Token); + await using var client = await SocketClient.ConnectAsync(webSocket, guard.Token); + + var request = new OperationRequest("subscription OnMessageSubscription { onMessage }"); + + using var result = await client.ExecuteAsync(request, guard.Token); + var results = result.ReadResultsAsync().GetAsyncEnumerator(guard.Token); + + // act + // the subgraph emits one event then completes its stream + try + { + Assert.True(await results.MoveNextAsync()); + Assert.False(await results.MoveNextAsync()); + } + finally + { + await IgnoreSocketTeardownAsync(results.DisposeAsync().AsTask()); + } + + // the session encloses every span, so close it before the trace is read + await CloseWebSocketAsync(webSocket, guard.Token); + + // assert + activities.MatchSnapshot(Postfix([NET11_0])); + } + } + + [Fact] + public async Task WebSocket_Subscription_Should_Be_Ok_When_Client_Closes_Connection() + { + using var guard = new CancellationTokenSource(TimeSpan.FromSeconds(10)); + + using (CaptureActivities(out var activities)) + { + // arrange + using var server1 = CreateSourceSchema( + "a", + b => b + .AddQueryType() + .AddSubscriptionType()); + + using var gateway = await CreateCompositeSchemaAsync( + [ + ("a", server1) + ], + configureGatewayBuilder: b => b.AddInstrumentation(o => + o.Scopes = FusionActivityScopes.All)); + + using var webSocket = await ConnectWebSocketAsync(gateway, guard.Token); + await using var client = await SocketClient.ConnectAsync(webSocket, guard.Token); + + var request = new OperationRequest("subscription OnIdleMessageSubscription { onIdleMessage }"); + + using var result = await client.ExecuteAsync(request, guard.Token); + var results = result.ReadResultsAsync().GetAsyncEnumerator(guard.Token); + + Assert.True(await results.MoveNextAsync()); + + // act + // close the connection without unsubscribing, so the gateway has to tear the + // still-running subscription down with the session + await CloseWebSocketAsync(webSocket, guard.Token); + await IgnoreSocketTeardownAsync(results.DisposeAsync().AsTask()); + + // assert + activities.MatchSnapshot(Postfix([NET11_0])); + } + } + + [Fact] + public async Task WebSocket_Header_Should_Be_Added_As_Tag_To_Request_And_Event_Spans() + { + using var guard = new CancellationTokenSource(TimeSpan.FromSeconds(10)); + + using (CaptureActivities(out var activities)) + { + // arrange + // non-browser clients can send the tenant as a handshake header instead + using var server1 = CreateSourceSchema( + "a", + b => b + .AddQueryType() + .AddSubscriptionType()); + + using var gateway = await CreateCompositeSchemaAsync( + [ + ("a", server1) + ], + configureGatewayBuilder: b => b + .AddInstrumentation(o => o.Scopes = FusionActivityScopes.All) + .AddApplicationService() + .Services.AddSingleton()); + + using var webSocket = await ConnectWebSocketAsync( + gateway, + guard.Token, + r => r.Headers[TenantHeaderName] = "acme-42"); + await using var client = await SocketClient.ConnectAsync(webSocket, guard.Token); + + var request = new OperationRequest("subscription OnMessageSubscription { onMessage }"); + + using var result = await client.ExecuteAsync(request, guard.Token); + var results = result.ReadResultsAsync().GetAsyncEnumerator(guard.Token); + + // act + // the subgraph emits one event then completes its stream + try + { + Assert.True(await results.MoveNextAsync()); + Assert.False(await results.MoveNextAsync()); + } + finally + { + await IgnoreSocketTeardownAsync(results.DisposeAsync().AsTask()); + } + + await CloseWebSocketAsync(webSocket, guard.Token); + + // assert + activities.MatchSnapshot(Postfix([NET11_0])); + } + } + + [Fact] + public async Task WebSocket_ConnectionInit_Payload_Should_Be_Added_As_Tag_To_Request_And_Event_Spans() + { + using var guard = new CancellationTokenSource(TimeSpan.FromSeconds(10)); + + using (CaptureActivities(out var activities)) + { + // arrange + using var server1 = CreateSourceSchema( + "a", + b => b + .AddQueryType() + .AddSubscriptionType()); + + using var gateway = await CreateCompositeSchemaAsync( + [ + ("a", server1) + ], + configureGatewayBuilder: b => b + .AddInstrumentation(o => o.Scopes = FusionActivityScopes.All) + .AddApplicationService() + .Services.AddSingleton()); + + using var webSocket = await ConnectWebSocketAsync(gateway, guard.Token); + var payload = JsonSerializer.SerializeToElement(new { tenant = "acme-42" }); + await using var client = await SocketClient.ConnectAsync(webSocket, payload, guard.Token); + + var request = new OperationRequest("subscription OnMessageSubscription { onMessage }"); + + using var result = await client.ExecuteAsync(request, guard.Token); + var results = result.ReadResultsAsync().GetAsyncEnumerator(guard.Token); + + // act + // the subgraph emits one event then completes its stream + try + { + Assert.True(await results.MoveNextAsync()); + Assert.False(await results.MoveNextAsync()); + } + finally + { + await IgnoreSocketTeardownAsync(results.DisposeAsync().AsTask()); + } + + await CloseWebSocketAsync(webSocket, guard.Token); + + // assert + activities.MatchSnapshot(Postfix([NET11_0])); + } + } + + private const string TenantHeaderName = "X-Tenant"; + + public sealed record TenantFeature(string Tenant); + + public sealed class TenantActivityEnricher(InstrumentationOptions options) + : FusionActivityEnricher(options) + { + /// + /// Stores the tenant on the connection so it outlives the connection + /// initialization message, which is only valid during this call. + /// + public override void OnWebSocketConnectionInitialized( + ISocketSession session, + IOperationMessagePayload connectionInitMessage) + { + if (connectionInitMessage.Payload is { ValueKind: JsonValueKind.Object } payload + && payload.TryGetProperty("tenant", out var tenant) + && tenant.ValueKind is JsonValueKind.String) + { + session.Connection.Features.Set(new TenantFeature(tenant.GetString()!)); + } + } + + public override void EnrichExecuteRequest(RequestContext context, Activity activity) + { + base.EnrichExecuteRequest(context, activity); + SetTenantTag(context, activity); + } + + public override void EnrichOnSubscriptionEvent( + OperationPlanContext context, + ExecutionNode node, + string schemaName, + ulong subscriptionId, + Activity activity) + { + base.EnrichOnSubscriptionEvent(context, node, schemaName, subscriptionId, activity); + SetTenantTag(context.RequestContext, activity); + } + + private static void SetTenantTag(RequestContext context, Activity activity) + { + if (ResolveTenant(context) is { } tenant) + { + activity.SetTag("test.tenant", tenant); + } + } + + private static string? ResolveTenant(RequestContext context) + { + if (context.ContextData.TryGetValue(nameof(ISocketSession), out var value) + && value is ISocketSession session + && session.Connection.Features.Get() is { } tenant) + { + return tenant.Tenant; + } + + if (context.Features.Get() is { } httpContext + && httpContext.Request.Headers.TryGetValue(TenantHeaderName, out var header)) + { + return header.ToString(); + } + + return null; + } + } + + [Fact] + public async Task WebSocket_Apollo_ConnectionInit_Payload_Should_Be_Added_As_Tag_To_Request_And_Event_Spans() + { + using var guard = new CancellationTokenSource(TimeSpan.FromSeconds(10)); + + using (CaptureActivities(out var activities)) + { + // arrange + // the legacy apollo protocol raises the event from its own protocol handler + using var server1 = CreateSourceSchema( + "a", + b => b + .AddQueryType() + .AddSubscriptionType()); + + using var gateway = await CreateCompositeSchemaAsync( + [ + ("a", server1) + ], + configureGatewayBuilder: b => b + .AddInstrumentation(o => o.Scopes = FusionActivityScopes.All) + .AddApplicationService() + .Services.AddSingleton()); + + var webSocketClient = gateway.CreateWebSocketClient(); + webSocketClient.ConfigureRequest = + r => r.Headers.SecWebSocketProtocol = WellKnownProtocols.GraphQL_WS; + using var webSocket = await webSocketClient.ConnectAsync(s_webSocketUrl, guard.Token); + + await SendApolloMessageAsync( + webSocket, + """{"type":"connection_init","payload":{"tenant":"acme-42"}}""", + guard.Token); + Assert.NotNull(await WaitForApolloMessageAsync(webSocket, "connection_ack", guard.Token)); + + // act + // the subgraph emits one event then completes its stream + await SendApolloMessageAsync( + webSocket, + """ + {"type":"start","id":"1","payload":{"query":"subscription OnMessageSubscription { onMessage }"}} + """, + guard.Token); + Assert.NotNull(await WaitForApolloMessageAsync(webSocket, "data", guard.Token)); + Assert.NotNull(await WaitForApolloMessageAsync(webSocket, "complete", guard.Token)); + + await CloseWebSocketAsync(webSocket, guard.Token); + + // assert + activities.MatchSnapshot(Postfix([NET11_0])); + } + } + + private static async Task SendApolloMessageAsync( + WebSocket webSocket, + string message, + CancellationToken cancellationToken) + => await webSocket.SendAsync( + Encoding.UTF8.GetBytes(message), + WebSocketMessageType.Text, + true, + cancellationToken); + + private static async Task WaitForApolloMessageAsync( + WebSocket webSocket, + string type, + CancellationToken cancellationToken) + { + using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(5)); + using var combined = + CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeout.Token); + var buffer = new byte[4096]; + + try + { + while (!combined.Token.IsCancellationRequested) + { + await using var stream = new MemoryStream(); + WebSocketReceiveResult result; + + do + { + result = await webSocket.ReceiveAsync( + new ArraySegment(buffer), + combined.Token); + stream.Write(buffer, 0, result.Count); + } + while (!result.EndOfMessage); + + if (stream.Length == 0) + { + continue; + } + + var message = JsonDocument.Parse(stream.ToArray()); + + if (message.RootElement.GetProperty("type").GetString() == type) + { + return message; + } + + message.Dispose(); + } + } + catch (OperationCanceledException) + { + // expected: no matching message arrived within the timeout + } + + return null; + } + + private static async Task ConnectWebSocketAsync( + Gateway gateway, + CancellationToken cancellationToken, + Action? configureRequest = null) + { + var webSocketClient = gateway.CreateWebSocketClient(); + webSocketClient.ConfigureRequest = r => + { + r.Headers.SecWebSocketProtocol = WellKnownProtocols.GraphQL_Transport_WS; + configureRequest?.Invoke(r); + }; + return await webSocketClient.ConnectAsync(s_webSocketUrl, cancellationToken); + } + + private static async Task CloseWebSocketAsync( + WebSocket webSocket, + CancellationToken cancellationToken) + { + try + { + await webSocket.CloseOutputAsync( + WebSocketCloseStatus.NormalClosure, + "done", + cancellationToken); + } + catch (Exception ex) when (ex is WebSocketException or IOException or ObjectDisposedException) + { + // the gateway can end the session before the close frame is sent + } + } + + private static async Task IgnoreSocketTeardownAsync(Task task) + { + try + { + await task; + } + catch (OperationCanceledException) + { + // expected: the streamed read was aborted by the client + } + catch (IOException) + { + // expected: aborting an in-flight read can surface as an I/O failure + } + catch (WebSocketException) + { + // expected: the socket was closed while a read was in flight + } + catch (SocketClosedException) + { + // expected: the client dropped the connection without a close frame + } + } + private static async Task IgnoreCancellationAsync(Task task) { try diff --git a/src/HotChocolate/Fusion/test/Fusion.Diagnostics.Tests/__snapshots__/FusionActivityServerDiagnosticListenerTests.WebSocket_Apollo_ConnectionInit_Payload_Should_Be_Added_As_Tag_To_Request_And_Event_Spans.snap b/src/HotChocolate/Fusion/test/Fusion.Diagnostics.Tests/__snapshots__/FusionActivityServerDiagnosticListenerTests.WebSocket_Apollo_ConnectionInit_Payload_Should_Be_Added_As_Tag_To_Request_And_Event_Spans.snap new file mode 100644 index 00000000000..e03f14a6a0a --- /dev/null +++ b/src/HotChocolate/Fusion/test/Fusion.Diagnostics.Tests/__snapshots__/FusionActivityServerDiagnosticListenerTests.WebSocket_Apollo_ConnectionInit_Payload_Should_Be_Added_As_Tag_To_Request_And_Event_Spans.snap @@ -0,0 +1,321 @@ +{ + "source": { + "name": "HotChocolate.Fusion.Diagnostics" + }, + "activities": [ + { + "OperationName": "Microsoft.AspNetCore.Hosting.HttpRequestIn", + "DisplayName": "GET /graphql/{**slug}", + "Kind": "Server", + "Status": "Unset", + "tags": [ + { + "Key": "server.address", + "Value": "localhost" + }, + { + "Key": "server.port", + "Value": 5000 + }, + { + "Key": "http.request.method", + "Value": "GET" + }, + { + "Key": "url.scheme", + "Value": "http" + }, + { + "Key": "url.path", + "Value": "/graphql" + }, + { + "Key": "network.protocol.version", + "Value": "1.1" + }, + { + "Key": "http.route", + "Value": "/graphql/{**slug}" + }, + { + "Key": "http.response.status_code", + "Value": 101 + } + ], + "event": [], + "activities": [ + { + "OperationName": "GraphQL Operation", + "DisplayName": "subscription", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "request" + }, + { + "Key": "server.address", + "Value": "localhost" + }, + { + "Key": "server.port", + "Value": 5000 + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + }, + { + "Key": "test.tenant", + "Value": "acme-42" + } + ], + "event": [ + { + "Name": "AddedOperationPlanToCache", + "Tags": [] + }, + { + "Name": "AddedDocumentToCache", + "Tags": [] + } + ], + "activities": [ + { + "OperationName": "GraphQL Document Validation", + "DisplayName": "GraphQL Document Validation", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "validate" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + } + ], + "event": [] + }, + { + "OperationName": "GraphQL Operation Planning", + "DisplayName": "GraphQL Operation Planning", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "plan" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + } + ], + "event": [] + }, + { + "OperationName": "GraphQL Operation Execution", + "DisplayName": "GraphQL Operation Execution", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "execute" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + } + ], + "event": [], + "activities": [ + { + "OperationName": "GraphQL Step Execution", + "DisplayName": "GraphQL Step Execution", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "step_execute" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + }, + { + "Key": "graphql.operation.step.id", + "Value": "1" + }, + { + "Key": "graphql.operation.step.kind", + "Value": "operation" + }, + { + "Key": "graphql.operation.step.plan.id", + "Value": "bb94346d204b2f13a21dcba79a06b47065115db28b9ba2c04bce67382b50cef0" + }, + { + "Key": "graphql.source_schema.name", + "Value": "a" + }, + { + "Key": "graphql.source_schema.operation.name", + "Value": "OnMessageSubscription_ef859b98_1" + }, + { + "Key": "graphql.source_schema.operation.hash", + "Value": "sha256:f9abddbb651ad8e118793eb7c4a7c8db7fe43cae95faba710a0ff35b26b17136" + } + ], + "event": [] + } + ] + }, + { + "OperationName": "GraphQL Subscription Event", + "DisplayName": "GraphQL Subscription Event", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "subscription_event" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + }, + { + "Key": "graphql.subscription.id", + "Value": "" + }, + { + "Key": "test.tenant", + "Value": "acme-42" + } + ], + "event": [] + } + ] + }, + { + "OperationName": "System.Net.Http.HttpRequestOut", + "DisplayName": "System.Net.Http.HttpRequestOut", + "Kind": "Client", + "Status": "Unset", + "tags": [ + { + "Key": "http.request.method", + "Value": "POST" + }, + { + "Key": "server.address", + "Value": "localhost" + }, + { + "Key": "server.port", + "Value": 5000 + }, + { + "Key": "url.full", + "Value": "http://localhost:5000/graphql" + }, + { + "Key": "http.response.status_code", + "Value": 200 + } + ], + "event": [], + "activities": [ + { + "OperationName": "Microsoft.AspNetCore.Hosting.HttpRequestIn", + "DisplayName": "POST /graphql/{**slug}", + "Kind": "Server", + "Status": "Unset", + "tags": [ + { + "Key": "server.address", + "Value": "localhost" + }, + { + "Key": "server.port", + "Value": 5000 + }, + { + "Key": "http.request.method", + "Value": "POST" + }, + { + "Key": "url.scheme", + "Value": "http" + }, + { + "Key": "url.path", + "Value": "/graphql" + }, + { + "Key": "network.protocol.version", + "Value": "1.1" + }, + { + "Key": "http.route", + "Value": "/graphql/{**slug}" + }, + { + "Key": "http.response.status_code", + "Value": 200 + } + ], + "event": [] + } + ] + } + ] + } + ] +} diff --git a/src/HotChocolate/Fusion/test/Fusion.Diagnostics.Tests/__snapshots__/FusionActivityServerDiagnosticListenerTests.WebSocket_Apollo_ConnectionInit_Payload_Should_Be_Added_As_Tag_To_Request_And_Event_Spans_NET11_0.snap b/src/HotChocolate/Fusion/test/Fusion.Diagnostics.Tests/__snapshots__/FusionActivityServerDiagnosticListenerTests.WebSocket_Apollo_ConnectionInit_Payload_Should_Be_Added_As_Tag_To_Request_And_Event_Spans_NET11_0.snap new file mode 100644 index 00000000000..f3c18f13722 --- /dev/null +++ b/src/HotChocolate/Fusion/test/Fusion.Diagnostics.Tests/__snapshots__/FusionActivityServerDiagnosticListenerTests.WebSocket_Apollo_ConnectionInit_Payload_Should_Be_Added_As_Tag_To_Request_And_Event_Spans_NET11_0.snap @@ -0,0 +1,321 @@ +{ + "source": { + "name": "HotChocolate.Fusion.Diagnostics" + }, + "activities": [ + { + "OperationName": "Microsoft.AspNetCore.Hosting.HttpRequestIn", + "DisplayName": "GET /graphql/{**slug}", + "Kind": "Server", + "Status": "Unset", + "tags": [ + { + "Key": "server.address", + "Value": "localhost" + }, + { + "Key": "server.port", + "Value": 5000 + }, + { + "Key": "http.request.method", + "Value": "GET" + }, + { + "Key": "url.scheme", + "Value": "http" + }, + { + "Key": "url.path", + "Value": "/graphql" + }, + { + "Key": "network.protocol.version", + "Value": "1.1" + }, + { + "Key": "http.response.status_code", + "Value": 101 + }, + { + "Key": "http.route", + "Value": "/graphql/{**slug}" + } + ], + "event": [], + "activities": [ + { + "OperationName": "GraphQL Operation", + "DisplayName": "subscription", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "request" + }, + { + "Key": "server.address", + "Value": "localhost" + }, + { + "Key": "server.port", + "Value": 5000 + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + }, + { + "Key": "test.tenant", + "Value": "acme-42" + } + ], + "event": [ + { + "Name": "AddedOperationPlanToCache", + "Tags": [] + }, + { + "Name": "AddedDocumentToCache", + "Tags": [] + } + ], + "activities": [ + { + "OperationName": "GraphQL Document Validation", + "DisplayName": "GraphQL Document Validation", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "validate" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + } + ], + "event": [] + }, + { + "OperationName": "GraphQL Operation Planning", + "DisplayName": "GraphQL Operation Planning", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "plan" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + } + ], + "event": [] + }, + { + "OperationName": "GraphQL Operation Execution", + "DisplayName": "GraphQL Operation Execution", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "execute" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + } + ], + "event": [], + "activities": [ + { + "OperationName": "GraphQL Step Execution", + "DisplayName": "GraphQL Step Execution", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "step_execute" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + }, + { + "Key": "graphql.operation.step.id", + "Value": "1" + }, + { + "Key": "graphql.operation.step.kind", + "Value": "operation" + }, + { + "Key": "graphql.operation.step.plan.id", + "Value": "bb94346d204b2f13a21dcba79a06b47065115db28b9ba2c04bce67382b50cef0" + }, + { + "Key": "graphql.source_schema.name", + "Value": "a" + }, + { + "Key": "graphql.source_schema.operation.name", + "Value": "OnMessageSubscription_ef859b98_1" + }, + { + "Key": "graphql.source_schema.operation.hash", + "Value": "sha256:f9abddbb651ad8e118793eb7c4a7c8db7fe43cae95faba710a0ff35b26b17136" + } + ], + "event": [] + } + ] + }, + { + "OperationName": "GraphQL Subscription Event", + "DisplayName": "GraphQL Subscription Event", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "subscription_event" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + }, + { + "Key": "graphql.subscription.id", + "Value": "" + }, + { + "Key": "test.tenant", + "Value": "acme-42" + } + ], + "event": [] + } + ] + }, + { + "OperationName": "System.Net.Http.HttpRequestOut", + "DisplayName": "System.Net.Http.HttpRequestOut", + "Kind": "Client", + "Status": "Unset", + "tags": [ + { + "Key": "http.request.method", + "Value": "POST" + }, + { + "Key": "server.address", + "Value": "localhost" + }, + { + "Key": "server.port", + "Value": 5000 + }, + { + "Key": "url.full", + "Value": "http://localhost:5000/graphql" + }, + { + "Key": "http.response.status_code", + "Value": 200 + } + ], + "event": [], + "activities": [ + { + "OperationName": "Microsoft.AspNetCore.Hosting.HttpRequestIn", + "DisplayName": "POST /graphql/{**slug}", + "Kind": "Server", + "Status": "Unset", + "tags": [ + { + "Key": "server.address", + "Value": "localhost" + }, + { + "Key": "server.port", + "Value": 5000 + }, + { + "Key": "http.request.method", + "Value": "POST" + }, + { + "Key": "url.scheme", + "Value": "http" + }, + { + "Key": "url.path", + "Value": "/graphql" + }, + { + "Key": "network.protocol.version", + "Value": "1.1" + }, + { + "Key": "http.response.status_code", + "Value": 200 + }, + { + "Key": "http.route", + "Value": "/graphql/{**slug}" + } + ], + "event": [] + } + ] + } + ] + } + ] +} diff --git a/src/HotChocolate/Fusion/test/Fusion.Diagnostics.Tests/__snapshots__/FusionActivityServerDiagnosticListenerTests.WebSocket_ConnectionInit_Payload_Should_Be_Added_As_Tag_To_Request_And_Event_Spans.snap b/src/HotChocolate/Fusion/test/Fusion.Diagnostics.Tests/__snapshots__/FusionActivityServerDiagnosticListenerTests.WebSocket_ConnectionInit_Payload_Should_Be_Added_As_Tag_To_Request_And_Event_Spans.snap new file mode 100644 index 00000000000..e03f14a6a0a --- /dev/null +++ b/src/HotChocolate/Fusion/test/Fusion.Diagnostics.Tests/__snapshots__/FusionActivityServerDiagnosticListenerTests.WebSocket_ConnectionInit_Payload_Should_Be_Added_As_Tag_To_Request_And_Event_Spans.snap @@ -0,0 +1,321 @@ +{ + "source": { + "name": "HotChocolate.Fusion.Diagnostics" + }, + "activities": [ + { + "OperationName": "Microsoft.AspNetCore.Hosting.HttpRequestIn", + "DisplayName": "GET /graphql/{**slug}", + "Kind": "Server", + "Status": "Unset", + "tags": [ + { + "Key": "server.address", + "Value": "localhost" + }, + { + "Key": "server.port", + "Value": 5000 + }, + { + "Key": "http.request.method", + "Value": "GET" + }, + { + "Key": "url.scheme", + "Value": "http" + }, + { + "Key": "url.path", + "Value": "/graphql" + }, + { + "Key": "network.protocol.version", + "Value": "1.1" + }, + { + "Key": "http.route", + "Value": "/graphql/{**slug}" + }, + { + "Key": "http.response.status_code", + "Value": 101 + } + ], + "event": [], + "activities": [ + { + "OperationName": "GraphQL Operation", + "DisplayName": "subscription", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "request" + }, + { + "Key": "server.address", + "Value": "localhost" + }, + { + "Key": "server.port", + "Value": 5000 + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + }, + { + "Key": "test.tenant", + "Value": "acme-42" + } + ], + "event": [ + { + "Name": "AddedOperationPlanToCache", + "Tags": [] + }, + { + "Name": "AddedDocumentToCache", + "Tags": [] + } + ], + "activities": [ + { + "OperationName": "GraphQL Document Validation", + "DisplayName": "GraphQL Document Validation", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "validate" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + } + ], + "event": [] + }, + { + "OperationName": "GraphQL Operation Planning", + "DisplayName": "GraphQL Operation Planning", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "plan" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + } + ], + "event": [] + }, + { + "OperationName": "GraphQL Operation Execution", + "DisplayName": "GraphQL Operation Execution", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "execute" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + } + ], + "event": [], + "activities": [ + { + "OperationName": "GraphQL Step Execution", + "DisplayName": "GraphQL Step Execution", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "step_execute" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + }, + { + "Key": "graphql.operation.step.id", + "Value": "1" + }, + { + "Key": "graphql.operation.step.kind", + "Value": "operation" + }, + { + "Key": "graphql.operation.step.plan.id", + "Value": "bb94346d204b2f13a21dcba79a06b47065115db28b9ba2c04bce67382b50cef0" + }, + { + "Key": "graphql.source_schema.name", + "Value": "a" + }, + { + "Key": "graphql.source_schema.operation.name", + "Value": "OnMessageSubscription_ef859b98_1" + }, + { + "Key": "graphql.source_schema.operation.hash", + "Value": "sha256:f9abddbb651ad8e118793eb7c4a7c8db7fe43cae95faba710a0ff35b26b17136" + } + ], + "event": [] + } + ] + }, + { + "OperationName": "GraphQL Subscription Event", + "DisplayName": "GraphQL Subscription Event", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "subscription_event" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + }, + { + "Key": "graphql.subscription.id", + "Value": "" + }, + { + "Key": "test.tenant", + "Value": "acme-42" + } + ], + "event": [] + } + ] + }, + { + "OperationName": "System.Net.Http.HttpRequestOut", + "DisplayName": "System.Net.Http.HttpRequestOut", + "Kind": "Client", + "Status": "Unset", + "tags": [ + { + "Key": "http.request.method", + "Value": "POST" + }, + { + "Key": "server.address", + "Value": "localhost" + }, + { + "Key": "server.port", + "Value": 5000 + }, + { + "Key": "url.full", + "Value": "http://localhost:5000/graphql" + }, + { + "Key": "http.response.status_code", + "Value": 200 + } + ], + "event": [], + "activities": [ + { + "OperationName": "Microsoft.AspNetCore.Hosting.HttpRequestIn", + "DisplayName": "POST /graphql/{**slug}", + "Kind": "Server", + "Status": "Unset", + "tags": [ + { + "Key": "server.address", + "Value": "localhost" + }, + { + "Key": "server.port", + "Value": 5000 + }, + { + "Key": "http.request.method", + "Value": "POST" + }, + { + "Key": "url.scheme", + "Value": "http" + }, + { + "Key": "url.path", + "Value": "/graphql" + }, + { + "Key": "network.protocol.version", + "Value": "1.1" + }, + { + "Key": "http.route", + "Value": "/graphql/{**slug}" + }, + { + "Key": "http.response.status_code", + "Value": 200 + } + ], + "event": [] + } + ] + } + ] + } + ] +} diff --git a/src/HotChocolate/Fusion/test/Fusion.Diagnostics.Tests/__snapshots__/FusionActivityServerDiagnosticListenerTests.WebSocket_ConnectionInit_Payload_Should_Be_Added_As_Tag_To_Request_And_Event_Spans_NET11_0.snap b/src/HotChocolate/Fusion/test/Fusion.Diagnostics.Tests/__snapshots__/FusionActivityServerDiagnosticListenerTests.WebSocket_ConnectionInit_Payload_Should_Be_Added_As_Tag_To_Request_And_Event_Spans_NET11_0.snap new file mode 100644 index 00000000000..f3c18f13722 --- /dev/null +++ b/src/HotChocolate/Fusion/test/Fusion.Diagnostics.Tests/__snapshots__/FusionActivityServerDiagnosticListenerTests.WebSocket_ConnectionInit_Payload_Should_Be_Added_As_Tag_To_Request_And_Event_Spans_NET11_0.snap @@ -0,0 +1,321 @@ +{ + "source": { + "name": "HotChocolate.Fusion.Diagnostics" + }, + "activities": [ + { + "OperationName": "Microsoft.AspNetCore.Hosting.HttpRequestIn", + "DisplayName": "GET /graphql/{**slug}", + "Kind": "Server", + "Status": "Unset", + "tags": [ + { + "Key": "server.address", + "Value": "localhost" + }, + { + "Key": "server.port", + "Value": 5000 + }, + { + "Key": "http.request.method", + "Value": "GET" + }, + { + "Key": "url.scheme", + "Value": "http" + }, + { + "Key": "url.path", + "Value": "/graphql" + }, + { + "Key": "network.protocol.version", + "Value": "1.1" + }, + { + "Key": "http.response.status_code", + "Value": 101 + }, + { + "Key": "http.route", + "Value": "/graphql/{**slug}" + } + ], + "event": [], + "activities": [ + { + "OperationName": "GraphQL Operation", + "DisplayName": "subscription", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "request" + }, + { + "Key": "server.address", + "Value": "localhost" + }, + { + "Key": "server.port", + "Value": 5000 + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + }, + { + "Key": "test.tenant", + "Value": "acme-42" + } + ], + "event": [ + { + "Name": "AddedOperationPlanToCache", + "Tags": [] + }, + { + "Name": "AddedDocumentToCache", + "Tags": [] + } + ], + "activities": [ + { + "OperationName": "GraphQL Document Validation", + "DisplayName": "GraphQL Document Validation", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "validate" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + } + ], + "event": [] + }, + { + "OperationName": "GraphQL Operation Planning", + "DisplayName": "GraphQL Operation Planning", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "plan" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + } + ], + "event": [] + }, + { + "OperationName": "GraphQL Operation Execution", + "DisplayName": "GraphQL Operation Execution", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "execute" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + } + ], + "event": [], + "activities": [ + { + "OperationName": "GraphQL Step Execution", + "DisplayName": "GraphQL Step Execution", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "step_execute" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + }, + { + "Key": "graphql.operation.step.id", + "Value": "1" + }, + { + "Key": "graphql.operation.step.kind", + "Value": "operation" + }, + { + "Key": "graphql.operation.step.plan.id", + "Value": "bb94346d204b2f13a21dcba79a06b47065115db28b9ba2c04bce67382b50cef0" + }, + { + "Key": "graphql.source_schema.name", + "Value": "a" + }, + { + "Key": "graphql.source_schema.operation.name", + "Value": "OnMessageSubscription_ef859b98_1" + }, + { + "Key": "graphql.source_schema.operation.hash", + "Value": "sha256:f9abddbb651ad8e118793eb7c4a7c8db7fe43cae95faba710a0ff35b26b17136" + } + ], + "event": [] + } + ] + }, + { + "OperationName": "GraphQL Subscription Event", + "DisplayName": "GraphQL Subscription Event", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "subscription_event" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + }, + { + "Key": "graphql.subscription.id", + "Value": "" + }, + { + "Key": "test.tenant", + "Value": "acme-42" + } + ], + "event": [] + } + ] + }, + { + "OperationName": "System.Net.Http.HttpRequestOut", + "DisplayName": "System.Net.Http.HttpRequestOut", + "Kind": "Client", + "Status": "Unset", + "tags": [ + { + "Key": "http.request.method", + "Value": "POST" + }, + { + "Key": "server.address", + "Value": "localhost" + }, + { + "Key": "server.port", + "Value": 5000 + }, + { + "Key": "url.full", + "Value": "http://localhost:5000/graphql" + }, + { + "Key": "http.response.status_code", + "Value": 200 + } + ], + "event": [], + "activities": [ + { + "OperationName": "Microsoft.AspNetCore.Hosting.HttpRequestIn", + "DisplayName": "POST /graphql/{**slug}", + "Kind": "Server", + "Status": "Unset", + "tags": [ + { + "Key": "server.address", + "Value": "localhost" + }, + { + "Key": "server.port", + "Value": 5000 + }, + { + "Key": "http.request.method", + "Value": "POST" + }, + { + "Key": "url.scheme", + "Value": "http" + }, + { + "Key": "url.path", + "Value": "/graphql" + }, + { + "Key": "network.protocol.version", + "Value": "1.1" + }, + { + "Key": "http.response.status_code", + "Value": 200 + }, + { + "Key": "http.route", + "Value": "/graphql/{**slug}" + } + ], + "event": [] + } + ] + } + ] + } + ] +} diff --git a/src/HotChocolate/Fusion/test/Fusion.Diagnostics.Tests/__snapshots__/FusionActivityServerDiagnosticListenerTests.WebSocket_Header_Should_Be_Added_As_Tag_To_Request_And_Event_Spans.snap b/src/HotChocolate/Fusion/test/Fusion.Diagnostics.Tests/__snapshots__/FusionActivityServerDiagnosticListenerTests.WebSocket_Header_Should_Be_Added_As_Tag_To_Request_And_Event_Spans.snap new file mode 100644 index 00000000000..e03f14a6a0a --- /dev/null +++ b/src/HotChocolate/Fusion/test/Fusion.Diagnostics.Tests/__snapshots__/FusionActivityServerDiagnosticListenerTests.WebSocket_Header_Should_Be_Added_As_Tag_To_Request_And_Event_Spans.snap @@ -0,0 +1,321 @@ +{ + "source": { + "name": "HotChocolate.Fusion.Diagnostics" + }, + "activities": [ + { + "OperationName": "Microsoft.AspNetCore.Hosting.HttpRequestIn", + "DisplayName": "GET /graphql/{**slug}", + "Kind": "Server", + "Status": "Unset", + "tags": [ + { + "Key": "server.address", + "Value": "localhost" + }, + { + "Key": "server.port", + "Value": 5000 + }, + { + "Key": "http.request.method", + "Value": "GET" + }, + { + "Key": "url.scheme", + "Value": "http" + }, + { + "Key": "url.path", + "Value": "/graphql" + }, + { + "Key": "network.protocol.version", + "Value": "1.1" + }, + { + "Key": "http.route", + "Value": "/graphql/{**slug}" + }, + { + "Key": "http.response.status_code", + "Value": 101 + } + ], + "event": [], + "activities": [ + { + "OperationName": "GraphQL Operation", + "DisplayName": "subscription", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "request" + }, + { + "Key": "server.address", + "Value": "localhost" + }, + { + "Key": "server.port", + "Value": 5000 + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + }, + { + "Key": "test.tenant", + "Value": "acme-42" + } + ], + "event": [ + { + "Name": "AddedOperationPlanToCache", + "Tags": [] + }, + { + "Name": "AddedDocumentToCache", + "Tags": [] + } + ], + "activities": [ + { + "OperationName": "GraphQL Document Validation", + "DisplayName": "GraphQL Document Validation", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "validate" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + } + ], + "event": [] + }, + { + "OperationName": "GraphQL Operation Planning", + "DisplayName": "GraphQL Operation Planning", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "plan" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + } + ], + "event": [] + }, + { + "OperationName": "GraphQL Operation Execution", + "DisplayName": "GraphQL Operation Execution", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "execute" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + } + ], + "event": [], + "activities": [ + { + "OperationName": "GraphQL Step Execution", + "DisplayName": "GraphQL Step Execution", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "step_execute" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + }, + { + "Key": "graphql.operation.step.id", + "Value": "1" + }, + { + "Key": "graphql.operation.step.kind", + "Value": "operation" + }, + { + "Key": "graphql.operation.step.plan.id", + "Value": "bb94346d204b2f13a21dcba79a06b47065115db28b9ba2c04bce67382b50cef0" + }, + { + "Key": "graphql.source_schema.name", + "Value": "a" + }, + { + "Key": "graphql.source_schema.operation.name", + "Value": "OnMessageSubscription_ef859b98_1" + }, + { + "Key": "graphql.source_schema.operation.hash", + "Value": "sha256:f9abddbb651ad8e118793eb7c4a7c8db7fe43cae95faba710a0ff35b26b17136" + } + ], + "event": [] + } + ] + }, + { + "OperationName": "GraphQL Subscription Event", + "DisplayName": "GraphQL Subscription Event", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "subscription_event" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + }, + { + "Key": "graphql.subscription.id", + "Value": "" + }, + { + "Key": "test.tenant", + "Value": "acme-42" + } + ], + "event": [] + } + ] + }, + { + "OperationName": "System.Net.Http.HttpRequestOut", + "DisplayName": "System.Net.Http.HttpRequestOut", + "Kind": "Client", + "Status": "Unset", + "tags": [ + { + "Key": "http.request.method", + "Value": "POST" + }, + { + "Key": "server.address", + "Value": "localhost" + }, + { + "Key": "server.port", + "Value": 5000 + }, + { + "Key": "url.full", + "Value": "http://localhost:5000/graphql" + }, + { + "Key": "http.response.status_code", + "Value": 200 + } + ], + "event": [], + "activities": [ + { + "OperationName": "Microsoft.AspNetCore.Hosting.HttpRequestIn", + "DisplayName": "POST /graphql/{**slug}", + "Kind": "Server", + "Status": "Unset", + "tags": [ + { + "Key": "server.address", + "Value": "localhost" + }, + { + "Key": "server.port", + "Value": 5000 + }, + { + "Key": "http.request.method", + "Value": "POST" + }, + { + "Key": "url.scheme", + "Value": "http" + }, + { + "Key": "url.path", + "Value": "/graphql" + }, + { + "Key": "network.protocol.version", + "Value": "1.1" + }, + { + "Key": "http.route", + "Value": "/graphql/{**slug}" + }, + { + "Key": "http.response.status_code", + "Value": 200 + } + ], + "event": [] + } + ] + } + ] + } + ] +} diff --git a/src/HotChocolate/Fusion/test/Fusion.Diagnostics.Tests/__snapshots__/FusionActivityServerDiagnosticListenerTests.WebSocket_Header_Should_Be_Added_As_Tag_To_Request_And_Event_Spans_NET11_0.snap b/src/HotChocolate/Fusion/test/Fusion.Diagnostics.Tests/__snapshots__/FusionActivityServerDiagnosticListenerTests.WebSocket_Header_Should_Be_Added_As_Tag_To_Request_And_Event_Spans_NET11_0.snap new file mode 100644 index 00000000000..f3c18f13722 --- /dev/null +++ b/src/HotChocolate/Fusion/test/Fusion.Diagnostics.Tests/__snapshots__/FusionActivityServerDiagnosticListenerTests.WebSocket_Header_Should_Be_Added_As_Tag_To_Request_And_Event_Spans_NET11_0.snap @@ -0,0 +1,321 @@ +{ + "source": { + "name": "HotChocolate.Fusion.Diagnostics" + }, + "activities": [ + { + "OperationName": "Microsoft.AspNetCore.Hosting.HttpRequestIn", + "DisplayName": "GET /graphql/{**slug}", + "Kind": "Server", + "Status": "Unset", + "tags": [ + { + "Key": "server.address", + "Value": "localhost" + }, + { + "Key": "server.port", + "Value": 5000 + }, + { + "Key": "http.request.method", + "Value": "GET" + }, + { + "Key": "url.scheme", + "Value": "http" + }, + { + "Key": "url.path", + "Value": "/graphql" + }, + { + "Key": "network.protocol.version", + "Value": "1.1" + }, + { + "Key": "http.response.status_code", + "Value": 101 + }, + { + "Key": "http.route", + "Value": "/graphql/{**slug}" + } + ], + "event": [], + "activities": [ + { + "OperationName": "GraphQL Operation", + "DisplayName": "subscription", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "request" + }, + { + "Key": "server.address", + "Value": "localhost" + }, + { + "Key": "server.port", + "Value": 5000 + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + }, + { + "Key": "test.tenant", + "Value": "acme-42" + } + ], + "event": [ + { + "Name": "AddedOperationPlanToCache", + "Tags": [] + }, + { + "Name": "AddedDocumentToCache", + "Tags": [] + } + ], + "activities": [ + { + "OperationName": "GraphQL Document Validation", + "DisplayName": "GraphQL Document Validation", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "validate" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + } + ], + "event": [] + }, + { + "OperationName": "GraphQL Operation Planning", + "DisplayName": "GraphQL Operation Planning", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "plan" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + } + ], + "event": [] + }, + { + "OperationName": "GraphQL Operation Execution", + "DisplayName": "GraphQL Operation Execution", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "execute" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + } + ], + "event": [], + "activities": [ + { + "OperationName": "GraphQL Step Execution", + "DisplayName": "GraphQL Step Execution", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "step_execute" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + }, + { + "Key": "graphql.operation.step.id", + "Value": "1" + }, + { + "Key": "graphql.operation.step.kind", + "Value": "operation" + }, + { + "Key": "graphql.operation.step.plan.id", + "Value": "bb94346d204b2f13a21dcba79a06b47065115db28b9ba2c04bce67382b50cef0" + }, + { + "Key": "graphql.source_schema.name", + "Value": "a" + }, + { + "Key": "graphql.source_schema.operation.name", + "Value": "OnMessageSubscription_ef859b98_1" + }, + { + "Key": "graphql.source_schema.operation.hash", + "Value": "sha256:f9abddbb651ad8e118793eb7c4a7c8db7fe43cae95faba710a0ff35b26b17136" + } + ], + "event": [] + } + ] + }, + { + "OperationName": "GraphQL Subscription Event", + "DisplayName": "GraphQL Subscription Event", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "subscription_event" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + }, + { + "Key": "graphql.subscription.id", + "Value": "" + }, + { + "Key": "test.tenant", + "Value": "acme-42" + } + ], + "event": [] + } + ] + }, + { + "OperationName": "System.Net.Http.HttpRequestOut", + "DisplayName": "System.Net.Http.HttpRequestOut", + "Kind": "Client", + "Status": "Unset", + "tags": [ + { + "Key": "http.request.method", + "Value": "POST" + }, + { + "Key": "server.address", + "Value": "localhost" + }, + { + "Key": "server.port", + "Value": 5000 + }, + { + "Key": "url.full", + "Value": "http://localhost:5000/graphql" + }, + { + "Key": "http.response.status_code", + "Value": 200 + } + ], + "event": [], + "activities": [ + { + "OperationName": "Microsoft.AspNetCore.Hosting.HttpRequestIn", + "DisplayName": "POST /graphql/{**slug}", + "Kind": "Server", + "Status": "Unset", + "tags": [ + { + "Key": "server.address", + "Value": "localhost" + }, + { + "Key": "server.port", + "Value": 5000 + }, + { + "Key": "http.request.method", + "Value": "POST" + }, + { + "Key": "url.scheme", + "Value": "http" + }, + { + "Key": "url.path", + "Value": "/graphql" + }, + { + "Key": "network.protocol.version", + "Value": "1.1" + }, + { + "Key": "http.response.status_code", + "Value": 200 + }, + { + "Key": "http.route", + "Value": "/graphql/{**slug}" + } + ], + "event": [] + } + ] + } + ] + } + ] +} diff --git a/src/HotChocolate/Fusion/test/Fusion.Diagnostics.Tests/__snapshots__/FusionActivityServerDiagnosticListenerTests.WebSocket_Subscription_Should_Be_Ok_When_Client_Closes_Connection.snap b/src/HotChocolate/Fusion/test/Fusion.Diagnostics.Tests/__snapshots__/FusionActivityServerDiagnosticListenerTests.WebSocket_Subscription_Should_Be_Ok_When_Client_Closes_Connection.snap new file mode 100644 index 00000000000..610f071d287 --- /dev/null +++ b/src/HotChocolate/Fusion/test/Fusion.Diagnostics.Tests/__snapshots__/FusionActivityServerDiagnosticListenerTests.WebSocket_Subscription_Should_Be_Ok_When_Client_Closes_Connection.snap @@ -0,0 +1,313 @@ +{ + "source": { + "name": "HotChocolate.Fusion.Diagnostics" + }, + "activities": [ + { + "OperationName": "Microsoft.AspNetCore.Hosting.HttpRequestIn", + "DisplayName": "GET /graphql/{**slug}", + "Kind": "Server", + "Status": "Unset", + "tags": [ + { + "Key": "server.address", + "Value": "localhost" + }, + { + "Key": "server.port", + "Value": 5000 + }, + { + "Key": "http.request.method", + "Value": "GET" + }, + { + "Key": "url.scheme", + "Value": "http" + }, + { + "Key": "url.path", + "Value": "/graphql" + }, + { + "Key": "network.protocol.version", + "Value": "1.1" + }, + { + "Key": "http.route", + "Value": "/graphql/{**slug}" + }, + { + "Key": "http.response.status_code", + "Value": 101 + } + ], + "event": [], + "activities": [ + { + "OperationName": "GraphQL Operation", + "DisplayName": "subscription", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "request" + }, + { + "Key": "server.address", + "Value": "localhost" + }, + { + "Key": "server.port", + "Value": 5000 + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnIdleMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:51d9b3bca5722d3209429e79f8ee6435" + } + ], + "event": [ + { + "Name": "AddedOperationPlanToCache", + "Tags": [] + }, + { + "Name": "AddedDocumentToCache", + "Tags": [] + } + ], + "activities": [ + { + "OperationName": "GraphQL Document Validation", + "DisplayName": "GraphQL Document Validation", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "validate" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:51d9b3bca5722d3209429e79f8ee6435" + } + ], + "event": [] + }, + { + "OperationName": "GraphQL Operation Planning", + "DisplayName": "GraphQL Operation Planning", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "plan" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:51d9b3bca5722d3209429e79f8ee6435" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnIdleMessageSubscription" + } + ], + "event": [] + }, + { + "OperationName": "GraphQL Operation Execution", + "DisplayName": "GraphQL Operation Execution", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "execute" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnIdleMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:51d9b3bca5722d3209429e79f8ee6435" + } + ], + "event": [], + "activities": [ + { + "OperationName": "GraphQL Step Execution", + "DisplayName": "GraphQL Step Execution", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "step_execute" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnIdleMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:51d9b3bca5722d3209429e79f8ee6435" + }, + { + "Key": "graphql.operation.step.id", + "Value": "1" + }, + { + "Key": "graphql.operation.step.kind", + "Value": "operation" + }, + { + "Key": "graphql.operation.step.plan.id", + "Value": "8fef749a1b041aca849173d1dd38a6022e142b1ebbf92aeb666766e3a0654610" + }, + { + "Key": "graphql.source_schema.name", + "Value": "a" + }, + { + "Key": "graphql.source_schema.operation.name", + "Value": "OnIdleMessageSubscription_51d9b3bc_1" + }, + { + "Key": "graphql.source_schema.operation.hash", + "Value": "sha256:6b6ed56d2f50e7e614c9096e8e46d789db0e605355b6baf21ab088bde107ca0e" + } + ], + "event": [] + } + ] + }, + { + "OperationName": "GraphQL Subscription Event", + "DisplayName": "GraphQL Subscription Event", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "subscription_event" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnIdleMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:51d9b3bca5722d3209429e79f8ee6435" + }, + { + "Key": "graphql.subscription.id", + "Value": "" + } + ], + "event": [] + } + ] + }, + { + "OperationName": "System.Net.Http.HttpRequestOut", + "DisplayName": "System.Net.Http.HttpRequestOut", + "Kind": "Client", + "Status": "Unset", + "tags": [ + { + "Key": "http.request.method", + "Value": "POST" + }, + { + "Key": "server.address", + "Value": "localhost" + }, + { + "Key": "server.port", + "Value": 5000 + }, + { + "Key": "url.full", + "Value": "http://localhost:5000/graphql" + }, + { + "Key": "http.response.status_code", + "Value": 200 + } + ], + "event": [], + "activities": [ + { + "OperationName": "Microsoft.AspNetCore.Hosting.HttpRequestIn", + "DisplayName": "POST /graphql/{**slug}", + "Kind": "Server", + "Status": "Unset", + "tags": [ + { + "Key": "server.address", + "Value": "localhost" + }, + { + "Key": "server.port", + "Value": 5000 + }, + { + "Key": "http.request.method", + "Value": "POST" + }, + { + "Key": "url.scheme", + "Value": "http" + }, + { + "Key": "url.path", + "Value": "/graphql" + }, + { + "Key": "network.protocol.version", + "Value": "1.1" + }, + { + "Key": "http.route", + "Value": "/graphql/{**slug}" + }, + { + "Key": "http.response.status_code", + "Value": 200 + } + ], + "event": [] + } + ] + } + ] + } + ] +} diff --git a/src/HotChocolate/Fusion/test/Fusion.Diagnostics.Tests/__snapshots__/FusionActivityServerDiagnosticListenerTests.WebSocket_Subscription_Should_Be_Ok_When_Client_Closes_Connection_NET11_0.snap b/src/HotChocolate/Fusion/test/Fusion.Diagnostics.Tests/__snapshots__/FusionActivityServerDiagnosticListenerTests.WebSocket_Subscription_Should_Be_Ok_When_Client_Closes_Connection_NET11_0.snap new file mode 100644 index 00000000000..9dfe166518d --- /dev/null +++ b/src/HotChocolate/Fusion/test/Fusion.Diagnostics.Tests/__snapshots__/FusionActivityServerDiagnosticListenerTests.WebSocket_Subscription_Should_Be_Ok_When_Client_Closes_Connection_NET11_0.snap @@ -0,0 +1,313 @@ +{ + "source": { + "name": "HotChocolate.Fusion.Diagnostics" + }, + "activities": [ + { + "OperationName": "Microsoft.AspNetCore.Hosting.HttpRequestIn", + "DisplayName": "GET /graphql/{**slug}", + "Kind": "Server", + "Status": "Unset", + "tags": [ + { + "Key": "server.address", + "Value": "localhost" + }, + { + "Key": "server.port", + "Value": 5000 + }, + { + "Key": "http.request.method", + "Value": "GET" + }, + { + "Key": "url.scheme", + "Value": "http" + }, + { + "Key": "url.path", + "Value": "/graphql" + }, + { + "Key": "network.protocol.version", + "Value": "1.1" + }, + { + "Key": "http.response.status_code", + "Value": 101 + }, + { + "Key": "http.route", + "Value": "/graphql/{**slug}" + } + ], + "event": [], + "activities": [ + { + "OperationName": "GraphQL Operation", + "DisplayName": "subscription", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "request" + }, + { + "Key": "server.address", + "Value": "localhost" + }, + { + "Key": "server.port", + "Value": 5000 + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnIdleMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:51d9b3bca5722d3209429e79f8ee6435" + } + ], + "event": [ + { + "Name": "AddedOperationPlanToCache", + "Tags": [] + }, + { + "Name": "AddedDocumentToCache", + "Tags": [] + } + ], + "activities": [ + { + "OperationName": "GraphQL Document Validation", + "DisplayName": "GraphQL Document Validation", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "validate" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:51d9b3bca5722d3209429e79f8ee6435" + } + ], + "event": [] + }, + { + "OperationName": "GraphQL Operation Planning", + "DisplayName": "GraphQL Operation Planning", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "plan" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:51d9b3bca5722d3209429e79f8ee6435" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnIdleMessageSubscription" + } + ], + "event": [] + }, + { + "OperationName": "GraphQL Operation Execution", + "DisplayName": "GraphQL Operation Execution", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "execute" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnIdleMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:51d9b3bca5722d3209429e79f8ee6435" + } + ], + "event": [], + "activities": [ + { + "OperationName": "GraphQL Step Execution", + "DisplayName": "GraphQL Step Execution", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "step_execute" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnIdleMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:51d9b3bca5722d3209429e79f8ee6435" + }, + { + "Key": "graphql.operation.step.id", + "Value": "1" + }, + { + "Key": "graphql.operation.step.kind", + "Value": "operation" + }, + { + "Key": "graphql.operation.step.plan.id", + "Value": "8fef749a1b041aca849173d1dd38a6022e142b1ebbf92aeb666766e3a0654610" + }, + { + "Key": "graphql.source_schema.name", + "Value": "a" + }, + { + "Key": "graphql.source_schema.operation.name", + "Value": "OnIdleMessageSubscription_51d9b3bc_1" + }, + { + "Key": "graphql.source_schema.operation.hash", + "Value": "sha256:6b6ed56d2f50e7e614c9096e8e46d789db0e605355b6baf21ab088bde107ca0e" + } + ], + "event": [] + } + ] + }, + { + "OperationName": "GraphQL Subscription Event", + "DisplayName": "GraphQL Subscription Event", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "subscription_event" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnIdleMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:51d9b3bca5722d3209429e79f8ee6435" + }, + { + "Key": "graphql.subscription.id", + "Value": "" + } + ], + "event": [] + } + ] + }, + { + "OperationName": "System.Net.Http.HttpRequestOut", + "DisplayName": "System.Net.Http.HttpRequestOut", + "Kind": "Client", + "Status": "Unset", + "tags": [ + { + "Key": "http.request.method", + "Value": "POST" + }, + { + "Key": "server.address", + "Value": "localhost" + }, + { + "Key": "server.port", + "Value": 5000 + }, + { + "Key": "url.full", + "Value": "http://localhost:5000/graphql" + }, + { + "Key": "http.response.status_code", + "Value": 200 + } + ], + "event": [], + "activities": [ + { + "OperationName": "Microsoft.AspNetCore.Hosting.HttpRequestIn", + "DisplayName": "POST /graphql/{**slug}", + "Kind": "Server", + "Status": "Unset", + "tags": [ + { + "Key": "server.address", + "Value": "localhost" + }, + { + "Key": "server.port", + "Value": 5000 + }, + { + "Key": "http.request.method", + "Value": "POST" + }, + { + "Key": "url.scheme", + "Value": "http" + }, + { + "Key": "url.path", + "Value": "/graphql" + }, + { + "Key": "network.protocol.version", + "Value": "1.1" + }, + { + "Key": "http.response.status_code", + "Value": 200 + }, + { + "Key": "http.route", + "Value": "/graphql/{**slug}" + } + ], + "event": [] + } + ] + } + ] + } + ] +} diff --git a/src/HotChocolate/Fusion/test/Fusion.Diagnostics.Tests/__snapshots__/FusionActivityServerDiagnosticListenerTests.WebSocket_Subscription_Should_Be_Ok_When_Server_Completes.snap b/src/HotChocolate/Fusion/test/Fusion.Diagnostics.Tests/__snapshots__/FusionActivityServerDiagnosticListenerTests.WebSocket_Subscription_Should_Be_Ok_When_Server_Completes.snap new file mode 100644 index 00000000000..1e77d211eae --- /dev/null +++ b/src/HotChocolate/Fusion/test/Fusion.Diagnostics.Tests/__snapshots__/FusionActivityServerDiagnosticListenerTests.WebSocket_Subscription_Should_Be_Ok_When_Server_Completes.snap @@ -0,0 +1,313 @@ +{ + "source": { + "name": "HotChocolate.Fusion.Diagnostics" + }, + "activities": [ + { + "OperationName": "Microsoft.AspNetCore.Hosting.HttpRequestIn", + "DisplayName": "GET /graphql/{**slug}", + "Kind": "Server", + "Status": "Unset", + "tags": [ + { + "Key": "server.address", + "Value": "localhost" + }, + { + "Key": "server.port", + "Value": 5000 + }, + { + "Key": "http.request.method", + "Value": "GET" + }, + { + "Key": "url.scheme", + "Value": "http" + }, + { + "Key": "url.path", + "Value": "/graphql" + }, + { + "Key": "network.protocol.version", + "Value": "1.1" + }, + { + "Key": "http.route", + "Value": "/graphql/{**slug}" + }, + { + "Key": "http.response.status_code", + "Value": 101 + } + ], + "event": [], + "activities": [ + { + "OperationName": "GraphQL Operation", + "DisplayName": "subscription", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "request" + }, + { + "Key": "server.address", + "Value": "localhost" + }, + { + "Key": "server.port", + "Value": 5000 + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + } + ], + "event": [ + { + "Name": "AddedOperationPlanToCache", + "Tags": [] + }, + { + "Name": "AddedDocumentToCache", + "Tags": [] + } + ], + "activities": [ + { + "OperationName": "GraphQL Document Validation", + "DisplayName": "GraphQL Document Validation", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "validate" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + } + ], + "event": [] + }, + { + "OperationName": "GraphQL Operation Planning", + "DisplayName": "GraphQL Operation Planning", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "plan" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + } + ], + "event": [] + }, + { + "OperationName": "GraphQL Operation Execution", + "DisplayName": "GraphQL Operation Execution", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "execute" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + } + ], + "event": [], + "activities": [ + { + "OperationName": "GraphQL Step Execution", + "DisplayName": "GraphQL Step Execution", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "step_execute" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + }, + { + "Key": "graphql.operation.step.id", + "Value": "1" + }, + { + "Key": "graphql.operation.step.kind", + "Value": "operation" + }, + { + "Key": "graphql.operation.step.plan.id", + "Value": "bb94346d204b2f13a21dcba79a06b47065115db28b9ba2c04bce67382b50cef0" + }, + { + "Key": "graphql.source_schema.name", + "Value": "a" + }, + { + "Key": "graphql.source_schema.operation.name", + "Value": "OnMessageSubscription_ef859b98_1" + }, + { + "Key": "graphql.source_schema.operation.hash", + "Value": "sha256:f9abddbb651ad8e118793eb7c4a7c8db7fe43cae95faba710a0ff35b26b17136" + } + ], + "event": [] + } + ] + }, + { + "OperationName": "GraphQL Subscription Event", + "DisplayName": "GraphQL Subscription Event", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "subscription_event" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + }, + { + "Key": "graphql.subscription.id", + "Value": "" + } + ], + "event": [] + } + ] + }, + { + "OperationName": "System.Net.Http.HttpRequestOut", + "DisplayName": "System.Net.Http.HttpRequestOut", + "Kind": "Client", + "Status": "Unset", + "tags": [ + { + "Key": "http.request.method", + "Value": "POST" + }, + { + "Key": "server.address", + "Value": "localhost" + }, + { + "Key": "server.port", + "Value": 5000 + }, + { + "Key": "url.full", + "Value": "http://localhost:5000/graphql" + }, + { + "Key": "http.response.status_code", + "Value": 200 + } + ], + "event": [], + "activities": [ + { + "OperationName": "Microsoft.AspNetCore.Hosting.HttpRequestIn", + "DisplayName": "POST /graphql/{**slug}", + "Kind": "Server", + "Status": "Unset", + "tags": [ + { + "Key": "server.address", + "Value": "localhost" + }, + { + "Key": "server.port", + "Value": 5000 + }, + { + "Key": "http.request.method", + "Value": "POST" + }, + { + "Key": "url.scheme", + "Value": "http" + }, + { + "Key": "url.path", + "Value": "/graphql" + }, + { + "Key": "network.protocol.version", + "Value": "1.1" + }, + { + "Key": "http.route", + "Value": "/graphql/{**slug}" + }, + { + "Key": "http.response.status_code", + "Value": 200 + } + ], + "event": [] + } + ] + } + ] + } + ] +} diff --git a/src/HotChocolate/Fusion/test/Fusion.Diagnostics.Tests/__snapshots__/FusionActivityServerDiagnosticListenerTests.WebSocket_Subscription_Should_Be_Ok_When_Server_Completes_Default.snap b/src/HotChocolate/Fusion/test/Fusion.Diagnostics.Tests/__snapshots__/FusionActivityServerDiagnosticListenerTests.WebSocket_Subscription_Should_Be_Ok_When_Server_Completes_Default.snap new file mode 100644 index 00000000000..3b4398e3218 --- /dev/null +++ b/src/HotChocolate/Fusion/test/Fusion.Diagnostics.Tests/__snapshots__/FusionActivityServerDiagnosticListenerTests.WebSocket_Subscription_Should_Be_Ok_When_Server_Completes_Default.snap @@ -0,0 +1,286 @@ +{ + "source": { + "name": "HotChocolate.Fusion.Diagnostics" + }, + "activities": [ + { + "OperationName": "Microsoft.AspNetCore.Hosting.HttpRequestIn", + "DisplayName": "GET /graphql/{**slug}", + "Kind": "Server", + "Status": "Unset", + "tags": [ + { + "Key": "server.address", + "Value": "localhost" + }, + { + "Key": "server.port", + "Value": 5000 + }, + { + "Key": "http.request.method", + "Value": "GET" + }, + { + "Key": "url.scheme", + "Value": "http" + }, + { + "Key": "url.path", + "Value": "/graphql" + }, + { + "Key": "network.protocol.version", + "Value": "1.1" + }, + { + "Key": "http.route", + "Value": "/graphql/{**slug}" + }, + { + "Key": "http.response.status_code", + "Value": 101 + } + ], + "event": [], + "activities": [ + { + "OperationName": "GraphQL Operation", + "DisplayName": "subscription", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "request" + }, + { + "Key": "server.address", + "Value": "localhost" + }, + { + "Key": "server.port", + "Value": 5000 + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + } + ], + "event": [ + { + "Name": "AddedOperationPlanToCache", + "Tags": [] + }, + { + "Name": "AddedDocumentToCache", + "Tags": [] + } + ], + "activities": [ + { + "OperationName": "GraphQL Document Validation", + "DisplayName": "GraphQL Document Validation", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "validate" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + } + ], + "event": [] + }, + { + "OperationName": "GraphQL Operation Planning", + "DisplayName": "GraphQL Operation Planning", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "plan" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + } + ], + "event": [] + }, + { + "OperationName": "GraphQL Subscription Event", + "DisplayName": "GraphQL Subscription Event", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "subscription_event" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + }, + { + "Key": "graphql.subscription.id", + "Value": "" + } + ], + "event": [] + } + ] + }, + { + "OperationName": "GraphQL Step Execution", + "DisplayName": "GraphQL Step Execution", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "step_execute" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + }, + { + "Key": "graphql.operation.step.id", + "Value": "1" + }, + { + "Key": "graphql.operation.step.kind", + "Value": "operation" + }, + { + "Key": "graphql.operation.step.plan.id", + "Value": "bb94346d204b2f13a21dcba79a06b47065115db28b9ba2c04bce67382b50cef0" + }, + { + "Key": "graphql.source_schema.name", + "Value": "a" + }, + { + "Key": "graphql.source_schema.operation.name", + "Value": "OnMessageSubscription_ef859b98_1" + }, + { + "Key": "graphql.source_schema.operation.hash", + "Value": "sha256:f9abddbb651ad8e118793eb7c4a7c8db7fe43cae95faba710a0ff35b26b17136" + } + ], + "event": [] + }, + { + "OperationName": "System.Net.Http.HttpRequestOut", + "DisplayName": "System.Net.Http.HttpRequestOut", + "Kind": "Client", + "Status": "Unset", + "tags": [ + { + "Key": "http.request.method", + "Value": "POST" + }, + { + "Key": "server.address", + "Value": "localhost" + }, + { + "Key": "server.port", + "Value": 5000 + }, + { + "Key": "url.full", + "Value": "http://localhost:5000/graphql" + }, + { + "Key": "http.response.status_code", + "Value": 200 + } + ], + "event": [], + "activities": [ + { + "OperationName": "Microsoft.AspNetCore.Hosting.HttpRequestIn", + "DisplayName": "POST /graphql/{**slug}", + "Kind": "Server", + "Status": "Unset", + "tags": [ + { + "Key": "server.address", + "Value": "localhost" + }, + { + "Key": "server.port", + "Value": 5000 + }, + { + "Key": "http.request.method", + "Value": "POST" + }, + { + "Key": "url.scheme", + "Value": "http" + }, + { + "Key": "url.path", + "Value": "/graphql" + }, + { + "Key": "network.protocol.version", + "Value": "1.1" + }, + { + "Key": "http.route", + "Value": "/graphql/{**slug}" + }, + { + "Key": "http.response.status_code", + "Value": 200 + } + ], + "event": [] + } + ] + } + ] + } + ] +} diff --git a/src/HotChocolate/Fusion/test/Fusion.Diagnostics.Tests/__snapshots__/FusionActivityServerDiagnosticListenerTests.WebSocket_Subscription_Should_Be_Ok_When_Server_Completes_Default_NET11_0.snap b/src/HotChocolate/Fusion/test/Fusion.Diagnostics.Tests/__snapshots__/FusionActivityServerDiagnosticListenerTests.WebSocket_Subscription_Should_Be_Ok_When_Server_Completes_Default_NET11_0.snap new file mode 100644 index 00000000000..e6e5ffeacc2 --- /dev/null +++ b/src/HotChocolate/Fusion/test/Fusion.Diagnostics.Tests/__snapshots__/FusionActivityServerDiagnosticListenerTests.WebSocket_Subscription_Should_Be_Ok_When_Server_Completes_Default_NET11_0.snap @@ -0,0 +1,286 @@ +{ + "source": { + "name": "HotChocolate.Fusion.Diagnostics" + }, + "activities": [ + { + "OperationName": "Microsoft.AspNetCore.Hosting.HttpRequestIn", + "DisplayName": "GET /graphql/{**slug}", + "Kind": "Server", + "Status": "Unset", + "tags": [ + { + "Key": "server.address", + "Value": "localhost" + }, + { + "Key": "server.port", + "Value": 5000 + }, + { + "Key": "http.request.method", + "Value": "GET" + }, + { + "Key": "url.scheme", + "Value": "http" + }, + { + "Key": "url.path", + "Value": "/graphql" + }, + { + "Key": "network.protocol.version", + "Value": "1.1" + }, + { + "Key": "http.response.status_code", + "Value": 101 + }, + { + "Key": "http.route", + "Value": "/graphql/{**slug}" + } + ], + "event": [], + "activities": [ + { + "OperationName": "GraphQL Operation", + "DisplayName": "subscription", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "request" + }, + { + "Key": "server.address", + "Value": "localhost" + }, + { + "Key": "server.port", + "Value": 5000 + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + } + ], + "event": [ + { + "Name": "AddedOperationPlanToCache", + "Tags": [] + }, + { + "Name": "AddedDocumentToCache", + "Tags": [] + } + ], + "activities": [ + { + "OperationName": "GraphQL Document Validation", + "DisplayName": "GraphQL Document Validation", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "validate" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + } + ], + "event": [] + }, + { + "OperationName": "GraphQL Operation Planning", + "DisplayName": "GraphQL Operation Planning", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "plan" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + } + ], + "event": [] + }, + { + "OperationName": "GraphQL Subscription Event", + "DisplayName": "GraphQL Subscription Event", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "subscription_event" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + }, + { + "Key": "graphql.subscription.id", + "Value": "" + } + ], + "event": [] + } + ] + }, + { + "OperationName": "GraphQL Step Execution", + "DisplayName": "GraphQL Step Execution", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "step_execute" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + }, + { + "Key": "graphql.operation.step.id", + "Value": "1" + }, + { + "Key": "graphql.operation.step.kind", + "Value": "operation" + }, + { + "Key": "graphql.operation.step.plan.id", + "Value": "bb94346d204b2f13a21dcba79a06b47065115db28b9ba2c04bce67382b50cef0" + }, + { + "Key": "graphql.source_schema.name", + "Value": "a" + }, + { + "Key": "graphql.source_schema.operation.name", + "Value": "OnMessageSubscription_ef859b98_1" + }, + { + "Key": "graphql.source_schema.operation.hash", + "Value": "sha256:f9abddbb651ad8e118793eb7c4a7c8db7fe43cae95faba710a0ff35b26b17136" + } + ], + "event": [] + }, + { + "OperationName": "System.Net.Http.HttpRequestOut", + "DisplayName": "System.Net.Http.HttpRequestOut", + "Kind": "Client", + "Status": "Unset", + "tags": [ + { + "Key": "http.request.method", + "Value": "POST" + }, + { + "Key": "server.address", + "Value": "localhost" + }, + { + "Key": "server.port", + "Value": 5000 + }, + { + "Key": "url.full", + "Value": "http://localhost:5000/graphql" + }, + { + "Key": "http.response.status_code", + "Value": 200 + } + ], + "event": [], + "activities": [ + { + "OperationName": "Microsoft.AspNetCore.Hosting.HttpRequestIn", + "DisplayName": "POST /graphql/{**slug}", + "Kind": "Server", + "Status": "Unset", + "tags": [ + { + "Key": "server.address", + "Value": "localhost" + }, + { + "Key": "server.port", + "Value": 5000 + }, + { + "Key": "http.request.method", + "Value": "POST" + }, + { + "Key": "url.scheme", + "Value": "http" + }, + { + "Key": "url.path", + "Value": "/graphql" + }, + { + "Key": "network.protocol.version", + "Value": "1.1" + }, + { + "Key": "http.response.status_code", + "Value": 200 + }, + { + "Key": "http.route", + "Value": "/graphql/{**slug}" + } + ], + "event": [] + } + ] + } + ] + } + ] +} diff --git a/src/HotChocolate/Fusion/test/Fusion.Diagnostics.Tests/__snapshots__/FusionActivityServerDiagnosticListenerTests.WebSocket_Subscription_Should_Be_Ok_When_Server_Completes_NET11_0.snap b/src/HotChocolate/Fusion/test/Fusion.Diagnostics.Tests/__snapshots__/FusionActivityServerDiagnosticListenerTests.WebSocket_Subscription_Should_Be_Ok_When_Server_Completes_NET11_0.snap new file mode 100644 index 00000000000..d7c24ff2863 --- /dev/null +++ b/src/HotChocolate/Fusion/test/Fusion.Diagnostics.Tests/__snapshots__/FusionActivityServerDiagnosticListenerTests.WebSocket_Subscription_Should_Be_Ok_When_Server_Completes_NET11_0.snap @@ -0,0 +1,313 @@ +{ + "source": { + "name": "HotChocolate.Fusion.Diagnostics" + }, + "activities": [ + { + "OperationName": "Microsoft.AspNetCore.Hosting.HttpRequestIn", + "DisplayName": "GET /graphql/{**slug}", + "Kind": "Server", + "Status": "Unset", + "tags": [ + { + "Key": "server.address", + "Value": "localhost" + }, + { + "Key": "server.port", + "Value": 5000 + }, + { + "Key": "http.request.method", + "Value": "GET" + }, + { + "Key": "url.scheme", + "Value": "http" + }, + { + "Key": "url.path", + "Value": "/graphql" + }, + { + "Key": "network.protocol.version", + "Value": "1.1" + }, + { + "Key": "http.response.status_code", + "Value": 101 + }, + { + "Key": "http.route", + "Value": "/graphql/{**slug}" + } + ], + "event": [], + "activities": [ + { + "OperationName": "GraphQL Operation", + "DisplayName": "subscription", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "request" + }, + { + "Key": "server.address", + "Value": "localhost" + }, + { + "Key": "server.port", + "Value": 5000 + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + } + ], + "event": [ + { + "Name": "AddedOperationPlanToCache", + "Tags": [] + }, + { + "Name": "AddedDocumentToCache", + "Tags": [] + } + ], + "activities": [ + { + "OperationName": "GraphQL Document Validation", + "DisplayName": "GraphQL Document Validation", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "validate" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + } + ], + "event": [] + }, + { + "OperationName": "GraphQL Operation Planning", + "DisplayName": "GraphQL Operation Planning", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "plan" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + } + ], + "event": [] + }, + { + "OperationName": "GraphQL Operation Execution", + "DisplayName": "GraphQL Operation Execution", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "execute" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + } + ], + "event": [], + "activities": [ + { + "OperationName": "GraphQL Step Execution", + "DisplayName": "GraphQL Step Execution", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "step_execute" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + }, + { + "Key": "graphql.operation.step.id", + "Value": "1" + }, + { + "Key": "graphql.operation.step.kind", + "Value": "operation" + }, + { + "Key": "graphql.operation.step.plan.id", + "Value": "bb94346d204b2f13a21dcba79a06b47065115db28b9ba2c04bce67382b50cef0" + }, + { + "Key": "graphql.source_schema.name", + "Value": "a" + }, + { + "Key": "graphql.source_schema.operation.name", + "Value": "OnMessageSubscription_ef859b98_1" + }, + { + "Key": "graphql.source_schema.operation.hash", + "Value": "sha256:f9abddbb651ad8e118793eb7c4a7c8db7fe43cae95faba710a0ff35b26b17136" + } + ], + "event": [] + } + ] + }, + { + "OperationName": "GraphQL Subscription Event", + "DisplayName": "GraphQL Subscription Event", + "Kind": "Internal", + "Status": "Ok", + "tags": [ + { + "Key": "graphql.processing.type", + "Value": "subscription_event" + }, + { + "Key": "graphql.operation.type", + "Value": "subscription" + }, + { + "Key": "graphql.operation.name", + "Value": "OnMessageSubscription" + }, + { + "Key": "graphql.document.hash", + "Value": "md5:ef859b98c9d8c17038c8fd9aeecdb1e2" + }, + { + "Key": "graphql.subscription.id", + "Value": "" + } + ], + "event": [] + } + ] + }, + { + "OperationName": "System.Net.Http.HttpRequestOut", + "DisplayName": "System.Net.Http.HttpRequestOut", + "Kind": "Client", + "Status": "Unset", + "tags": [ + { + "Key": "http.request.method", + "Value": "POST" + }, + { + "Key": "server.address", + "Value": "localhost" + }, + { + "Key": "server.port", + "Value": 5000 + }, + { + "Key": "url.full", + "Value": "http://localhost:5000/graphql" + }, + { + "Key": "http.response.status_code", + "Value": 200 + } + ], + "event": [], + "activities": [ + { + "OperationName": "Microsoft.AspNetCore.Hosting.HttpRequestIn", + "DisplayName": "POST /graphql/{**slug}", + "Kind": "Server", + "Status": "Unset", + "tags": [ + { + "Key": "server.address", + "Value": "localhost" + }, + { + "Key": "server.port", + "Value": 5000 + }, + { + "Key": "http.request.method", + "Value": "POST" + }, + { + "Key": "url.scheme", + "Value": "http" + }, + { + "Key": "url.path", + "Value": "/graphql" + }, + { + "Key": "network.protocol.version", + "Value": "1.1" + }, + { + "Key": "http.response.status_code", + "Value": 200 + }, + { + "Key": "http.route", + "Value": "/graphql/{**slug}" + } + ], + "event": [] + } + ] + } + ] + } + ] +} diff --git a/website/content/docs/fusion/migration/migrate-from-16-6-to-16-7.md b/website/content/docs/fusion/migration/migrate-from-16-6-to-16-7.md index 892365ee9a0..7ed684805ff 100644 --- a/website/content/docs/fusion/migration/migrate-from-16-6-to-16-7.md +++ b/website/content/docs/fusion/migration/migrate-from-16-6-to-16-7.md @@ -1,10 +1,30 @@ --- title: Migrate Hot Chocolate Fusion from 16.6 to 16.7 -description: "Migration guide for Hot Chocolate Fusion v16.6 to v16.7: replace raw condition masks with ConditionFlags and configure wide operation limits." +description: "Migration guide for Hot Chocolate Fusion v16.6 to v16.7: implement the new WebSocket connection initialization diagnostic event, replace raw condition masks with ConditionFlags, and configure wide operation limits." --- Update every Hot Chocolate Fusion package in the application to version 16.7 before applying these changes. +# Breaking changes + +Things that have been removed or had a change in behavior that may cause your code not to compile or lead to unexpected behavior at runtime if not addressed. + +## IServerDiagnosticEvents gained a WebSocket connection initialization event + +`IServerDiagnosticEvents` has a new `WebSocketConnectionInitialized` member. The gateway raises it once per WebSocket session, for both the `graphql-transport-ws` and the legacy `graphql-ws` protocol, after the client's connection initialization message has been accepted. + +Listeners that derive from `ServerDiagnosticEventListener` need no change, because the base class provides a virtual no-op. Types that implement `IServerDiagnosticEvents` directly have to implement the new member: + +```csharp +public void WebSocketConnectionInitialized( + ISocketSession session, + IOperationMessagePayload connectionInitMessage) +{ +} +``` + +The payload of `connectionInitMessage` is only valid for the duration of the call. Read out any value that is needed later inside the callback, for example onto `ISocketConnection.Features`. + # Deprecations ## Raw condition masks replaced by ConditionFlags diff --git a/website/content/docs/hotchocolate/migrating/migrate-from-16-6-to-16-7.md b/website/content/docs/hotchocolate/migrating/migrate-from-16-6-to-16-7.md index 0e5694a34cc..f8a3ae84420 100644 --- a/website/content/docs/hotchocolate/migrating/migrate-from-16-6-to-16-7.md +++ b/website/content/docs/hotchocolate/migrating/migrate-from-16-6-to-16-7.md @@ -1,11 +1,31 @@ --- title: Migrate Hot Chocolate from 16.6 to 16.7 metaTitle: "Hot Chocolate 16.7 Migration Guide" -description: "Migration guide for Hot Chocolate v16.6 to v16.7: replace raw condition masks with ConditionFlags and configure wide operation limits." +description: "Migration guide for Hot Chocolate v16.6 to v16.7: implement the new WebSocket connection initialization diagnostic event, replace raw condition masks with ConditionFlags, and configure wide operation limits." --- Update every `HotChocolate.*` package in the application to version 16.7 before applying these changes. +# Breaking changes + +Things that have been removed or had a change in behavior that may cause your code not to compile or lead to unexpected behavior at runtime if not addressed. + +## IServerDiagnosticEvents gained a WebSocket connection initialization event + +`IServerDiagnosticEvents` has a new `WebSocketConnectionInitialized` member. It is raised once per WebSocket session, for both the `graphql-transport-ws` and the legacy `graphql-ws` protocol, after the client's connection initialization message has been accepted. + +Listeners that derive from `ServerDiagnosticEventListener` need no change, because the base class provides a virtual no-op. Types that implement `IServerDiagnosticEvents` directly have to implement the new member: + +```csharp +public void WebSocketConnectionInitialized( + ISocketSession session, + IOperationMessagePayload connectionInitMessage) +{ +} +``` + +The payload of `connectionInitMessage` is only valid for the duration of the call. Read out any value that is needed later inside the callback, for example onto `ISocketConnection.Features`. + # Deprecations ## Raw condition masks replaced by ConditionFlags