diff --git a/src/SignalR/server/Core/src/Internal/DefaultHubDispatcher.cs b/src/SignalR/server/Core/src/Internal/DefaultHubDispatcher.cs index b6f7ffd02a0069520374655fba622fe100342e78..49224d1de1e7b4fc0c5e278a5991df76ea420496 100644 --- a/src/SignalR/server/Core/src/Internal/DefaultHubDispatcher.cs +++ b/src/SignalR/server/Core/src/Internal/DefaultHubDispatcher.cs @@ -474,6 +474,7 @@ internal sealed partial class DefaultHubDispatcher<THub> : HubDispatcher<THub> w catch (ChannelClosedException ex) { // If the channel closes from an exception in the streaming method, grab the innerException for the error from the streaming method + Log.FailedStreaming(_logger, invocationId, descriptor.MethodExecutor.MethodInfo.Name, ex.InnerException ?? ex); error = ErrorMessageHelper.BuildErrorMessage("An error occurred on the server while streaming results.", ex.InnerException ?? ex, _enableDetailedErrors); } catch (Exception ex) @@ -481,6 +482,7 @@ internal sealed partial class DefaultHubDispatcher<THub> : HubDispatcher<THub> w // If the streaming method was canceled we don't want to send a HubException message - this is not an error case if (!(ex is OperationCanceledException && streamCts.IsCancellationRequested)) { + Log.FailedStreaming(_logger, invocationId, descriptor.MethodExecutor.MethodInfo.Name, ex); error = ErrorMessageHelper.BuildErrorMessage("An error occurred on the server while streaming results.", ex, _enableDetailedErrors); } } diff --git a/src/SignalR/server/Core/src/Internal/DefaultHubDispatcherLog.cs b/src/SignalR/server/Core/src/Internal/DefaultHubDispatcherLog.cs index f80a970935a36e717cf529c8a27ce6d2ffcd3b75..6394c478458e37748dc6dbded80e538737c9f85d 100644 --- a/src/SignalR/server/Core/src/Internal/DefaultHubDispatcherLog.cs +++ b/src/SignalR/server/Core/src/Internal/DefaultHubDispatcherLog.cs @@ -105,4 +105,7 @@ internal static partial class DefaultHubDispatcherLog [LoggerMessage(24, LogLevel.Debug, "CompletionMessage for invocation ID '{InvocationId}' received unexpectedly.", EventName = "UnexpectedCompletion")] public static partial void UnexpectedCompletion(ILogger logger, string invocationId); + + [LoggerMessage(25, LogLevel.Error, "Invocation ID {InvocationId}: Failed while sending stream items from hub method {HubMethod}.", EventName = "FailedStreaming")] + public static partial void FailedStreaming(ILogger logger, string invocationId, string hubMethod, Exception exception); } diff --git a/src/SignalR/server/SignalR/test/HubConnectionHandlerTestUtils/Hubs.cs b/src/SignalR/server/SignalR/test/HubConnectionHandlerTestUtils/Hubs.cs index 7ee5efc3b128157c7e47bcffae10580b487d4c73..d1ab0f3b3c06024a2ce54e4bd498dc04cc876c0a 100644 --- a/src/SignalR/server/SignalR/test/HubConnectionHandlerTestUtils/Hubs.cs +++ b/src/SignalR/server/SignalR/test/HubConnectionHandlerTestUtils/Hubs.cs @@ -691,6 +691,15 @@ public class StreamingHub : TestHub } } + public async IAsyncEnumerable<string> ExceptionAsyncEnumerable() + { + await Task.Yield(); + throw new Exception("Exception from async enumerable"); +#pragma warning disable CS0162 // Unreachable code detected + yield break; +#pragma warning restore CS0162 // Unreachable code detected + } + public async Task<IAsyncEnumerable<string>> CounterAsyncEnumerableAsync(int count) { await Task.Yield(); @@ -719,6 +728,20 @@ public class StreamingHub : TestHub return channel.Reader; } + public ChannelReader<int> ChannelClosedExceptionStream() + { + var channel = Channel.CreateUnbounded<int>(); + channel.Writer.TryComplete(new ChannelClosedException("ChannelClosedException from channel")); + return channel.Reader; + } + + public ChannelReader<int> ChannelClosedExceptionInnerExceptionStream() + { + var channel = Channel.CreateUnbounded<int>(); + channel.Writer.TryComplete(new ChannelClosedException(new Exception("ChannelClosedException from channel"))); + return channel.Reader; + } + public ChannelReader<int> ThrowStream() { throw new Exception("Throw from hub method"); diff --git a/src/SignalR/server/SignalR/test/HubConnectionHandlerTests.cs b/src/SignalR/server/SignalR/test/HubConnectionHandlerTests.cs index bce1522e597dd927ad1d8cfd93972b482e6a8484..8b35d46475909618795ee86469829d17066b27ef 100644 --- a/src/SignalR/server/SignalR/test/HubConnectionHandlerTests.cs +++ b/src/SignalR/server/SignalR/test/HubConnectionHandlerTests.cs @@ -2066,16 +2066,28 @@ public partial class HubConnectionHandlerTests : VerifiableLoggedTest } [Theory] - [InlineData(true)] - [InlineData(false)] - public async Task ReceiveCorrectErrorFromStreamThrowing(bool detailedErrors) + [InlineData(nameof(StreamingHub.ExceptionAsyncEnumerable), "Exception: Exception from async enumerable")] + [InlineData(nameof(StreamingHub.ExceptionAsyncEnumerable), null)] + [InlineData(nameof(StreamingHub.ExceptionStream), "Exception: Exception from channel")] + [InlineData(nameof(StreamingHub.ExceptionStream), null)] + [InlineData(nameof(StreamingHub.ChannelClosedExceptionStream), "ChannelClosedException: ChannelClosedException from channel")] + [InlineData(nameof(StreamingHub.ChannelClosedExceptionStream), null)] + [InlineData(nameof(StreamingHub.ChannelClosedExceptionInnerExceptionStream), "Exception: ChannelClosedException from channel")] + [InlineData(nameof(StreamingHub.ChannelClosedExceptionInnerExceptionStream), null)] + public async Task ReceiveCorrectErrorFromStreamThrowing(string streamMethod, string detailedError) { - using (StartVerifiableLog()) + bool ExpectedErrors(WriteContext writeContext) + { + return writeContext.LoggerName == "Microsoft.AspNetCore.SignalR.Internal.DefaultHubDispatcher" && + writeContext.EventId.Name == "FailedStreaming"; + } + + using (StartVerifiableLog(ExpectedErrors)) { var serviceProvider = HubConnectionHandlerTestUtils.CreateServiceProvider(builder => builder.AddSignalR(options => { - options.EnableDetailedErrors = detailedErrors; + options.EnableDetailedErrors = detailedError != null; }), LoggerFactory); var connectionHandler = serviceProvider.GetService<HubConnectionHandler<StreamingHub>>(); @@ -2085,14 +2097,14 @@ public partial class HubConnectionHandlerTests : VerifiableLoggedTest await client.Connected.DefaultTimeout(); - var messages = await client.StreamAsync(nameof(StreamingHub.ExceptionStream)); + var messages = await client.StreamAsync(streamMethod); Assert.Equal(1, messages.Count); var completion = messages[0] as CompletionMessage; Assert.NotNull(completion); - if (detailedErrors) + if (detailedError != null) { - Assert.Equal("An error occurred on the server while streaming results. Exception: Exception from channel", completion.Error); + Assert.Equal($"An error occurred on the server while streaming results. {detailedError}", completion.Error); } else {