Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ internal static partial class GrpcServerLog
public static partial void ErrorExecutingServiceMethod(ILogger logger, string serviceMethod, Exception ex);

[LoggerMessage(3, LogLevel.Information, "Error status code '{StatusCode}' with detail '{Detail}' raised.", EventName = "RpcConnectionError")]
public static partial void RpcConnectionError(ILogger logger, StatusCode statusCode, string detail);
public static partial void RpcConnectionError(ILogger logger, StatusCode statusCode, string detail, Exception? debugException);

[LoggerMessage(4, LogLevel.Debug, "Reading message.", EventName = "ReadingMessage")]
public static partial void ReadingMessage(ILogger logger);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,9 @@ internal async Task ProcessHandlerErrorAsync(Exception ex, string method, bool i
if (ex is RpcException rpcException)
{
// RpcException is thrown by client code to modify the status returned from the server.
// Log the status and detail. Don't log the exception to reduce log verbosity.
GrpcServerLog.RpcConnectionError(Logger, rpcException.StatusCode, rpcException.Status.Detail);
// Log the status, detail and debug exception (if present).
// Don't log the RpcException itself to reduce log verbosity. All of its information is already captured.
GrpcServerLog.RpcConnectionError(Logger, rpcException.StatusCode, rpcException.Status.Detail, rpcException.Status.DebugException);

status = rpcException.Status;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,53 @@ public async Task HandleCallAsync_MessageThenError_MessageThenErrorReturned()
Assert.Equal("Exception was thrown by handler.", responseJson2.RootElement.GetProperty("error").GetString());
Assert.Equal(2, responseJson2.RootElement.GetProperty("code").GetInt32());

var exceptionWrite = TestSink.Writes.Single(w => w.EventId.Name == "ErrorExecutingServiceMethod");
Assert.Equal("Error when executing service method 'TestMethodName'.", exceptionWrite.Message);
Assert.Equal("Exception!", exceptionWrite.Exception.Message);

await callTask.DefaultTimeout();
}

[Fact]
public async Task HandleCallAsync_MessageThenRpcException_MessageThenErrorReturned()
{
// Arrange
var debugException = new Exception("Error!");
ServerStreamingServerMethod<JsonTranscodingGreeterService, HelloRequest, HelloReply> invoker = async (s, r, w, c) =>
{
await w.WriteAsync(new HelloReply { Message = $"Hello {r.Name} 1" });
throw new RpcException(new Status(StatusCode.Aborted, "Detail!", debugException));
};

var pipe = new Pipe();

var routeParameterDescriptors = new Dictionary<string, List<FieldDescriptor>>
{
["name"] = new List<FieldDescriptor>(new[] { HelloRequest.Descriptor.FindFieldByNumber(HelloRequest.NameFieldNumber) })
};
var descriptorInfo = TestHelpers.CreateDescriptorInfo(routeParameterDescriptors: routeParameterDescriptors);
var callHandler = CreateCallHandler(invoker, descriptorInfo: descriptorInfo);
var httpContext = TestHelpers.CreateHttpContext(bodyStream: pipe.Writer.AsStream());
httpContext.Request.RouteValues["name"] = "TestName!";

// Act
var callTask = callHandler.HandleCallAsync(httpContext);

// Assert
var line1 = await ReadLineAsync(pipe.Reader).DefaultTimeout();
using var responseJson1 = JsonDocument.Parse(line1!);
Assert.Equal("Hello TestName! 1", responseJson1.RootElement.GetProperty("message").GetString());

var line2 = await ReadLineAsync(pipe.Reader).DefaultTimeout();
using var responseJson2 = JsonDocument.Parse(line2!);
Assert.Equal("Detail!", responseJson2.RootElement.GetProperty("message").GetString());
Assert.Equal("Detail!", responseJson2.RootElement.GetProperty("error").GetString());
Assert.Equal((int)StatusCode.Aborted, responseJson2.RootElement.GetProperty("code").GetInt32());

var exceptionWrite = TestSink.Writes.Single(w => w.EventId.Name == "RpcConnectionError");
Assert.Equal("Error status code 'Aborted' with detail 'Detail!' raised.", exceptionWrite.Message);
Assert.Equal(debugException, exceptionWrite.Exception);

await callTask.DefaultTimeout();
}

Expand Down Expand Up @@ -143,6 +190,10 @@ public async Task HandleCallAsync_ErrorWithDetailedErrors_DetailedErrorResponse(
Assert.Equal("Exception was thrown by handler. Exception: Exception!", responseJson.RootElement.GetProperty("error").GetString());
Assert.Equal(2, responseJson.RootElement.GetProperty("code").GetInt32());

var exceptionWrite = TestSink.Writes.Single(w => w.EventId.Name == "ErrorExecutingServiceMethod");
Assert.Equal("Error when executing service method 'TestMethodName'.", exceptionWrite.Message);
Assert.Equal("Exception!", exceptionWrite.Exception.Message);

await callTask.DefaultTimeout();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -548,9 +548,10 @@ public async Task HandleCallAsync_RpcExceptionReturned_StatusReturned()
public async Task HandleCallAsync_RpcExceptionThrown_StatusReturned()
{
// Arrange
var debugException = new Exception("Error!");
UnaryServerMethod<JsonTranscodingGreeterService, HelloRequest, HelloReply> invoker = (s, r, c) =>
{
throw new RpcException(new Status(StatusCode.Unauthenticated, "Detail!"), "Message!");
throw new RpcException(new Status(StatusCode.Unauthenticated, "Detail!", debugException), "Message!");
};

var unaryServerCallHandler = CreateCallHandler(invoker);
Expand All @@ -567,6 +568,70 @@ public async Task HandleCallAsync_RpcExceptionThrown_StatusReturned()
Assert.Equal("Detail!", responseJson.RootElement.GetProperty("message").GetString());
Assert.Equal("Detail!", responseJson.RootElement.GetProperty("error").GetString());
Assert.Equal((int)StatusCode.Unauthenticated, responseJson.RootElement.GetProperty("code").GetInt32());

var exceptionWrite = TestSink.Writes.Single(w => w.EventId.Name == "RpcConnectionError");
Assert.Equal("Error status code 'Unauthenticated' with detail 'Detail!' raised.", exceptionWrite.Message);
Assert.Equal(debugException, exceptionWrite.Exception);
}

[Fact]
public async Task HandleCallAsync_OtherExceptionThrown_StatusReturned()
{
// Arrange
UnaryServerMethod<JsonTranscodingGreeterService, HelloRequest, HelloReply> invoker = (s, r, c) =>
{
throw new InvalidOperationException("Error!");
};

var unaryServerCallHandler = CreateCallHandler(invoker);
var httpContext = TestHelpers.CreateHttpContext();

// Act
await unaryServerCallHandler.HandleCallAsync(httpContext);

// Assert
Assert.Equal(500, httpContext.Response.StatusCode);

httpContext.Response.Body.Seek(0, SeekOrigin.Begin);
using var responseJson = JsonDocument.Parse(httpContext.Response.Body);
Assert.Equal("Exception was thrown by handler.", responseJson.RootElement.GetProperty("message").GetString());
Assert.Equal("Exception was thrown by handler.", responseJson.RootElement.GetProperty("error").GetString());
Assert.Equal((int)StatusCode.Unknown, responseJson.RootElement.GetProperty("code").GetInt32());

var exceptionWrite = TestSink.Writes.Single(w => w.EventId.Name == "ErrorExecutingServiceMethod");
Assert.Equal("Error when executing service method 'TestMethodName'.", exceptionWrite.Message);
Assert.Equal("Error!", exceptionWrite.Exception.Message);
}

[Fact]
public async Task HandleCallAsync_EnableDetailedErrors_OtherExceptionThrown_StatusReturned()
{
// Arrange
UnaryServerMethod<JsonTranscodingGreeterService, HelloRequest, HelloReply> invoker = (s, r, c) =>
{
throw new InvalidOperationException("Error!");
};

var unaryServerCallHandler = CreateCallHandler(
invoker,
serviceOptions: new GrpcServiceOptions { EnableDetailedErrors = true });
var httpContext = TestHelpers.CreateHttpContext();

// Act
await unaryServerCallHandler.HandleCallAsync(httpContext);

// Assert
Assert.Equal(500, httpContext.Response.StatusCode);

httpContext.Response.Body.Seek(0, SeekOrigin.Begin);
using var responseJson = JsonDocument.Parse(httpContext.Response.Body);
Assert.Equal("Exception was thrown by handler. InvalidOperationException: Error!", responseJson.RootElement.GetProperty("message").GetString());
Assert.Equal("Exception was thrown by handler. InvalidOperationException: Error!", responseJson.RootElement.GetProperty("error").GetString());
Assert.Equal((int)StatusCode.Unknown, responseJson.RootElement.GetProperty("code").GetInt32());

var exceptionWrite = TestSink.Writes.Single(w => w.EventId.Name == "ErrorExecutingServiceMethod");
Assert.Equal("Error when executing service method 'TestMethodName'.", exceptionWrite.Message);
Assert.Equal("Error!", exceptionWrite.Exception.Message);
}

[Fact]
Expand Down Expand Up @@ -1271,26 +1336,29 @@ private UnaryServerCallHandler<JsonTranscodingGreeterService, HelloRequest, Hell
UnaryServerMethod<JsonTranscodingGreeterService, HelloRequest, HelloReply> invoker,
CallHandlerDescriptorInfo? descriptorInfo = null,
List<(Type Type, object[] Args)>? interceptors = null,
GrpcJsonTranscodingOptions? jsonTranscodingOptions = null)
GrpcJsonTranscodingOptions? jsonTranscodingOptions = null,
GrpcServiceOptions? serviceOptions = null)
{
return CreateCallHandler(
invoker,
CreateServiceMethod("TestMethodName", HelloRequest.Parser, HelloReply.Parser),
descriptorInfo,
interceptors,
jsonTranscodingOptions);
jsonTranscodingOptions,
serviceOptions);
}

private UnaryServerCallHandler<JsonTranscodingGreeterService, TRequest, TResponse> CreateCallHandler<TRequest, TResponse>(
UnaryServerMethod<JsonTranscodingGreeterService, TRequest, TResponse> invoker,
Method<TRequest, TResponse> method,
CallHandlerDescriptorInfo? descriptorInfo = null,
List<(Type Type, object[] Args)>? interceptors = null,
GrpcJsonTranscodingOptions? jsonTranscodingOptions = null)
GrpcJsonTranscodingOptions? jsonTranscodingOptions = null,
GrpcServiceOptions? serviceOptions = null)
where TRequest : class, IMessage<TRequest>
where TResponse : class, IMessage<TResponse>
{
var serviceOptions = new GrpcServiceOptions();
serviceOptions ??= new GrpcServiceOptions();
if (interceptors != null)
{
foreach (var interceptor in interceptors)
Expand Down