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 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 template to create a Web API project.
 
 
 
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 “NLog.Web.AspNetCore” library to the project using 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
  1. namespace GlobalExceptionHandler.Logging  
  2. {  
  3.     public interface ILog  
  4.     {  
  5.         void Information(string message);  
  6.         void Warning(string message);  
  7.         void Debug(string message);  
  8.         void Error(string message);  
  9.     }  
  10. }  
Though, we will handle only exceptions in this application, we have provided options for all other logging like Info, Warning, Debug in the above interface.
 
We can create a “LogNLog” class and implement the ILog interface.
 
LogNLog.cs
  1. using NLog;  
  2.   
  3. namespace GlobalExceptionHandler.Logging  
  4. {  
  5.     public class LogNLog : ILog  
  6.     {  
  7.         private static readonly ILogger logger = LogManager.GetCurrentClassLogger();  
  8.   
  9.         public LogNLog()  
  10.         {  
  11.         }  
  12.   
  13.         public void Information(string message)  
  14.         {  
  15.             logger.Info(message);  
  16.         }  
  17.   
  18.         public void Warning(string message)  
  19.         {  
  20.             logger.Warn(message);  
  21.         }  
  22.   
  23.         public void Debug(string message)  
  24.         {  
  25.             logger.Debug(message);  
  26.         }  
  27.   
  28.         public void Error(string message)  
  29.         {  
  30.             logger.Error(message);  
  31.         }  
  32.     }  
  33. }  
Create an “ErrorDetails” class to handle return messages from HTTP response after exception occurs.
 
ErrorDetails.cs
  1. using System.Text.Json;  
  2.   
  3. namespace GlobalExceptionHandler.Logging  
  4. {  
  5.     public class ErrorDetails  
  6.     {  
  7.         public int StatusCode { getset; }  
  8.         public string Message { getset; }  
  9.         public override string ToString()  
  10.         {  
  11.             return JsonSerializer.Serialize(this);  
  12.         }  
  13.     }  
  14. }  
We can create the exception middleware class “ExceptionMiddlewareExtension” and “ConfigureExceptionHandler” method inside it. This is a static method.
 
ExceptionMiddlewareExtension.cs
  1. using Microsoft.AspNetCore.Builder;  
  2. using Microsoft.AspNetCore.Diagnostics;  
  3. using Microsoft.AspNetCore.Http;  
  4. using System.Net;  
  5.   
  6. namespace GlobalExceptionHandler.Logging  
  7. {  
  8.     public static class ExceptionMiddlewareExtension  
  9.     {  
  10.         public static void ConfigureExceptionHandler(this IApplicationBuilder app, ILog logger)  
  11.         {  
  12.             app.UseExceptionHandler(appError =>  
  13.             {  
  14.                 appError.Run(async context =>  
  15.                 {  
  16.                     context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;  
  17.                     context.Response.ContentType = "application/json";  
  18.   
  19.                     var contextFeature = context.Features.Get<IExceptionHandlerFeature>();  
  20.                     if (contextFeature != null)  
  21.                     {  
  22.                         logger.Error($"Something went wrong: {contextFeature.Error}");  
  23.   
  24.                         await context.Response.WriteAsync(new ErrorDetails()  
  25.                         {  
  26.                             StatusCode = context.Response.StatusCode,  
  27.                             Message = "Internal Server Error. Error generated by NLog!"  
  28.                         }.ToString());  
  29.                     }  
  30.                 });  
  31.             });  
  32.         }  
  33.     }  
  34. }  
We can create config file “nlog.config” inside the project root folder for NLog configuration settings.
 
 
 
nlog.config
  1. <?xml version="1.0" encoding="utf-8" ?>  
  2. <nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" autoReload="true"  
  3.       internalLogLevel="Trace" internalLogFile="D:\Work\Sarathlal\ASP.NET Core\GlobalExceptionHandler\Log\InnerLog.txt">  
  4.     <extensions>  
  5.         <add assembly="NLog.Extended" />  
  6.   </extensions>  
  7.      
  8.     <targets>  
  9.         <target name="logfile" xsi:type="File" fileName="D:/Work/Sarathlal/ASP.NET Core/GlobalExceptionHandler/Log/${shortdate}_log.txt"   
  10.                 layout="${longdate} ${level:uppercase=true} ${message}"/>  
  11.   </targets>  
  12.      
  13.     <rules>  
  14.         <logger name="*" minlevel="Debug" writeTo="logfile" />  
  15.   </rules>  
  16. </nlog>  
We must provide internal log file name and exception file name separately.
 
We can register the ILog interface and LogNLog class inside the CofigureServices method of Startup class.
 
 
 
We can add our exception middleware to the .NET Core pipeline.
 
 
 
Startup.cs
  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Threading.Tasks;  
  5. using GlobalExceptionHandler.Logging;  
  6. using Microsoft.AspNetCore.Builder;  
  7. using Microsoft.AspNetCore.Hosting;  
  8. using Microsoft.AspNetCore.Mvc;  
  9. using Microsoft.Extensions.Configuration;  
  10. using Microsoft.Extensions.DependencyInjection;  
  11. using Microsoft.Extensions.Hosting;  
  12. using Microsoft.Extensions.Logging;  
  13.   
  14. namespace GlobalExceptionHandler  
  15. {  
  16.     public class Startup  
  17.     {  
  18.         public Startup(IConfiguration configuration)  
  19.         {  
  20.             Configuration = configuration;  
  21.         }  
  22.   
  23.         public IConfiguration Configuration { get; }  
  24.   
  25.         // This method gets called by the runtime. Use this method to add services to the container.  
  26.         public void ConfigureServices(IServiceCollection services)  
  27.         {  
  28.             services.AddControllers();  
  29.   
  30.             services.AddSingleton<ILog, LogNLog>();  
  31.         }  
  32.   
  33.         // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.  
  34.         public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILog logger)  
  35.         {  
  36.             if (env.IsDevelopment())  
  37.             {  
  38.                 app.UseDeveloperExceptionPage();  
  39.             }  
  40.   
  41.             app.ConfigureExceptionHandler(logger);  
  42.   
  43.             app.UseRouting();  
  44.   
  45.             app.UseAuthorization();  
  46.   
  47.             app.UseEndpoints(endpoints =>  
  48.             {  
  49.                 endpoints.MapControllers();  
  50.             });  
  51.         }  
  52.     }  
  53. }  
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.
 
 
We have added a throw statement instead of default return statement. We can run the application and call this web method to see how this exception handled globally by middleware and 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.
 
 
 
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.
 
 

Conclusion


In this post, we have created a middleware for global exception handling in an ASP.NET Core application. We have used 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.