Think with Enlab

Diving deep into the ocean of technology

Stay Connected. No spam!

Top logging frameworks for .NET applications and the best configuration tips

 

In software development, logging is an essential part that helps you to monitor the system.
So what is logging? There are many definitions of logging but from a developer's perspective, here is one of the best descriptions that I found. It's from Colin Eberhardt’s article.

“Logging is the process of recording application actions and state to a secondary interface.”

This is exactly what we expected from logging; it can be a message on a console screen, log file, email, third-party service, etc.

Logging is an indispensable thing to save time when investigating a bug that developers have missed. It’s also useful to detect common mistakes users make when using the application, as well as security purposes.

 

Introduction to .NET core built-in logging

In the .NET core application, logging is a built-in feature for some types of applications such as ASP.NET Core and .NET Core Work Services. For other types that don’t support logging as a built-in feature, you need to install Microsoft.Extensions.Logging package to use the .NET logging API.

The Logging API doesn’t work independently. It works with one or more logging suppliers that store (or just display) log messages to a destination place.

For example, the default ASP.NET application includes some built-in providers such as:

  • Console: Display log message to the console screen.
  • Debug: Log output by using the System.Diagnostics.Debug class. Calls the WriteLine method to write the Debug provider. Log location is dependent on the operator system (ex: /var/log/message on ubuntu). You can see it in the debug windows on many IDEs.
  • EventLog: Log output to the Windows Event Log (So it only works with Windows)

One other thing is the log level. Log levels indicate the importance or severity of log messages. Log providers include extension methods to indicate log levels. Below is the list of log levels in .NET Core:

 

LogLevel Severity Method Description
Trace 0 LogTrace

Contain the most detailed messages. These messages may contain sensitive app data. These messages are disabled by default and should not be enabled in production.

 

Debug 1 LogDebug

For debugging and development. Use caution in production due to the high volume.

 

Information 2 LogInformation

Tracks the general flow of the app. It may have long-term value.

 

Warning 3 LogWarning

For abnormal or unexpected events. Typically includes errors or conditions that don't cause the app to fail.

 

Error 4 LogError

For errors and exceptions that cannot be handled. These messages indicate a failure in the current operation or request, not an app-wide failure.

 

Critical 5 LogCritical

For failures that require immediate attention. Examples: data loss scenarios, out of disk space.

 

None 6  

Specifies that a logging category should not write any messages.

 

 

How to use it with examples?

If you take a look at the ASP.NET core default template, you will see that we can inject a logger dependency into our Controller class constructor. This is because the default ASP.NET configuration registers a logging provider factory with the built-in dependency injection system.

 

logging frameworks

 

As you can see, the parameter into the constructor is a generic type referencing the class itself. This is a requirement when using the .NET logging as it allows the log factory to create a logger, which instance specific to that class. This approach adds a little extra context into log messages and allows class and namespace specific log level configuration (talk about it later).

Now, we have a logger in our class; let’s take it! Add the following code to the Get method:

public IEnumerable<WeatherForecast> Get()
        {
            _logger.LogInformation("Hello from logger...");

            var rng = new Random();
            return Enumerable.Range(1, 5).Select(index => new WeatherForecast
            {
                Date = DateTime.Now.AddDays(index),
                TemperatureC = rng.Next(-20, 55),
                Summary = Summaries[rng.Next(Summaries.Length)]
            })
            .ToArray();
        }

 

Start the application and send a GET request to the /weatherforecast endpoint.

If you look at the IDE’s debug window or the console screen, in the meantime, you are running the application with .NET CLI; you should see our information message. It goes like this:

WebApplication.Controllers.WeatherForecastController: Information: Hello from logger...

That’s for an ASP.NET core application with the built-in logging feature.

How about other .NET application types that don’t include built-in logging?

Let's create a console application by running the .NET command
dotnet new console or with UI if you are using VS.

To write the log. First, you need to add Microsoft.Extensions.Logging package.
Run the command: dotnet add package Microsoft.Extensions.Logging

Then you need to add a logging provider; let's use a console provider.
Run the command: dotnet add package Microsoft.Extensions.Logging.Console

Now, everything is ready. Update the primary method as below:

static void Main(string[] args)
        {
            using (var serviceProvider = new ServiceCollection()
                .AddLogging(config => config
                    .ClearProviders()
                    .AddConsole()
                    .SetMinimumLevel(LogLevel.Trace))
                .BuildServiceProvider())
            {
                // Get logger factory service
                var loggerFactory = serviceProvider.GetService<ILoggerFactory>();
                // Create logger
                var logger = loggerFactory.CreateLogger<Program>();
                // run app logic
                logger.LogInformation("Hello! This is log message from .NET console app...");
            }
            Console.Write("Yup");
        }

Start the application, and you will see a log message like this.

info: ConsoleApplication.Program[0]
      Hello! This is a log message from the .NET console app…

Top third-party logging providers for .NET core

