ASP.Net Core MVC Global Error Handling & Logging in 5 minute

asp.net core web api global exception handler asp.net core exception handling middleware asp.net core 2.0 exception handling asp.net core 2.0 web api exception handling asp.net core web api error handling middleware asp.net core 2.1 exception handling asp.net core exception logging middleware asp.net core 2.1 web api exception handling

Catch every error in applicatoin and log either to a file or in database is a good idiea. But how to catch every error in application and show a proper error page or page not found page? We will try to see how easily we can acheive it and how to log error in a file by using the microsoft provided logger feature to log error in a text (.txt) file.

If you will open the startup.cs file in any .Net Core MVC application, it already have the code to show the error page with some static message, but it don't have any code to get the error detail.

Go to configure method in startup.cs file, it will have:

if (env.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
    app.UseDatabaseErrorPage();
}
else
{
    app.UseExceptionHandler("/Home/Error");
    app.UseHsts();
}

It also have an action method in HomeController to hande the error:

[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
    return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}

To show the error it have a view in shared folder with name 'Error.cshtml'.

Go ahead and remove all the above code, we don't need it, we will write our own code for it, which is very simple and easy.

Catching all errors and page not found

First we will see how to catch errors and show error or page not found page then finally we will try to log the error in a file.

Create a separate controller name it ErrorController and add following code in it:

public class ErrorController : Controller
{        
    [HttpGet("/Error")]
    public IActionResult Index()
    {
        var exception = HttpContext.Features.Get<IExceptionHandlerFeature>();           
        return View(exception);
    }

    [HttpGet("/Error/NotFound/{statusCode}")]
    public IActionResult NotFound(int statusCode)
    {
        var exception = HttpContext.Features.Get<IStatusCodeReExecuteFeature>();
        return View("NotFound", exception.OriginalPath);
    }
}

Now we need to create the view for these two action methods, here is the code for error view:

@model Microsoft.AspNetCore.Diagnostics.IExceptionHandlerFeature
@{
    ViewData["Title"] = "Index";
    Layout = "~/Views/Shared/_Layout.cshtml";
}
<div class="row">
    <div class="col-12">
        <h1>Error</h1>
        <hr />
    </div>
</div>
<div class="row">
    <div class="col-12">
        <h3>@Model.Error.Message</h3>
    </div>
</div>
<div class="row">
    <div class="col-12">
        <p>@Model.Error.StackTrace</p>
        <p>@Model.Error.InnerException</p>
    </div>
</div>

try to divide by zero error

You can design this page according to your need.

Try to run the application and search a page which doesn't exist, so expecting, it should show the page not found error, but it shows following error page, then I realize that I have not created view for NotFound action, it proved both error and page not found are working as it should be.

If view page is not created error

Create the view for page Not found:

@model string
@{
    ViewData["Title"] = "NotFound";
    Layout = "~/Views/Shared/_Layout.cshtml";
}

<h1 class="text-danger">404 - Page Not Found</h1>
<hr />

<h3>The requested URL @Model was not found!</h3>

Now we need to setup the startup.cs configure method so it can call these action for error or for page not found. Open Startup.cs file on the root and change the code to this"

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    /* Some other code */
    app.UseExceptionHandler("/Error"); // Added to handle error
    app.UseStatusCodePagesWithReExecute("/Error/NotFound/{0}");  // Added to handle Page Not Found

    app.UseMvc(routes =>
    {
        routes.MapRoute(
            name: "default",
            template: "{controller=Home}/{action=Index}/{id?}");
    });
}

That's it, run the code and try to divide by zero in any action method or any other kind of error, it will show the error page with complete error detail, which we can change before deploying to the production.

Type any URL which doesn't exists, and it will show the page not found error.

Note: we added code in both the action method to get the exception detail but we are not doing anything, if you already have any service to log the error in your database, call that one and get the detail from exception, that would be easy so no need to discuss that.

