Skip to content

Log error when exception occurs during streaming #42283

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jun 22, 2022

Conversation

campersau
Copy link
Contributor

Log error when exception occurs during streaming

Logs the exception when there is an exception during streaming on the server.

Fixes #42050

@ghost ghost added area-signalr Includes: SignalR clients and servers community-contribution Indicates that the PR has been added by a community member labels Jun 18, 2022
@ghost
Copy link

ghost commented Jun 18, 2022

Thanks for your PR, @campersau. Someone from the team will get assigned to your PR shortly and we'll get it reviewed.

error = ErrorMessageHelper.BuildErrorMessage("An error occurred on the server while streaming results.", ex.InnerException ?? ex, _enableDetailedErrors);
}
catch (Exception ex)
{
// 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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure should this one. cancellation failures shouldn't be logged as an error, maybe debug.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It isn't see !. Or do you mean other OperationCanceledExceptions when !streamCts.IsCancellationRequested?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, @campersau. There is an !!!

It is surprising that this wasn't logged already. Are we sure this isn't logged anywhere else? This really should just be calling Log.FailedInvokingHubMethod(_logger, hubMethodInvocationMessage.Target, ex) if we observe that an Exception is thrown from a streaming hub method without the client cancelling the invocation.

I would assume the !streamCts.IsCancellationRequested by itself is sufficient to ensure that. I'm not sure about the ChannelClosedException. @BrennanConroy?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah good catch @campersau! I missed the ! too.

For the ChannelClosedException, we should have a test too right?

Copy link
Contributor Author

@campersau campersau Jun 19, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There isn't currently any test for the ChannelClosedException case, so I have added one which manually throws a ChannelClosedException.
Because from looking at the Channels implementation in runtime it looks like these don't throw ChannelClosedException from within WaitToReadAsync / TryRead but instead always throw the users exception from TryComplete(Exception ex).
Only ReadAsync would throw a ChannelClosedException as far as I can tell.

https://github.com/dotnet/runtime/blob/57bfe474518ab5b7cfe6bf7424a79ce3af9d6657/src/libraries/System.Threading.Channels/src/System/Threading/Channels/BoundedChannel.cs#L208-L210

https://github.com/dotnet/runtime/blob/57bfe474518ab5b7cfe6bf7424a79ce3af9d6657/src/libraries/System.Threading.Channels/src/System/Threading/Channels/UnboundedChannel.cs#L167-L169

For reference AsyncEnumerableAdapters only uses WaitToReadAsync / TryRead:

if (await _channel.WaitToReadAsync(_cancellationToken).ConfigureAwait(false) && _channel.TryRead(out var item))


Regarding: Log.FailedInvokingHubMethod
I can change it to use this instead, but maybe a different log might be better to distinguish between those two cases. The case where it would throw immediately vs. the case where it could throw mid reading. (The tests currently throw directly for the first read but it could also happen much later.)
Also Log.FailedInvokingHubMethod does not include the invocationId which might be useful as this exception might be logged much later.
In general the logs are quite inconsistent with the inclusion of the invocationId.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like ChannelClosedException was relevant many years ago in the first implementation of the feature: https://github.com/dotnet/aspnetcore/blame/1289636c3afa0e5f297df141359e4ba72dac6e70/src/SignalR/common/Shared/AsyncEnumerableAdapters.cs#L28

I'm not against a new log message, it's always helpful to have more specific logs when investigating issues (although you can argue here that the exception stack trace would give you enough information).

I would also be happy with adding the invocation ID to the FailedInvokingHubMethod log.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we remove the ChannelClosedException case? I guess there could still be custom Channel implementations which do throw a ChannelClosedException, so maybe no?


There are currently three proposed options for logging:

  1. Use a new logger message like FailedStreaming as currently proposed in the PR.
  2. Use the existing logger message FailedInvokingHubMethod.
  3. Use the existing logger message FailedInvokingHubMethod and add the invocationId.

I am fine with either of those, just wanted to bring up the discussion.

If we want to go with (3) then there are some other logger messages which currently don't have an invocationId so we could add it to them too for consistentcy. Here are all of these:

Log.HubMethodNotAuthorized(_logger, hubMethodInvocationMessage.Target);

Log.InvalidHubParameters(_logger, hubMethodInvocationMessage.Target, ex);

Log.FailedInvokingHubMethod(logger, hubMethodInvocationMessage.Target, ex);

Log.FailedInvokingHubMethod(_logger, hubMethodInvocationMessage.Target, ex);

Log.FailedInvokingHubMethod(_logger, hubMethodInvocationMessage.Target, ex);

Log.FailedInvokingHubMethod(_logger, hubMethodInvocationMessage.Target, ex);

Log.InvalidReturnValueFromStreamingMethod(_logger, descriptor.MethodExecutor.MethodInfo.Name);

Log.StreamingMethodCalledWithInvoke(_logger, hubMethodInvocationMessage);

Log.NonStreamingMethodCalledWithStream(_logger, hubMethodInvocationMessage);

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we remove the ChannelClosedException case?

I don't see any harm in keeping it.

There are currently three proposed options for logging:

I'm going to vote for adding a new log, it's more user friendly to see specific logs.

We can look at adding invocation IDs to existing logs in a separate change.

@@ -105,4 +105,7 @@ public static void ClosingStreamWithBindingError(ILogger logger, CompletionMessa

[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, "InvocationId {InvocationId}: Failed during streaming '{HubMethod}'.", EventName = "FailedStreaming")]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:

Suggested change
[LoggerMessage(25, LogLevel.Error, "InvocationId {InvocationId}: Failed during streaming '{HubMethod}'.", EventName = "FailedStreaming")]
[LoggerMessage(25, LogLevel.Error, "InvocationId '{InvocationId}': Failed during streaming '{HubMethod}'.", EventName = "FailedStreaming")]

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some have quotes:

[LoggerMessage(23, LogLevel.Debug, "Invocation ID '{InvocationId}' is already in use.", EventName = "InvocationIdInUse")]

[LoggerMessage(24, LogLevel.Debug, "CompletionMessage for invocation ID '{InvocationId}' received unexpectedly.", EventName = "UnexpectedCompletion")]

And some have no quotes:

[LoggerMessage(6, LogLevel.Trace, "InvocationId {InvocationId}: Streaming result of type '{ResultType}'.", EventName = "StreamingResult", SkipEnabledCheck = true)]

[LoggerMessage(7, LogLevel.Trace, "InvocationId {InvocationId}: Sending result of type '{ResultType}'.", EventName = "SendingResult", SkipEnabledCheck = true)]

[LoggerMessage(10, LogLevel.Debug, "Canceling stream for invocation {InvocationId}.", EventName = "CancelStream")]

I copied it from the ones without quotes :)
Let's wait if a new LoggerMessage is even needed based on the ongoing discussion above.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bike-shed time.

"Invocation ID {InvocationId}: Failed while sending stream items from hub method {HubMethod}."

@BrennanConroy BrennanConroy merged commit 7c68e77 into dotnet:main Jun 22, 2022
@ghost ghost added this to the 7.0-preview6 milestone Jun 22, 2022
@BrennanConroy
Copy link
Member

Thanks @campersau you're on a roll!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-signalr Includes: SignalR clients and servers community-contribution Indicates that the PR has been added by a community member
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Exceptions in SignalR streaming results are not reported in the server logs
6 participants