As you can see, .NET provides many built-in logging providers that log messages to the console, Debug, EvenLog, etc. But we have no available provider that helps us write messages to a log file, which everybody does in staging and production environments.

To do that, we will need to implement a custom logging provider, and this may take time as we need to take care of many things like reading/writing performance, storage space, configurations, etc.

Fortunately, we have some third-party packages that did well - Nlog, SeriLog, and Log4Net. These packages allow to storage of messages to a log file and are quickly set up. Just install them from the NuGet package store and then add some configurations.

 

Log4Net is one of the leading logging frameworks for .NET. It started in 2001 as a port of the Java framework “log4j”. Log4Net is flexible in where the logging information is stored. It has been the first choice for the .NET logging framework. But unfortunately, it hasn’t seen a significant release since 2017. This explains why many fans of Log4Net are moving to another framework.

 

SeriLog was released in 2013; the main difference between Serilog and the other frameworks is that it supports structured logging. So what is structured logging?
The problem with log files is that they are unstructured text data. It's hard to filter, sort, and query helpful information. It would be great for a developer to filter all logs by a certain such as user ID or transaction ID. Structured logging allows us to do it and additional analytics as well.

 

NLog is a popular logging framework for .NET, and the first version was released in 2006. It supports writing to various destinations, including files, consoles, emails, and databases. Moreover, Nlog also supports structured logging. Advantageously, it is easy to configure both through the configuration file and programmatic configuration. And the benchmarks suggest that using it has some performance improvements. That is why I always choose it for my .NET core project, if possible.

 

Here are the steps to use Nlog in your ASP.NET core project with a configuration file:

1. Install NLog.Web.AspNETCore NuGet package. You can install it from Visual Studio or run this command: dotnet add package NLog.Web.AspNetCore

2. Add “nlog.config” file: In this file, we will define a target that will write logs to a file on our local system, the format of log messages, maximum log file size, archived old log file, etc. You can read more about the nlog.config file here.

Here is the simple config for “nlog.config” file to write a log file to the bin/logs folder:

<?xml version="1.0" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
 
  <targets>
    <target name="file" xsi:type="File"
            layout="${longdate} ${logger}: ${message}${exception:format=ToString}"
            fileName="${basedir}/logs/${shortdate}.log"
            keepFileOpen="true"
            encoding="utf-8" />
  </targets>
 
  <rules>
    <logger name="*" minlevel="Trace" writeTo="file" />
  </rules>
</nlog>

 

3. Update your program.cs. Replace the built-in provider with Nlog.

public class Program
    {
        public static void Main(string[] args)
        {
            CreateHostBuilder(args).Build().Run();
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                })
                .ConfigureLogging(logging =>
                {
                    logging.ClearProviders();
                    logging.SetMinimumLevel(LogLevel.Trace);
                })
                .UseNLog();  // NLog: Setup NLog for Dependency injection;
    }

 

That's all! Start the project, and you will get the log file under the “.\bin\Debug\netcoreapp3.1\logs'' folder. The message is the same as the example for the built-in provider above.

 

messages

 

How to log in with the monitoring platform “Sentry”?

With third-party logging providers, you can save error messages, stack trace to a log file for investigation, and fix the issue. But one disadvantage of this way is you have to connect to the server to get the log files, and the log file usually includes the log message in a whole day, so it will be tricky for us to get the necessary information. That is why we need error-tracking systems like Sentry.

Sentry.io is an open-source error tracking system that supports real-time error tracking with a clear UI. Sentry also supports a wide range of server, browser, desktop, and native mobile languages and frameworks, including PHP, Node.js, Python, Ruby, C#, Java, Go, React, Angular, Vue, JavaScript, and more.

Sentry developer accounts are free, with commercial options for larger teams generating thousands of events across multiple projects.

Real-time logging for ASP.NET project with Sentry

1. First, you need to create a new account; or log in to your existing Google, GitHub, or Azure DevOps account.

 

Creating a new account

 

2. Next, you need to create a Sentry project to map with your project once you have logged in. Select the “Projects” menu on the left and then click on the “Create Project” button.

 

New project

 

3. On the “Create a new Project” screen, select the language or framework you are using. Enter the project name. You can set the alert configuration if needed. And then click on “Create Project” to create your Sentry project.

 

Choose platform

 

4. Your project will be added to the “Projects” screen.

 

Project

 

5. On your ASP.NET core project, Install Sentry.AspNetCore NuGet package.

Run the command: dotnet add package Sentry.AspNetCore

 

6. Add this configuration to the “appsettings.json” file.

"Sentry": {
    "Dsn": "https://2a73e898f8464c9d99dc4b10b1463818@o495637.ingest.sentry.io/5568686",
    "IncludeRequestPayload": true,
    "SendDefaultPii": true,
    "MinimumBreadcrumbLevel": "Debug",
    "MinimumEventLevel": "Warning",
    "AttachStackTrace": true,
    "Debug": true,
    "DiagnosticsLevel": "Error"
  }

 