Page Not found error for route doesn't exist

If you want to show page not found from any controller where you try to find something, but that doesn't exist, then you can return view("NotFound", "some string message"); then you can move the NotFound View in shared folder.

Logging Error To Text File

Microsoft provided ILogger but it don't have any way to log the error into file so we will use Serilog to log error into file.

Open package manager and run the following command to install Serilog:

Install-Package Serilog.Extensions.Logging.File -Version 1.1.0

Once you install it, we need to configure it, so open one more time startup.cs file

    public void Configure(IApplicationBuilder app, IHostingEnvironment env,
            ILoggerFactory loggerFactory) // Added one more parameter ILoggerFactory
    {
        /* Some other code */
        loggerFactory.AddConsole(Configuration.GetSection("Logging"));   // Read Level of logging
        loggerFactory.AddDebug(); // Log everything
        loggerFactory.AddFile($"{env.WebRootPath}/Logs/{DateTime.Today:MMM_yyyy}.txt");  // FileName to log error in a folder logs

        app.UseExceptionHandler("/Error");
        app.UseStatusCodePagesWithReExecute("/Error/NotFound/{0}");

        app.UseMvc(routes =>
        {
            routes.MapRoute(
                name: "default",
                template: "{controller=Home}/{action=Index}/{id?}");
        });
    }

See the first three line of code, we are reading Logging section from appsettings.json

It have value for production, means log level warning or higher:

"Logging": {
   "LogLevel": {
      "Default": "Warning"
   }
},

But developer version have this value:

"Logging": {
  "LogLevel": {
    "Default": "Debug",
    "System": "Information",
    "Microsoft": "Information"
  }
}

It will log too much, all the request and response which we don't want, so change them all to Warning so it will log only the warning, error or critical

Here is the list of log level and their description, so you can use according to your need:

  • Critical 5
    Logs that describe an unrecoverable application or system crash, or a catastrophic failure that requires immediate attention.

  • Debug 1
    Logs that are used for interactive investigation during development. These logs should primarily contain information useful for debugging and have no long-term value.

  • Error 4
    Logs that highlight when the current flow of execution is stopped due to a failure. These should indicate a failure in the current activity, not an application-wide failure.

  • Information 2
    Logs that track the general flow of the application. These logs should have long-term value.

  • None 6
    Not used for writing log messages. Specifies that a logging category should not write any messages.

  • Trace 0
    Logs that contain the most detailed messages. These messages may contain sensitive application data. These messages are disabled by default and should never be enabled in a production environment.

  • Warning 3
    Logs that highlight an abnormal or unexpected event in the application flow, but do not otherwise cause the application execution to stop.

How can log all the possible errors from any controller or any page not found, so go ahead and change the error controller to:

public class ErrorController : Controller
{
    readonly ILogger _logger;
    public ErrorController(ILogger<ErrorController> logger) => _logger = logger;

    [HttpGet("/Error")]
    public IActionResult Index()
    {
        var exception = HttpContext.Features.Get<IExceptionHandlerFeature>();           
        return View(exception);
    }

    [HttpGet("/Error/NotFound/{statusCode}")]
    public IActionResult NotFound(int statusCode)
    {
        var exception = HttpContext.Features.Get<IStatusCodeReExecuteFeature>();
        _logger.LogError($"PAGE NOT FOUND: {exception.OriginalPath}");
        return View("NotFound", exception.OriginalPath);
    }
}

That's it, we are done. Run the applicaiton and try to create the error of any type from anywhere in application, it will be logged to your file.

Ali Adravi Having 13+ years of experience in Microsoft Technologies (C#, ASP.Net, MVC and SQL Server). Worked with Metaoption LLC, for more than 9 years and still with the same company. Always ready to learn new technologies and tricks.
  • .net-core
  • error-handling
  • error-logging
By Ali Adravi On 30 Jan, 19  Viewed: 5,356