Custom And Thread Safe Logging .NET Core API

Why logging is important?

Basically, many times we tackle application errors some we got at when our web application is working in the production environment, and if there is any type of errors occurred then it’s hard to track while solving bugs that’s why Logging is important.

How to implement Logging?

There are many tools in the market using that we are able to implement logging. For example, in the .NET Core people mostly used many libraries for logging using that we are going to handle all the scenarios which are present in the application and that will be helpful for us when the application is running in a production environment.

Prerequisites

  1. A basic understanding of C# Programming.
  2. Understanding of.NET Core API
  3. A Basic understanding of Dependency Injection.

So, let’s start with custom and thread logging how do we implement that without any logging tool and libraries which are already present in the .NET Core.

Agenda

  1. First, we create .NET Core API Application.
  2. Create Logger Class which we are going to use for logging purposes
  3. Register the Logger class in the Startup class in the Dependency Injection Container
  4. After that, we are going to inject that from the constructor of the API Controller
  5. Finally, apply it to one of the controller methods for testing purposes

Step 1

Open the Visual Studio and create a .NET Core API project.

Step 2

Create the Logger class

using System;
using System.IO;
using System.Threading.Tasks;

namespace LoggingDemo.Logging
{
	public class Logger
	{
		public async Task Log(string logMessage)
		{
			try
			{
				string dirPath = @"D:\Log";
				string fileName = Path.Combine(@"D:\Log", "Log" + DateTime.Now.ToString("yyyy-MM-dd") + ".txt");

				await WriteToLog(dirPath, fileName, logMessage);
			}
			catch (Exception e)
			{
				Console.WriteLine(e.Message);
			}
		}

		public async Task WriteToLog(string dir, string file, string content)
		{
			using (StreamWriter outputFile = new StreamWriter(Path.Combine(dir, file), true))
			{
				await outputFile.WriteLineAsync(string.Format("Logged on: {1} at: {2}{0}Message: {3} at {4}{0}--------------------{0}",
						  Environment.NewLine, DateTime.Now.ToLongDateString(),
						  DateTime.Now.ToLongTimeString(), content, DateTime.Now.ToString("dddd, dd MMMM yyyy HH:mm:ss.fff")));
			}
		}
	}
}
  • Here you can see we create a Logger class and inside that create two methods which we are going to use for writing the Log on a particular file and location after creating a file and if the file is already present at that location, then it will append log data to it.
  • So, inside the first method, we accept the log message, and inside the try block, we put our directory path on which we are going to create a Log file and write a log onto that file on a certain event and call another method and pass directory path, filename and message to that method.
  • Also, inside the second method of Logger class, we used Stream Writer to write the log on that we write log in well-structured format and also in a millisecond.

Step 3

Now, we set one environmental variable inside the app setting which we are going to use in configure services method which is present in the startup class.

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*",
  "IsLoggerEnabled": "true"
}

Step 4

Next, Register the Logger class as a singleton DI service in configure services method which is present inside the Startup class.

using LoggingDemo.Logging;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.OpenApi.Models;

namespace LoggingDemo
{
    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();

            var isEnabled = Configuration.GetSection("IsLoggerEnabled").Value;
            if (isEnabled == "true")
            {
                services.AddSingleton<Logger>();
            }

            services.AddSwaggerGen();
        }

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

            app.UseHttpsRedirection();

            // This middleware serves generated Swagger document as a JSON endpoint
            app.UseSwagger();

            // This middleware serves the Swagger documentation UI
            app.UseSwaggerUI(c =>
            {
                c.SwaggerEndpoint("/swagger/v1/swagger.json", "LoggingDemo API V1");
            });
            app.UseRouting();
            app.UseAuthorization();
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }
    }
}

So here we defined one condition and take the value of that environmental variable from the app setting file and check if the end-user or developer wants some logging mechanism somewhere inside the codebase while developing for testing some scenarios and monitoring the health of the application.

As you see in the configure service method we register the Logger class in Service Container related to Dependency Injection and also enable swagger for testing endpoint,

Step 5

Create one Test Controller and put some code for testing purposes as I mentioned below


using LoggingDemo.Logging;
using Microsoft.AspNetCore.Mvc;

namespace LoggingDemo.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class TestController : ControllerBase
    {
        private readonly Logger logger;
        public TestController(Logger _logger)
        {
            logger = _logger;   
        }

        [HttpGet]
        [Route("login")]
        public string Login()
        {
            _ = logger.Log("login succesfull");
            return "You have logged in successfully";
        }
    }
}

So here you can see we inject the Logger Class inside the Constructor and then use that inside the Login method just for testing.

Step 6

Finally, run the application and hit the endpoint 

Custom and Thread Safe Logging .NET Core API

Step 7

So here inside the Log file you can see the log is maintained in a well-structured format and which is very useful for us in many scenarios

Custom and Thread Safe Logging .NET Core API

Custom and Thread Safe Logging .NET Core API

Conclusion

So here we discussed how we implement custom and thread-safe logging and you can also able to use this in the synchronous method and also discussed how to register using Dependency Injection based on environmental variable and use it after injecting it into the API controller.

I hope you understand.

Happy Coding!

If you want to learn and explore dependency injection then visit my following blog