In a layered web application I want to move all error logging from the Domain and Data layers to the global exception handler, but I'm not sure what is the trade-off. I want to remove any logging call and replace it by a more specific Exception (custom if it's necessary) or remove the catching:
try{
. . .
}
catch
{
Logger.Error('Info'); // <-- remove this for a: throw new CustomException('Info', ex);
throw; // <-- then, remove this line
}
There is a configured Global Exception Handler as middle-ware in the WebAPI, then as part of the handler method I'm going to log any exception occurred
// Startup.cs
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseExceptionHandler(
error =>
{
GlobalExceptionHandler.ErrorHandling(error, env);
});
}
// GlobalExceptionHandler.cs
public static class GlobalExceptionHandler
{
public static void ErrorHandling(IApplicationBuilder errorApp, IHostingEnvironment env)
{
errorApp.Run(async context =>
{
.
.
.
Log.Current.Error(exception.Message, () => exception);
}
}
}
Could be a better approach to avoid duplicated logging records?
In the applications I build I like to use the approach you are suggesting. I'll post the middleware that I use:
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using MyProject.Interfaces;
namespace MyProject.Middlewares
{
public class ErrorReporterMiddleware
{
private readonly RequestDelegate RequestDelegate;
public ErrorReporterMiddleware(RequestDelegate requestDelegate)
{
RequestDelegate = requestDelegate ?? throw new ArgumentNullException(nameof(requestDelegate));
}
public async Task Invoke(HttpContext httpContext, IErrorReporter errorReporter)
{
try
{
await RequestDelegate(httpContext);
}
catch (Exception e)
{
await errorReporter?.CaptureAsync(e);
throw;
}
}
}
}
In this case IErrorReporter is an interface I have defined in the MyProject.Interfaces namespace. I use it to abstract the logging service:
using System;
using System.Threading.Tasks;
namespace MyProject.Interfaces
{
public interface IErrorReporter
{
Task CaptureAsync(Exception exception);
Task CaptureAsync(string message);
}
}
Then in the Startup.cs I just add the following line to the Configure method:
app.UseMiddleware<ErrorReporterMiddleware>();
Nothing special but I think it's a clean approach.
Related
I'm trying to set up global exception handling code in .NetCore 3.1 webpai
My goal is to log unhandled exception before the app exits, using log4net.
I tried following several tutorials, one using a filter, and several using middelware and when I'm done and test it the middleware never gets called when I throw an exception thusly.
I have a filter already (which is commented out for testing the middle ware in case they were interacting) which does work, but can't use IOC to load an instanve of ILogger
[HttpGet]
[Route( "/ThrowException" )]
public JqGridReturnCV ThrowException()
{
log.LogTrace( "AdStudentController::ThrowException() - in" );
throw new Exception( "This is a test Exception" );
log.LogTrace( "AdStudentController::ThrowException() - out" );
}
Here is my code for the middleware:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using log4net;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using StudentPortal4Api.Dto;
namespace StudentPortal4Api.Utilities
{
public class GlobalExceptionMiddleware
{
private readonly RequestDelegate next;
public readonly ILogger log;
public GlobalExceptionMiddleware( RequestDelegate _next, ILogger _log )
{
next = _next;
log = _log;
}
public async Task Invoke( HttpContext context )
{
try
{
await next( context );
}
catch ( Exception ex )
{
var response = context.Response;
response.ContentType = "application/json";
switch ( ex )
{
default:
// unhandled error
log.Log( LogLevel.Error, " GlobalException:" + ex.ToString() );
break;
}
throw;
}
}
}
public class ErrorDetails
{
public int StatusCode { get; set; }
public string Message { get; set; }
public override string ToString()
{
return JsonConvert.SerializeObject( this );
}
}
public static class ExceptionMiddlewareExtensions
{
public static void ConfigureExceptionHandler( this IApplicationBuilder app, ILogger logger )
{
app.UseExceptionHandler( appError =>
{
appError.Run( async context =>
{
context.Response.StatusCode = (int) HttpStatusCode.InternalServerError;
context.Response.ContentType = "application/json";
var contextFeature = context.Features.Get<IExceptionHandlerFeature>();
if ( contextFeature != null )
{
logger.LogError( $"Something went wrong: {contextFeature.Error}" );
await context.Response.WriteAsync( new ErrorDetails()
{
StatusCode = context.Response.StatusCode,
Message = "Internal Server Error."
}.ToString() );
}
} );
} );
}
}
}
and here is my configure method , where I suspect I'm doing something wrong in registering it
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure( IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory, ILogger log )
{
//code removed for clarity
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
//Code removed for clarity
// global error handler
app.UseMiddleware<GlobalExceptionMiddleware>();
app.ConfigureExceptionHandler( log );
}
}
}
anyone see what I'm doing wrong?
You have a order issue source
change to:
app.UseAuthorization();
// global error handler
app.UseMiddleware<GlobalExceptionMiddleware>(); //custom Middleware Must be before endpoints and after auth.
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
You will not need the app.UseExceptionHandler.
I have an application I would like to add middleware error handling to but the exception never seem to bubble up. I've read several articles about this having to do with async behavior but I can't see what I'm doing wrong.
For example this SO post (Exceptions not bubbling up to Error Handling Middleware?) is very similar but I already have async as that is how it was originally written before we added the middleware error handling.
I'll post what I think is relevant.
ExceptionMiddleware.cs:
public async Task Invoke(HttpContext context)
{
try
{
await _next(context);
}
catch (Exception ex)
{
//we never get here????
await HandleExceptionAsync(context, ex, _options);
}
}
APIService:
public async Task<Response<PaginationModel>> GetPagination(int result, int pageNumber,...)
{
_logger.GetPaginationInformation($"Enter with parameters result: {result},.....");
try
{
....do stuff
}
catch (Exception ex)
{
//we do get here
throw; //return CreateErrorMessage<PaginationModel>("GetPagination", ex);
}
}
Startup.cs:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseABCExceptionHandler(options => options.AddErrorDetails = FormatExceptionResponse);
app.UseCors();
// add http for Schema at default url /graphql
app.UseWebSockets();
app.UseGraphQLWebSockets<ISchema>();
app.UseGraphQL<ISchema>("/graphql");
app.UseGraphQLPlayground();
}
private void FormatExceptionResponse(HttpContext context, Exception exception, Response<PaginationModel> response)
{
response.message = exception.Message;
}
ExceptionMiddlewareExtensions.cs:
public static class ExceptionMiddlewareExtensions
{
public static IApplicationBuilder UseABCExceptionHandler(this IApplicationBuilder builder)
{
var options = new ExceptionOptions();
return builder.UseMiddleware<ExceptionMiddleware>(options);
}
public static IApplicationBuilder UseABCExceptionHandler(this IApplicationBuilder builder, Action<ExceptionOptions> configureOptions)
{
var options = new ExceptionOptions();
configureOptions(options);
return builder.UseMiddleware<ExceptionMiddleware>(options);
}
}
I set debug breakpoints and everything seems to register and all "hooks" seem to be set and execution flows as expected first through ExceptionMiddleware Invoke _next(context) then to ApiService GetPagination but even if I throw a hard exception or remove the try catch block in GetPagination it never flows back up to Invoke catch?
I'm sure this has something to do with lack of understanding how to handle globally with async Task but I follow the articles on it and it doesn't seem to matter??
Update
Based upon the comment from Andy I'm adding this information, it could be helpful.
GetPagination is NOT an API endpoint. It is a service class called by the GraphQL query.
GraphQL Query:
FieldAsync<Response...>(
"PaginationSearch",
"Returns paginated for specified search terms",
arguments: new QueryArguments(... { Name = "result" },
resolve: async context =>
{
var result = context.GetArgument<int>("result");
//Is this using statement introducing some unexpected behavior as it Disposes behind the scenes??
using (_logger.GetScope("PaginationSearch"))
{
return Service.GetPagination(result...);
}
}
);
Update 2
Moving the registration line to the bottom of the configure method as mentioned in the comments actually makes it so it doesn't flow through the middleware Invoke BUT moving it to the first line does.
To be clear at project start BOTH first line and last line it does flow through invoke. I'm specifically referring to execution when a graphql query is received.
When startup.cs has registration last line Middleware Invoke is not used
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
.....
app.UseABCExceptionHandler(options => options.AddErrorDetails = FormatExceptionResponse);
}
ExceptionMiddleware.cs:
{
try
{
await _next(context); //breakpoint is NOT hit when request received
}
catch (Exception ex)
{
await HandleExceptionAsync(context, ex, _options);
}
}
When startup.cs has registration line first Middleware Invoke is used
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseABCExceptionHandler(options => options.AddErrorDetails = FormatExceptionResponse);
.....
}
ExceptionMiddleware.cs:
{
try
{
await _next(context); //breakpoint **IS** hit when request received
}
catch (Exception ex)
{
await HandleExceptionAsync(context, ex, _options);
}
}
Also not sure if it matters but the API service that is registerd in startup.cs is a singleton.
services.AddSingleton<IAPIService, APIService>();
and the shared HTTP client (using HTTP Typed clients) is added to the services httpclient collection.
services.AddHttpClient<ISecurityClient, SecurityClient>();
I'm writing gRPC services using ASP.NET Core using GRPC.ASPNETCore.
I've tried to add an Exception Filter for gRPC methods like this
services.AddMvc(options =>
{
options.Filters.Add(typeof(BaseExceptionFilter));
});
or using the UseExceptionHandler extension method like this
app.UseExceptionHandler(configure =>
{
configure.Run(async e =>
{
Console.WriteLine("Exception test code");
});
});
But both of them are not working (not intercepting code).
Is it possible to add global exception handler for gRPC services in ASP.NET Core?
I don't want to write try-catch code wrapper for each method I want to call.
Add custom interceptor in Startup
services.AddGrpc(options =>
{
{
options.Interceptors.Add<ServerLoggerInterceptor>();
options.EnableDetailedErrors = true;
}
});
Create custom class.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Grpc.Core;
using Grpc.Core.Interceptors;
using Microsoft.Extensions.Logging;
namespace Systemx.WebService.Services
{
public class ServerLoggerInterceptor : Interceptor
{
private readonly ILogger<ServerLoggerInterceptor> _logger;
public ServerLoggerInterceptor(ILogger<ServerLoggerInterceptor> logger)
{
_logger = logger;
}
public override async Task<TResponse> UnaryServerHandler<TRequest, TResponse>(
TRequest request,
ServerCallContext context,
UnaryServerMethod<TRequest, TResponse> continuation)
{
//LogCall<TRequest, TResponse>(MethodType.Unary, context);
try
{
return await continuation(request, context);
}
catch (Exception ex)
{
// Note: The gRPC framework also logs exceptions thrown by handlers to .NET Core logging.
_logger.LogError(ex, $"Error thrown by {context.Method}.");
throw;
}
}
}
}
I am trying to add some middleware so that any unhandled exceptions I catch and log it but experiencing some difficulties in doing so. Not been able to find a lot on this and for some odd reason my code doesn't seem to be entering the catch block. Seems like it is gracefully handling this and even interrogating the dictionary I can't see the exception.
What I want to happen is, enter the catch block grab the exception and log the stack trace.
The code:
public class Startup
{
public void Configuration(IAppBuilder app)
{
var config = new HttpConfiguration();
app.Use(typeof(FooHandler));
app.UseWebApi(config);
}
}
public class FooHandler : OwinMiddleware
{
private static readonly ILog Logger = LogManager.GetLogger(typeof(FooHandler));
public FooHandler(OwinMiddleware next) : base(next)
{
}
public override async Task Invoke(IOwinContext context)
{
if (Logger.IsErrorEnabled)
{
try
{
await Next.Invoke(context);
}
catch (Exception ex)
{ // DOESN'T FALL INTO HERE!
Logger.Error(message, ex);
}
}
}
}
public class FooController : ApiController
{
public Task<IHttpActionResult> Get()
{
throw new Exception("Foo Bar");
}
}
This is because WebApi is handling the exception. You will need to handle exceptions thrown by Controllers in an ExceptionFilterAttribute
In my C# Web API, I'm trying to add a global exception handler. I've been using a custom global ExceptionFilterAttribute to handle the exception and return a HttpResponseMessage:
public override void OnException(HttpActionExecutedContext context)
{
...
const string message = "An unhandled exception was raised by the Web API.";
var httpResponseMessage = new HttpResponseMessage(HttpStatusCode.InternalServerError)
{
Content = new StringContent(message),
ReasonPhrase = message
};
context.Response = httpResponseMessage;
}
This has worked fine for handling exceptions thrown at the controller level.
However, during development we had an error thrown from our OWIN startup file due to a database connection issue, however, a standard IIS exception was returned, instead of going through the global exception handler, and the full HTML was returned to our API consumer.
I've tried a few different approaches to catch exceptions thrown in my OWIN startup:
Custom ApiControllerActionInvoker:
public class CustomActionInvoker : ApiControllerActionInvoker
{
public override Task<HttpResponseMessage> InvokeActionAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
{
var result = base.InvokeActionAsync(actionContext, cancellationToken);
if (result.Exception != null && result.Exception.GetBaseException() != null)
{
...
}
return result;
}
}
Custom ExceptionHandler:
public class CustomExceptionHandler : ExceptionHandler
{
public override void Handle(ExceptionHandlerContext context)
{
...
base.Handle(context);
}
public override bool ShouldHandle(ExceptionHandlerContext context)
{
return true;
}
}
Custom OwinMiddleware component:
public class CustomExceptionMiddleware : OwinMiddleware
{
public CustomExceptionMiddleware(OwinMiddleware next) : base(next)
{
}
public override async Task Invoke(IOwinContext context)
{
try
{
await Next.Invoke(context);
}
catch (Exception ex)
{
...
}
}
}
And finally just using Application_Error:
protected void Application_Error(object sender, EventArgs e)
{
...
}
But nothing seems to catch the exception.
Does anyone know of a way to catch the exception and return a HttpResponseMessage? Or if any of the approaches I've already tried should have worked?
Any help much appreciated.
I have an application that does this correctly. In my case I wrote a middleware class that always returns a message telling the caller that the service is unavailable because there was an error during startup. This class is called FailedSetupMiddleware in my solution. The outline of it looks like this:
public class FailedSetupMiddleware
{
private readonly Exception _exception;
public FailedSetupMiddleware(Exception exception)
{
_exception = exception;
}
public Task Invoke(IOwinContext context, Func<Task> next)
{
var message = ""; // construct your message here
return context.Response.WriteAsync(message);
}
}
In my Configuration class I have a try...catch block that configures the OWIN pipeline with only the FailedSetupMiddleware in the case where an exception was thrown during configuration.
My OWIN startup class looks like this:
[assembly: OwinStartup(typeof(Startup))]
public class Startup
{
public void Configuration(IAppBuilder app)
{
try
{
//
// various app.Use() statements here to configure
// OWIN middleware
//
}
catch (Exception ex)
{
app.Use(new FailedSetupMiddleware(ex).Invoke);
}
}
}