Global Error Handling In ASP.NET Core App Using NLog

Introduction

NLog is a flexible and free logging platform for various .NET platforms, including .NET standard. NLog makes it easy to write to several targets. (database, file, console) and change the logging configuration on the fly.

You can refer to this URL to get more details about NLog.

We will write a middleware in the ASP.NET Core application to catch all the exceptions globally and use NLog to write errors into a text file. We can see all the actions step by step.

Create an ASP.NET Core Web API application

We can open Visual Studio and choose ASP.NET Core and API templates to create a Web API project.

ASP.NET Core

This template will create a Web API project in .NET Core 3.1. I am using the latest .NET SDK 3.1.

We can add the “NLog.Web.AspNetCore” library to the project using the NuGet package manager.

Create an interface “ILog” inside a new folder “Logging”. We will add all other logging-related files inside this folder.

ILog.cs

namespace GlobalExceptionHandler.Logging
{
    public interface ILog
    {
        void Information(string message);
        void Warning(string message);
        void Debug(string message);
        void Error(string message);
    }
}

Though we will handle only exceptions in this application, we have provided options for all other logging like Info, Warning, and Debug in the above interface.

We can create a “LogNLog” class and implement the ILog interface.

LogNLog.cs

using NLog;

namespace GlobalExceptionHandler.Logging
{
    public class LogNLog : ILog
    {
        private static readonly ILogger logger = LogManager.GetCurrentClassLogger();

        public LogNLog()
        {
        }

        public void Information(string message)
        {
            logger.Info(message);
        }

        public void Warning(string message)
        {
            logger.Warn(message);
        }

        public void Debug(string message)
        {
            logger.Debug(message);
        }

        public void Error(string message)
        {
            logger.Error(message);
        }
    }
}

Create an “ErrorDetails” class to handle return messages from HTTP response after an exception occurs.

ErrorDetails.cs

using System.Text.Json;

namespace GlobalExceptionHandler.Logging
{
    public class ErrorDetails
    {
        public int StatusCode { get; set; }
        public string Message { get; set; }

        public override string ToString()
        {
            return JsonSerializer.Serialize(this);
        }
    }
}

We can create the exception middleware class “ExceptionMiddlewareExtension” and “ConfigureExceptionHandler” methods inside it. This is a static method.

ExceptionMiddlewareExtension.cs

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Http;
using System.Net;

namespace GlobalExceptionHandler.Logging
{
    public static class ExceptionMiddlewareExtension
    {
        public static void ConfigureExceptionHandler(this IApplicationBuilder app, ILog 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.Error($"Something went wrong: {contextFeature.Error}");

                        await context.Response.WriteAsync(new ErrorDetails()
                        {
                            StatusCode = context.Response.StatusCode,
                            Message = "Internal Server Error. Error generated by NLog!"
                        }.ToString());
                    }
                });
            });
        }
    }
}

We can create a config file “nlog. config” inside the project root folder for NLog configuration settings.

 NLog configuration settings

nlog. config

<?xml version="1.0" encoding="utf-8"?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" autoReload="true"
      internalLogLevel="Trace" internalLogFile="D:\Work\Sarathlal\ASP.NET Core\GlobalExceptionHandler\Log\InnerLog.txt">
    <extensions>
        <add assembly="NLog.Extended" />
    </extensions>
     
    <targets>
        <target name="logfile" xsi:type="File" fileName="D:/Work/Sarathlal/ASP.NET Core/GlobalExceptionHandler/Log/${shortdate}_log.txt"
                layout="${longdate} ${level:uppercase=true} ${message}"/>
    </targets>
     
    <rules>
        <logger name="*" minlevel="Debug" writeTo="logfile" />
    </rules>
</nlog>

We must provide the internal log file name and exception file name separately.

We can register the ILog interface and LogNLog class inside the CofigureServices method of the Startup class.

LogNLog class

We can add our exception middleware to the .NET Core pipeline.

 .NET Core pipeline

Startup. cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using GlobalExceptionHandler.Logging;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

namespace GlobalExceptionHandler
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();

            services.AddSingleton<ILog, LogNLog>();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILog logger)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.ConfigureExceptionHandler(logger);

            app.UseRouting();

            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }
    }
}

We can create a new API controller inside the “Controllers” folder. The scaffolding template will create an API with default web methods.

Modify one web method by adding an exception explicitly.

Exception explicitly

We have added a throw statement instead of the default return statement. We can run the application and call this web method to see how this exception handled globally by middleware and NLog.

NLog

We have successfully handled the exception by middleware and the serialized error message is returned from middleware.

We can open the log folder and see that there are two log files created by NLog.

Two log files

One log is inner log and another log contains our exception details. We can open the exception log file and see the entire exception stack details.

Entire exception stack details

Conclusion

In this post, we have created a middleware for global exception handling in an ASP.NET Core application. We have used the NLog library to write exceptions to a text file. We have explicitly created an exception by throwing an error from a web method. We have seen the entire error stack details in this text file.