Why is logging so important during the application development? Well, while your application is in the developing stage it is very easy to debug the code and to find out what went wrong. But, can you debug in the production environment?
Of course not.
That is why logging messages are a great way to find out what went wrong and where did errors happen in your code in the production environment. .NET Core has its own implementation of logging messages, but in all my projects, I prefer to create my own custom logger service.
If you want to see all the basic instructions and complete navigation for this series, please follow the following link: Introduction page for this tutorial.
For the previous part check out: Creating .NET Core WebApi project – Basic code preparations
So, let’s start.
Creating Required Projects
Let’s create two new projects. Name the first one Contracts
. We are going to store interfaces inside this project. Name the second one LoggerService
. We are going to use it for the logger logic.
To create a new project:
- Right-click on the solution window,
- Choose
Add
and thenNewProject
. We are going to choose the Class Library and name itContracts
:
Do the same thing for the second project, just name it LoggerService
.
With these two new projects in place, we need to reference the LoggerService
project to the main project.
To do that, we have to:
- Navigate to the main project inside solution explorer
- Right-click on the Dependences and choose the Add Project Reference
- Under the Projects click the Solution and choose the LoggerService
Before we proceed to the implementation of the LoggerService, let’s do one more thing.
In the LoggerService project right click on the Dependencies and then click on the Add Project Reference. Inside check the Contracts checkbox to import its reference inside the LoggerService project. This would automatically add the Contracts reference to the main project because we already have a LoggerService referenced inside.
Creating the Interface and Installing NLog
Our logger service will contain four methods for logging:
- Info messages
- Debug messages
- Warning messages
- And error messages
Additionally, we are going to create the interface ILoggerManager
inside the Contracts
project containing those four method definitions.
So, let’s create the ILoggerManager
interface and modify it:
namespace Contracts { public interface ILoggerManager { void LogInfo(string message); void LogWarn(string message); void LogDebug(string message); void LogError(string message); } }
Before we implement this interface inside the LoggerService
project, we need to install the NLog
library in our LoggerService
project. NLog
is a logging platform for the .NET which will help us create and log our messages.
We are going to show you two ways to add the NLog
library to our project.
For the first way, in the LoggerService
project, right-click on the Dependences
, and choose Manage NuGet Packages
. After the NuGet window appears, we have to install the required library:
The second option – from the View menu choose the Other Windows and then click on the Package Manager Console. After the console appears just follow the leads:
Install-Package NLog.Extensions.Logging
After a couple of seconds, NLog
is up and running in your app.
Implementing Interface and Nlog.Config File
In the LoggerService
project, let’s create a new LoggerManager
class.
Then, let’s modify that class:
using Contracts; using NLog; namespace LoggerService { public class LoggerManager : ILoggerManager { private static ILogger logger = LogManager.GetCurrentClassLogger(); public void LogDebug(string message) => logger.Debug(message); public void LogError(string message) => logger.Error(message); public void LogInfo(string message) => logger.Info(message); public void LogWarn(string message) => logger.Warn(message); } }
NLog needs to have information about the folder to create log files in, what the name of these files will be, and what a minimum level of logging is. Therefore, we need to create one text file in the main project with the name nlog.config
and populate it. You need to change the path of the internal log
and filename
parameters to your paths:
<?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:Projects\Blog-AccountOwner\Project\internal_logs\internallog.txt"> <targets> <target name="logfile" xsi:type="File" fileName="d:/Projects/Blog-AccountOwner/Project/logs/${shortdate}_logfile.txt" layout="${longdate} ${level:uppercase=true} ${message}"/> </targets> <rules> <logger name="*" minlevel="Debug" writeTo="logfile" /> </rules> </nlog>
Configuring Logger Service for Logging Messages
Setting up the configuration for a logger service is quite easy.
We have to modify the Program
class:
using NLog; var builder = WebApplication.CreateBuilder(args); LogManager.LoadConfiguration(string.Concat(Directory.GetCurrentDirectory(), "/nlog.config"));
For the .NET 5 version, we can modify the Startup
constructor:
public Startup(IConfiguration configuration) { LogManager.LoadConfiguration(String.Concat(Directory.GetCurrentDirectory(), "/nlog.config")); Configuration = configuration; }
Secondly, we need to add the logger service inside the .NET Core’s IOC container. There are three ways to do that:
- By calling
builder.Services.AddSingleton
will create the service the first time you request it and then every subsequent request is calling the same instance of the service. This means that all components are sharing the same service every time they need it. You are using the same instance always - By calling
builder.Services.AddScoped
will create the service once per request. That means whenever we send the HTTP request towards the application, a new instance of the service is created - By calling
builder.Services.AddTransient
will create the service each time the application request it. This means that if during one request towards our application, multiple components need the service, this service will be created again for every single component which needs it
So, in the ServiceExtensions
class we are going to add a new method:
public static void ConfigureLoggerService(this IServiceCollection services) { services.AddSingleton<ILoggerManager, LoggerManager>(); }
Lastly, in the Program
class, we have to invoke this extension method, right above builder.Services.AddControllers()
:
builder.Services.ConfigureLoggerService();
Every time we want to use a logger service, all we need to do is to inject it into the constructor of the class that is going to use that service. .NET Core will serve that service from the IOC container and all of its features will be available to use. This type of injecting object is called Dependency Injection.
DI, IOC, and Logger Service Testing
What is exactly Dependency Injection(DI) and what is IOC (Inversion of Control)?
Dependency injection is a technique for achieving loose coupling between objects and their dependencies. It means that rather than instantiating an object every time it is needed, we can instantiate it once and then serve it in a class, most often, through a constructor method. This specific approach we utilize is also known as the Constructor Injection.
In a system that is designed to use DI, you may find many classes requesting their dependencies via their constructor. In that case, it is helpful to have a class that will provide all instances to classes through the constructor. These classes are referred to as containers or more specific Inversion of Control containers. An IOC container is essentially a factory that is responsible for providing instances of types that are requested from it.
For the logger service testing purpose, we could use WeatherForecastController
. We are going to find it in the main project in the Controllers
folder.
So, let’s modify it:
[ApiController] [Route("[controller]")] public class WeatherForecastController : ControllerBase { private readonly ILoggerManager _logger; public WeatherForecastController(ILoggerManager logger) { _logger = logger; } [HttpGet] public IEnumerable<string> Get() { _logger.LogInfo("Here is info message from the controller."); _logger.LogDebug("Here is debug message from the controller."); _logger.LogWarn("Here is warn message from the controller."); _logger.LogError("Here is error message from the controller."); return new string[] { "value1", "value2" }; } }
After all, we can start the application, and browse to http://localhost:5000/weatherforecast
. As a result, we will see an array of two strings. Now, let’s go to the folder that we have specified in the nlog.config
file, and check out the result. We should see four lines logged inside that file.
Conclusion
Now you have the working logger service, you will use through an entire process of our application development.
By reading this post you’ve learned:
- Why logging is important
- How to create external service
- How to use the NLog library and how to configure it
- What are DI and IOC
- And how to use them for service injection
Thank you for reading and see you soon in the next blog post where we will talk about repository pattern with entity framework core.