Reputation: 54887
I've created an Azure Function with an HTTP trigger. When I run it locally, I notice that uncaught exceptions return a clean async-aware stack trace in the HTTP response. However, calling ToString()
on the exception gives the old clunky exception format. Which component is it that does the clean async-aware stack trace formatting? Is it something we can use in our code? I'm running on Azure Functions Runtime v1, which runs on .NET Framework, not .NET Core.
public static class Function1
{
[FunctionName("Function1")]
public static async Task<HttpResponseMessage> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)]HttpRequestMessage req,
TraceWriter log)
{
await A();
return req.CreateResponse(HttpStatusCode.OK);
}
private static async Task A()
{
await B();
}
private static async Task B()
{
await C();
}
private static async Task C()
{
await D();
}
private static async Task D()
{
await Task.Delay(1);
throw new InvalidOperationException();
}
This is the clean async-aware stack trace returned in the HTTP response for the uncaught exception:
Microsoft.Azure.WebJobs.Host.FunctionInvocationException : Exception while executing function: Function1 ---> System.InvalidOperationException : Operation is not valid due to the current state of the object.
at async FunctionApp1.Function1.D()
at async FunctionApp1.Function1.C()
at async FunctionApp1.Function1.B()
at async FunctionApp1.Function1.A()
at async FunctionApp1.Function1.Run(HttpRequestMessage req,TraceWriter log)
at async Microsoft.Azure.WebJobs.Host.Executors.FunctionInvoker`2.InvokeAsync[TReflected,TReturnValue](Object instance,Object[] arguments)
at async Microsoft.Azure.WebJobs.Host.Executors.FunctionExecutor.InvokeAsync(IFunctionInvoker invoker,ParameterHelper parameterHelper,CancellationTokenSource timeoutTokenSource,CancellationTokenSource functionCancellationTokenSource,Boolean throwOnTimeout,TimeSpan timerInterval,IFunctionInstance instance)
at async Microsoft.Azure.WebJobs.Host.Executors.FunctionExecutor.ExecuteWithWatchersAsync(IFunctionInstance instance,ParameterHelper parameterHelper,TraceWriter traceWriter,CancellationTokenSource functionCancellationTokenSource)
at async Microsoft.Azure.WebJobs.Host.Executors.FunctionExecutor.ExecuteWithLoggingAsync(??)
at async Microsoft.Azure.WebJobs.Host.Executors.FunctionExecutor.ExecuteWithLoggingAsync(??)
And this is the result of calling ToString()
on the exception:
System.InvalidOperationException: Operation is not valid due to the current state of the object.
at FunctionApp1.Function1.<D>d__4.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
at FunctionApp1.Function1.<C>d__3.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
at FunctionApp1.Function1.<B>d__2.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
at FunctionApp1.Function1.<A>d__1.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
at FunctionApp1.Function1.<Run>d__0.MoveNext()
Upvotes: 2
Views: 1184
Reputation: 54887
This functionality is implemented in the ExceptionFormatter
class, part of the Azure WebJobs SDK itself. To avail of it, one can simply call the GetFormattedException
method:
try
{
await A();
return req.CreateResponse(HttpStatusCode.OK);
}
catch (Exception ex)
{
return req.CreateResponse(
HttpStatusCode.InternalServerError,
ExceptionFormatter.GetFormattedException(ex), // ← here
MediaTypeNames.Text.Plain);
}
Result:
System.InvalidOperationException : Operation is not valid due to the current state of the object.
at async FunctionApp1.Function1.D()
at async FunctionApp1.Function1.C()
at async FunctionApp1.Function1.B()
at async FunctionApp1.Function1.A()
at async FunctionApp1.Function1.Run(HttpRequestMessage req,TraceWriter log)
If you're not running on Azure (and don't want to reference that SDK), there's similar (and richer) functionality implemented in a NuGet package called Ben.Demystifier. Install the said package and call ToStringDemystified()
on the exception.
try
{
await A();
return req.CreateResponse(HttpStatusCode.OK);
}
catch (Exception ex)
{
return req.CreateResponse(
HttpStatusCode.InternalServerError,
ex.ToStringDemystified(), // ← here
MediaTypeNames.Text.Plain);
}
Result:
System.InvalidOperationException: Operation is not valid due to the current state of the object.
at async Task FunctionApp1.Function1.D() in ...
at async Task FunctionApp1.Function1.C() in ...
at async Task FunctionApp1.Function1.B() in ...
at async Task FunctionApp1.Function1.A() in ...
at async Task<HttpResponseMessage> FunctionApp1.Function1.Run(HttpRequestMessage req, TraceWriter log) in ...
Upvotes: 2