A Clean, Centralized Logging Architecture for .NET 5
Last Update | February 3rd 2022 |
This article was written to discuss .NET 5, but the principles apply to the entirety of .NET Core, including .NET 6.
.NET 5 has brought new features, but also some new questions.
In early December, we updated the Loupe ASP.NET Core and Entity Framework agents to support .NET 5, along with WPF and Winforms. You can now use Loupe Server with .NET 5 projects (and [].NET 6 projects too](https://onloupe.com/blog/net6-support-and-loupe/))!
You may not be ready to dive in headfirst though, and still have questions. .NET projects are often made up of many different parts, each with its own specific logging requirements. How do you balance the logging needs for everyone involved in making your application? How do you keep a clean architecture when there are so many use cases?
For .NET 5, the basis for flexible, robust logging architecture can be found in a versatile logging framework started in .NET Core: Microsoft.Extensions.Logging.
This article will review how to create a clean, centralized logging architecture for .NET 5 with Microsoft.Extensions.Logging and what we recommend for Loupe users.
What is Microsoft.Extensions.Logging (M.E.L.)?
Let’s say you have an application with one team responsible for the web interface and another team responsible for a custom library. Both of these teams will have different needs, as they are working on two very different parts of the application. One of those needs is logging.
The web interface team may prefer to use a system like Loupe and have their project send logs to a centralized server. On the other hand, the library team may want to send their logs straight to the console while developing locally.
In the past, some teams would use separate logging frameworks, which is not ideal. Multiple frameworks will potentially make it more difficult for these teams to collaborate and understand each other’s projects. Other teams would attempt to find a common framework with an agreed-upon output target for their logs. Often this would result in compromising the needs of one or both teams and work sub-optimally for everyone.
This is a problem that M.E.L. fixes. With M.E.L., these teams can both use the same framework and simply use different target outputs for their logging data.
M.E.L. was introduced in .NET Core as a logging framework to be used across the entirety of .NET. It was kept for .NET 5 as the built-in logging framework for ASP.NET, Entity Framework, and even NuGet packages and other libraries.
So, for any developer that needs .NET 5 logging, it’s worth a look. It’s the default for the .NET ecosystem and should be for some time. But for developers putting in the effort to keep their logging architecture clean, there are two features that really make M.E.L. shine:
- Customizable logging providers to allow for different outputs without using multiple frameworks.
- Logging scopes to add context to log messages cleanly and easily.
When starting with M.E.L., we think you should give these features a try.
Easily configure log outputs with Logging Providers
Example of a possible Workflow with M.E.L. Note the two different errors using different workflows, but both running through M.E.L. The optimal workflow will vary for each team, and likely not look like this.
By default, M.E.L. has four built-in Logging Providers:
-
Console: Writes log messages to the application console window, complete with colorization.
-
Debug: Writes log messages to the debug output window, useful for having them show up in an IDE (like Visual Studio). A good choice when first working on an application locally.
-
EventSource: Writes log messages to an event source. On Windows, it creates messages with Event Tracing for Windows.
-
EventLog: Works for Windows only, writes log messages to view in the Windows Event Viewer. Appropriate for some desktop applications.
None of these would be considered a logging provider for outputting logs to a centralized server. Instead, they each have different output targets for local logging.
M.E.L. was also designed to work with custom logging providers. This enables developers to use a provider like Serilog, NLog, or even access complete centralized logging systems like Loupe (which you can still use with NLog and Serilog if you want).
By setting the output target to a central server, you can create a centralized logging architecture accessible to any team working on your application.
You may be familiar with how this operates as it’s like a log wrapper class. In 2014, we wrote an article suggesting that most teams should not use a log wrapper class. This was good advice at the time, but it has been six years, and a lot has changed. Most notably, M.E.L. is a lot easier for teams to pick up and use than a custom log wrapper class, and it does more than just create an abstraction that allows for different output targets. M.E.L. allows you to make more detailed logs, with less effort as well.
Add Context to Your Logs Easily with the ILogger.BeginScope() Method
Problems are much easier to solve when you have detailed, contextual information along with the error. Information like the request made, what controller and action were selected, what browser it came from, etc., will reduce the amount of time you spend troubleshooting.
With many logging frameworks, this information can be annoying to add to your logs. You have to add the information individually to every log message. This is not only tedious, but it also makes your code harder to read.
You can get around this problem with the M.E.L. method ILogger.BeginScope(). This method allows you to pass any properties (key-value pairs) you create to any log within the scope.
Not everyone who uses M.E.L. takes full advantage of scopes, but we think you should give them a try because they:
1. Keep your code clean: You can write key-value properties a single time for anything in the scope instead of once for each item. This reduces repetition and will make your application code easier to read for every team in your organization.
2. Always have the details: Any rule you write within the scope will have the key-value properties you create unless you specify an exception. This way, you will have the information you need by default, not only after a colossal amount of effort.
3. Keep the log messages clean: With a centralized logging system like Loupe, any properties you include with the log can be found in the details tab for the log entry, not stuffed within the message itself. This ensures you don’t have to worry about formatting the log message and can avoid being distracted by extraneous details unless you need them.
Overall, scopes just keep your logging architecture as clean as possible. Any team interested in using M.E.L. should put the time into researching the ILogger.BeginScope() method if they want to get the most out of the framework.
Examples of logs with added context on Loupe Server and Loupe Desktop. Loupe Server includes the details on the same screen while Loupe Desktop puts them into their own tab.
What makes Microsoft.Extensions.Logging good for a centralized logging architecture?
What really makes this a clean architecture is it allows projects to be made in an agnostic way. It allows each team to use a logging method appropriate for them without forcing a solution on the entire organization.
Many teams in an organization will benefit from logging to a centralized server, especially teams focused on the application in production. But a major focus for .NET 5 is giving individual teams more control over their specific part of the project. If a team developing a library thinks logging to the console would make the most sense for their circumstances, M.E.L. gives them an easy way to change the output target without changing the entire logging framework.
Microsoft.Extensions.Logging enables a centralized logging architecture that is easy to use across the entire project. In the few situations where local logging is preferred, developers can change the output target for the specific logs without requiring changes throughout the rest of the application. This way, those who rely on centralized logging can be confident that it’s always available no matter the other teams’ logging needs.
What About Microsoft.Extensions.Logging on .NET 6?
M.E.L. operates in the same way on .NET 6 as it does for .NET 5 and has for .NET Core.
There are some potential minor optimizations, like using a global using statement for M.E.L. instead of applying it to every file. Logging is ubiquitous enough that a global using statement could be handy. But the point is, what we discussed here will apply to .NET 6 applications you create as well.
Loupe users and Microsoft.Extensions.Logging
For most Loupe users with .NET 5, .NET 6, or any .NET Core applications, we recommend using M.E.L. In short, M.E.L. works natively with the whole of .NET 5, can be adapted for your logging needs, and still allows you to use an advanced log management system like Loupe Server where you need to.
Many Loupe users already use M.E.L., including ourselves.
For more details on how to use M.E.L. with Loupe, the materials listed below should provide a good starting point:
- Learn more about Loupe for Microsoft.Extensions.Logging
- Learn more about using Microsoft.Extensions.Logging with ASP.NET Core and Loupe
Or, if you want to try Centralized Logging for yourself, you can learn more about the Loupe Trial in the link below.