Eliminating ILogger<T> Injection in .NET for Cleaner Code
Written on
Chapter 1: Introduction to Dependency Injection
Dependency injection (DI) is a powerful tool that simplifies the process of creating and managing dependencies across your application. It significantly reduces the manual effort required to wire up instances. However, this convenience can lead to an overabundance of dependencies, making the codebase less maintainable. One common dependency that appears in numerous constructors across various projects is the ILogger<T>.
In this article, I’ll explore a method to eliminate ILogger<T> from your constructors, streamlining your services. While this method won’t entirely remove the dependency, it will simplify the constructor by eliminating one argument, thereby making your classes easier to mock during testing and saving you a line of code for each class.
Keep in mind that this approach has its drawbacks and deviates from traditional Clean Code and Clean Architecture principles. By removing ILogger from the constructor, you're effectively obscuring a dependency. This may complicate future adjustments when moving classes to new repositories that don't have access to the static logger factory. Additionally, testing may require extra effort and could confuse developers unfamiliar with this method. Use this solution only if you believe its advantages outweigh its disadvantages.
In my view, this technique can offer several benefits compared to the conventional method of injecting ILogger in the constructor, particularly in a single-solution environment rather than in distributed systems like microservices. It allows for logging in contexts where passing a logger would otherwise be impossible, such as in static classes, methods, and extension methods.
Testing becomes slightly more complex than with traditional DI. Instead of injecting an ILoggerFactory mock into the DI container, you would pass it to the static logger factory, which simplifies your code by eliminating one line. Changing the logger implementation requires only an adjustment in the static logger factory, as long as the ILogger interface remains unchanged. Furthermore, if a class cannot be instantiated through DI, you don’t need to figure out how to obtain a logger for it, such as when using a factory that requires an ILoggerFactory.
Clean methods for removing logger dependencies, like the Decorator Pattern, Dynamic Proxies, or Middlewares, allow logging only before and after method execution, not in between.
Section 1.1: Implementing the Static Logger Factory
The initial step in reducing dependency injection is to keep your dependencies static. For ILogger, this can be accomplished by maintaining a statically accessible ILoggerFactory. I developed a static class called StaticLoggerFactory that contains a static ConcurrentDictionary<Type, ILogger>, allowing concurrent access to all ILogger instances. This class also features the convenient GetOrAdd() method, which enables you to create an instance if it does not already exist in the dictionary.
This class must be initialized with an ILoggerFactory, which you can obtain from the IServiceProvider after adding Logging to your IServiceCollection.
Section 1.2: Utilizing the Static Logger Factory
Now that we have the setup in place, let’s examine how to use it in other classes. Simply invoke the GetStaticLogger<T> function from our static logger factory, and you're all set. It becomes even more straightforward if you add the following line to your global usings (.NET 6 and above):
global using static <YourNameSpace>.StaticLoggerFactory;
This allows you to call the static method without needing to reference the class name, as illustrated in the example.
Comparing to the traditional ILogger<T> injection, the injection and null check take up an additional 92 characters, while the static method only requires 35 characters (counted after the property name). By adopting this method, you can save a few more lines of code and avoid the hassle of creating multiple ILogger mocks in your tests.
Let me know your thoughts on this approach in the comments. Do you find it clean, or does it complicate matters? Would you prefer to omit the dictionary or keep it?
For those interested in the code, you can find it on my GitHub here:
GitHub - TobiStr/LoggerElimination: How to eliminate injecting ILogger everywhere in .NET
The video titled "You are doing .NET logging wrong. Let's fix it" provides further insights into managing logging effectively in your .NET applications.
Thank you for taking the time to read this article. I hope you found it informative and engaging. Your support and interaction are highly valued. If you want to stay updated on the latest trends, tips, and techniques for clean architecture and coding—especially in C#, .NET, and Angular—I would appreciate it if you considered following me.
Have a wonderful day!