I have written Azure function v1 using C# (library project) with Aspect (AOP) for Logging. I'm not getting exception in catch block.
Catch an exception thrown by an async method
I have same problem discussed above, however, Azure Function Run method is Async Task and its exception handling same as async void. Not sure where is a problem? assuming this is function SDK issue.
Azure Function
public static class PingFunction
{
[LoggerAspect]
[FunctionName("PingFunction")]
public static async Task<HttpResponseMessage> Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)]HttpRequestMessage req, TraceWriter log)
{
string name = string.Empty;
log.Info("C# HTTP trigger function processed a request.");
SomeService someService = new SomeService();
await someService.DoSomething();
return req.CreateResponse(HttpStatusCode.OK, "Hello " + name);
}
}
public class SomeService
{
public async Task DoSomething()
{
await Task.Delay(1000);
throw new Exception("Exception from Service");
}
}
Logger Aspect (MrAdvise)
public class LoggerAspectAttribute : Attribute, IMethodAdvice
{
public void Advise(MethodAdviceContext context)
{
//Logger initilizer here
Console.WriteLine($"{context.TargetType.Name} started...");
try
{
context.Proceed(); // this calls the original method
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
finally
{
Console.WriteLine($"{context.TargetType.Name} completed...");
}
}
}
Workaround
When I removed Async-await from Azure function and call async method by "GetAwaiter().GetResult()", then it works.
public static HttpResponseMessage Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)]HttpRequestMessage req, TraceWriter log)
{
string name = string.Empty;
log.Info("C# HTTP trigger function processed a request.");
SomeService someService = new SomeService();
someService.DoSomething().GetAwaiter().GetResult();
return req.CreateResponse(HttpStatusCode.OK, "Hello " + name);
}
Task.GetAwaiter().GetResult() methods cause the potential for deadlock issues and should be avoided in favor of async/await.
My function process millions for events per-day. Is it the right solution if this is FunctionSDK issue or something else?
you need to write an async advice, such as:
public class LoggerAspectAttribute : Attribute, IMethodAsyncAdvice
{
public async Task Advise(MethodAsyncAdviceContext context)
{
//Logger initilizer here
Console.WriteLine($"{context.TargetType.Name} started...");
try
{
await context.ProceedAsync(); // this calls the original method
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
finally
{
Console.WriteLine($"{context.TargetType.Name} completed...");
}
}
}
EDIT : and yes, it works with Mr Advice :)
Related
Let say my services are consuming a 3rd party API by using NSwag generated code. The purpose is just calling API, get result and track/handle exception if any as pseudocode below:
// Nswag generated code
class ApiClient: IApiClient
{
string GetData();
bool UploadData(data);
}
class Service1
{
IApiClient client;
ILogger logger;
void DoSth1()
{
try
{
var result = client.GetData();
} catch (Exception e)
{
HandleException(e); // additional logic
logger.Track(e);
}
}
}
class Service2
{
IApiClient client;
ILogger logger;
void DoSth2()
{
string result;
try
{
result = client.GetData();
} catch (Exception e)
{
HandleException(e); // additional logic
logger.Track(e);
}
HandleResult(result)
try
{
bool uploaded = client.UploadData(result);
} catch (Exception e)
{
HandleException(e); // additional logic
logger.Track(e);
}
}
}
These codes actually repeated over and over my services. I'm thinking about the way to move these logics into a new service, then handling error by using callback, for example with GetData():
class ApiService: IApiService
{
IApiClient apiClient;
string GetData(Action onSuccess, Action<Exception> onFailure)
{
return Handle(() => apiClient.GetData(), onSuccess, onFailure);
}
T Handle<T>(Action apiCall, Action onSuccess, Action<Exception> onFailure)
{
T result;
try
{
result = apiCall();
}
catch (Exception e)
{
onFailure.Invoke(e);
}
onSuccess.Invoke();
return result;
}
}
class Service1
{
IApiService service;
ILogger logger;
void DoSth1()
{
var result = service.GetData(
onFailure: (Exception e) =>
{
HandleException(e);
logger.Track(e);
}
)
}
}
This implementation worked well with my services.
The pros of this implementation are:
Reduce try...catch in services
Centralized the API calls
It seems like more cleaner to me.
The cons is about introducing new service, I get a new coupling between Service1, Service2 -IApiService and IApiService - IApiClient. That's all I can see.
My question: Is it a good approach to handle API result/exception? What are pros/cons that I've missed?
Thanks
I am exploring Azure Function running on .net 5 and I found out about the new middleware capabilities.
I have built a dummy middleware like this one:
public sealed class ExceptionLoggingMiddleware : IFunctionsWorkerMiddleware
{
private readonly ILogger<ExceptionLoggingMiddleware> m_logger;
public ExceptionLoggingMiddleware(ILogger<ExceptionLoggingMiddleware> logger)
{
m_logger = logger;
}
public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next)
{
try
{
await next(context);
}
catch (Exception unhandledException)
{
m_logger.LogCritical(unhandledException, "Unhandled exception caught: {UnhandledException}", unhandledException.Message);
}
}
}
In my use case, the Azure Function is an HTTP triggered function:
public sealed class StorageAccountsFunction
{
private readonly ILogger<StorageAccountsFunction> m_logger;
public StorageAccountsFunction
(
ILogger<StorageAccountsFunction> logger
)
{
m_logger = logger;
}
[Function("v1-post-storage-account")]
public async Task<HttpResponseData> CreateAsync
(
[HttpTrigger(AuthorizationLevel.Anonymous, "POST", Route = "v1/storage-accounts")]
HttpRequestData httpRequestData,
FunctionContext context
)
{
m_logger.LogInformation("Processing a request to create a new storage account");
throw new Exception("Oh no! Oh well..");
}
}
In my Function App running in-process on .net core 3.1, each Function had the responsibility of catching the unhandled exception (via a base class) and returned the appropriate HTTP status code.
I would like to have that logic sit in a middleware instead to have it centralized and avoid any future mistakes.
Question
The exception is caught by the middleware properly. However, I do not see how I can alter the response and return something more appropriate, instead of a 500 Internal Server Error that I get right now?
According to this issue, there is currently no official implementation regarding this, but they also mention a "hacky workaround" until the proper functionality is implemented directly into Azure functions
We created an extension method for FunctionContext:
internal static class FunctionUtilities
{
internal static HttpRequestData GetHttpRequestData(this FunctionContext context)
{
var keyValuePair = context.Features.SingleOrDefault(f => f.Key.Name == "IFunctionBindingsFeature");
var functionBindingsFeature = keyValuePair.Value;
var type = functionBindingsFeature.GetType();
var inputData = type.GetProperties().Single(p => p.Name == "InputData").GetValue(functionBindingsFeature) as IReadOnlyDictionary<string, object>;
return inputData?.Values.SingleOrDefault(o => o is HttpRequestData) as HttpRequestData;
}
internal static void InvokeResult(this FunctionContext context, HttpResponseData response)
{
var keyValuePair = context.Features.SingleOrDefault(f => f.Key.Name == "IFunctionBindingsFeature");
var functionBindingsFeature = keyValuePair.Value;
var type = functionBindingsFeature.GetType();
var result = type.GetProperties().Single(p => p.Name == "InvocationResult");
result.SetValue(functionBindingsFeature, response);
}
}
The usage in the middleware looks like this:
public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next)
{
try
{
await next(context);
}
catch (Exception ex)
{
if (ex.InnerException is *NameOfExceptionYouNeed* e)
{
var req = context.GetHttpRequestData();
var res = await req.ErrorResponseAsync(e.Message);
context.InvokeResult(res);
return;
}
throw;
}
}
This is natively supported now as of version 1.8.0 of Microsoft.Azure.Functions.Worker.
The FunctionContextHttpRequestExtensions class was introduced so now you can just
using Microsoft.Azure.Functions.Worker;
public class MyMiddleware : IFunctionsWorkerMiddleware
{
public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next)
{
// To access the RequestData
var req = await context.GetHttpRequestDataAsync();
// To set the ResponseData
var res = req!.CreateResponse();
await res.WriteStringAsync("Please login first", HttpStatusCode.Unauthorized);
context.GetInvocationResult().Value = res;
}
}
This code works for me. It is based on the example here: https://github.com/Azure/azure-functions-dotnet-worker/blob/main/samples/CustomMiddleware/ExceptionHandlingMiddleware.cs
public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next)
{
// Simple example which always fails. Use the following in an error condition
var httpReqData = await context.GetHttpRequestDataAsync();
if (httpReqData != null)
{
var newHttpResponse = httpReqData.CreateResponse(HttpStatusCode.InternalServerError);
await newHttpResponse.WriteAsJsonAsync(new { ResponseStatus = "Invocation failed!" }, newHttpResponse.StatusCode);
context.GetInvocationResult().Value = newHttpResponse;
}
}
In my AzureFunction code if an exception has occurred in my activity Function I see still runTimeStatus=Completed but in such a situation I would like to have runTimeStatus=Failed.
What should I do?
here is my Code:
[FunctionName("Durable_HttpStart")]
public static async Task<HttpResponseMessage> HttpStart(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")]HttpRequestMessage req,
[OrchestrationClient]DurableOrchestrationClient starter,
TraceWriter log)
{
// Function input comes from the request content.
string instanceId = await starter.StartNewAsync("Durable_Orchestrator", null);
log.Info($"Started orchestration with ID = '{instanceId}'.");
return starter.CreateCheckStatusResponse(req, instanceId);
}
[FunctionName("Durable_Orchestrator")]
public static async Task RunOrchestrator(
[OrchestrationTrigger] DurableOrchestrationContext context, TraceWriter log)
{
try
{
await context.CallActivityAsync<string>("Durable_Activity", "0");
}
catch (Exception e)
{
log.Info("ErrorOrchestrator");
}
}
[FunctionName("Durable_Activity")]
public static void DurableActivity([ActivityTrigger] string name, TraceWriter log)
{
try
{
logger = log;
DoSomething(logger);
}
catch (Exception ex)
{
log.Info("ErrorActivity");
}
}
just throw the exception in your Durable_Activity:
[FunctionName("Durable_Starter")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")]HttpRequest req,
[DurableClient] IDurableClient starter,
ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request.");
string instanceId = await starter.StartNewAsync("Durable_Orchestrator", request);
log.LogInformation($"Started orchestration with ID = '{instanceId}'.");
var checkStatusResponse = starter.CreateCheckStatusResponse(req, instanceId);
return checkStatusResponse;
}
[FunctionName("Durable_Orchestrator")]
public static async Task RunOrchestrator(
[OrchestrationTrigger] DurableOrchestrationContext context, TraceWriter log)
{
try
{
await context.CallActivityAsync<string>("Durable_Activity", "0");
}
catch (Exception e)
{
log.Info("ErrorOrchestrator");
throw;
}
}
[FunctionName("Durable_Activity")]
public static void DurableActivity([ActivityTrigger] string name, TraceWriter log)
{
try
{
logger = log;
DoSomething(logger);
}
catch (Exception ex)
{
log.Info("ErrorActivity");
throw;
}
}
I have a windows service that references the following code. My code that uses the below method contains a try..catch block but it doesn't seem to catch RefereshTokenException that is thrown in the below method. Obviously my understanding of async is not correct.
private async void RefreshTokens()
{
try
{
var cognito = new CognitoApi();
var response = cognito.TokenRefresh(_refreshToken);
if (response.HttpStatusCode == HttpStatusCode.OK)
{
_idToken = new AwsToken(response.AuthenticationResult.IdToken);
_accessToken = new AwsToken(response.AuthenticationResult.AccessToken);
}
else
{
await _signIn(_credentials.SiteId, _credentials.LocationId, null);
}
}
catch (NotAuthorizedException)
{
await _signIn(_credentials.SiteId, _credentials.LocationId, null);
}
catch (Exception ex)
{
throw new RefreshTokenException("Failed refreshing tokens.", ex);
}
}
This is the code that calls RefreshTokens
public async void Process(QueueMessage queueMessage, Action<QueueMessage> retryAction)
{
_processingCounter.Increment();
try
{
......
IAwsToken idToken = authenticationService.Tokens.IdToken; //This is the code that calls "RefreshTokens" method
........
}
catch (Exception ex)
{
//Code never reaches here...
_logger.Error("Error in ProcessMessage", ex);
}
_processingCounter.Decrement();
}
This is an async void. One of the main reasons to avoid async void methods is that you cannot handle the exceptions they throw.
Make it an async Task and await it in the caller.
Note that you then have the same issue in that caller, async void Process(...)
Make that an async Task as well and work your way up. async/await should form a chain, from your GUI or Controller down to an async I/O call.
I'm developping a web API with ASP.NET Core and I'm trying to implement a custom error handling middleware so I can throw standard exceptions that can be converted into a JSON response with the appropriate HTTP Status code.
For example if I do:
throw new NotFoundApiException("The object was not found");
I need it to be converted into:
StatusCode: 404
ContentType: application/json
ResponseBody: {"error": "The object was not found"}
Here is my middleware:
public class ErrorHandlingMiddleware
{
private readonly RequestDelegate next;
public ErrorHandlingMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext context)
{
try {
await next(context);
} catch (ApiException ex) {
await HandleExceptionAsync(context, ex);
}
}
private static Task HandleExceptionAsync(HttpContext context, ApiException exception)
{
var result = JsonConvert.SerializeObject(new { error = exception.Message });
context.Response.ContentType = "application/json";
context.Response.StatusCode = exception.httpStatusCode;
return context.Response.WriteAsync(result);
}
}
Exceptions
public class ApiException : System.Exception
{
private int _httpStatusCode = (int)HttpStatusCode.InternalServerError;
public ApiException() { }
public ApiException(string message): base(message) { }
public int httpStatusCode {
get { return this._httpStatusCode; }
}
}
public class NotFoundApiException : ApiException
{
private int _httpStatusCode = (int)HttpStatusCode.BadRequest;
public NotFoundApiException() { }
public NotFoundApiException(string message): base(message) { }
}
Startup
public void Configure(/*...*/)
{
loggerFactory.AddConsole();
app.UseMiddleware<ErrorHandlingMiddleware>();
app.UseMvc();
}
Controller action
[HttpGet("object/{guid}")]
public WebMessage Get(Guid guid)
{
throw new NotFoundApiException(string.Format("The object {0} was not found", guid));
//...
I can see the request entering my registered middleware but the exception is not catched and simply thrown as usual.
I'm suspecting a race condition or something similar, I don't know very much about them async functions actually.
Has someone got an idea why my exception is not catched ?
edit By continuing the execution with VisualStudio I can see the expected behavior: I'm finally getting my response.
Seems like the Exception is not really catched by the middleware but somehow processed afterwards.
My solution to this problem was to remove app.UseDeveloperExceptionPage(); in Startup.cs
In my case, I found that app.UseMiddleware<ExceptionHandlingMiddleware>(); should be at the top of Configure() method.
You can try also Exception filters.
(of course, filters are not so flexible like as error handling middleware, which is better in general case, but - at least for me - filters are working fine without any issues)
That's what I'm using:
public class ExceptionGlobalFilter : ExceptionFilterAttribute
{
private readonly ILogger logger;
public ExceptionGlobalFilter(ILoggerFactory lf)
{
logger = lf.CreateLogger("ExceptionGlobalFilter");
}
public override void OnException(ExceptionContext context)
{
var customObject = new CustomObject(context.Exception);
//TODO: Add logs
if (context.Exception is BadRequestException)
{
context.Result = new BadRequestObjectResult(customObject);
}
else if (context.Exception is NotFoundException)
{
context.Result = new NotFoundObjectResult(customObject);
}
else
{
context.Result = new OkObjectResult(customObject);
}
base.OnException(context);
}
public override async Task OnExceptionAsync(ExceptionContext context)
{
await base.OnExceptionAsync(context);
return;
}
}
Startup.cs:
services.AddMvc(config =>
{
config.Filters.Add(typeof(ExceptionGlobalFilter));
});
More info:
Introduction to Error Handling in ASP.NET Core
Exception filters
Filters
MVC Issue #5594
ExceptionHandlerMiddleware.cs
In my case app.UseDeveloperExceptionPage(); was written in the Startup after the exception handler middleware. The fix was simply by moving the exception handler middleware to be after it.
#Pierre, I have met the same issue here when using Middleware as the global exception handler. The issue was caused by my mistake to wrote an "async void" method, I have throwed an exception in the method named "NewException":
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
// GET api/values
[HttpGet]
public async Task<IActionResult> Get()
{
NewException();
return Ok("<h1>Hi, Welcome!</h1>");
}
private async void NewException()
{
throw new InvalidOperationException("WTF");
}
The exception [InvalidOperationException("WTF")] will not be catching by the Middleware, if I change the code snippet to :
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
// GET api/values
[HttpGet]
public async Task<IActionResult> Get()
{
await NewException();
return Ok("<h1>Hi, Welcome!</h1>");
}
private async Task NewException()
{
throw new InvalidOperationException("WTF");
}
The exception Middleware will catch it. Hope this help.