With “Dsn” is under project settings/Client Keys (DSN).
You can check for all Sentry configurations for ASP.NET core here.

 

Sentry configuration

 

7. Update your “program.cs”, and add use Sentry config.

public class Program
    {
        public static void Main(string[] args)
        {
            CreateHostBuilder(args).Build().Run();
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseSentry();
                    webBuilder.UseStartup<Startup>();
                });
    }

 

8. That's all! Now you can start the project and try to write a log. You can see the log message on the issue screen.

 

 log message

 

It will include very detailed information like IP, browser, OS, device, message, stack trace, etc.

 

 log message

 

Our best configuration tips

When we log more information, it will be easier to diagnose a fault. But logging code is like any other. If we log too many things, the performance will be affected. So do not log everything, just log the critical information that will help you diagnose a fault like unhandled exceptions. And one of the best ways to do it in ASP.NET is to use middleware.

Below is a simple example:

public class ExceptionMiddleware {
		private readonly RequestDelegate _next;
		private readonly IWebHostEnvironment _env;
		private readonly ILogger<ExceptionMiddleware> _logger;

		public ExceptionMiddleware(RequestDelegate next, IWebHostEnvironment env, ILogger<ExceptionMiddleware> logger)
		{
			_env = env;
			_logger = logger;
			_next = next;
		}

		public async Task InvokeAsync(HttpContext httpContext)
		{
			try {
				await _next(httpContext);
			}
			catch (Exception ex) {
				_logger.LogError(ex, ex.Message);

				await HandleExceptionAsync(httpContext, ex);
			}
		}

		private async Task HandleExceptionAsync(HttpContext context, Exception ex)
		{
			context.Response.ContentType = "application/json";
			if (context.Response.StatusCode == (int)HttpStatusCode.Unauthorized) {
				return;
			}
			context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;

			var baseEx = ex.GetBaseException();

			//
			// Convert to model
			var message = !_env.IsProduction() || ex is DomainException ? baseEx.GetErrorMessages() : "InternalServerError";
			var error = new ErrorViewModel(message, !_env.IsProduction() ? baseEx.StackTrace : null);
			ExceptionLogger.LogToFile(message + "\n" + baseEx.StackTrace);
			_logger.LogError(baseEx, baseEx.Message);
			//
			// Return as json
			context.Response.ContentType = "application/json";
			await context.Response.WriteAsync(System.Text.Json.JsonSerializer.Serialize(error));
		}
	}

 

And to easily access logging information on the production environment, you can use a tracking system like Sentry. In case your project is too small, you can write the log info to your database or send the log information to email, so you don't need to access the server.

One important thing that you need to know is the size of the log file. If the file is too large, it will affect log performance, so you need to limit the log file size, and if the file is reached the limit, we will create a new one. You also can archive a log file.

For example, in NLog, you can add some configurations as below:

<?xml version="1.0" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
	  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

	<targets>
		<target name="file" xsi:type="File"
				layout="${longdate} ${logger}: ${message}${exception:format=ToString}"
				fileName="${basedir}/logs/${shortdate}.log"
				keepFileOpen="true"
				encoding="utf-8" 
				archiveNumbering="DateAndSequence"
				archiveAboveSize="104857600"
				maxArchiveDays="14"/>
	</targets>

	<rules>
		<logger name="*" minlevel="Trace" writeTo="file" />
	</rules>
</nlog>

 

Conclusion

Effective logging is crucial to debugging in the production environment of an application. A good logging message is important for system administrators and developers to detect errors, threats, as well as the system’s state. It also makes a major difference in the supportability of an application. So when you write a log on your code, don't forget to log several following indications, such as: why the application failed, when the failure occurred, what line in the code the application failed, and what the system was doing when the loss occurred. This information will help you to investigate better and resolve an event.

I hope this blog post can help you get an overview of login and start it today if you are not already doing it.

 

CTA Enlab Software

 

References:

About the author

Tan Nguyen

Hi, my name is Tan! I am currently working as a Software Engineer at Enlab Software and I mostly focus on Software Architectures, Development, and Software Re-factoring. I love coding and learning new technologies so I'm excited to share my knowledge with you!

Up Next

March 21, 2024 by Dat Le
In the dynamic arena of startup development, an innovative trend is reshaping how entrepreneurs bring their...
Unveiling the Web Development Outsourcing Landscape
February 15, 2024 by Dat Le
In the era where digitalization dictates the tempo of business evolution, understanding the intricate fabric of...
Understanding different types of Angular Modules with practical examples
November 25, 2022 by Trong Ngo
In software development, grouping multiple specific functionalities or features into different modules is the key...
How to apply Test Driven Development with practical examples
June 22, 2022 by Tuan Do
Over the last few years, test-driven development (a.k.a. TDD) has grown in popularity. Many programmers...
Roll to Top

Can we send you our next blog posts? Only the best stuffs.

Subscribe