Should I take ILogger, ILogger, ILoggerFactory or ILoggerProvider for a library?

后端 未结 8 1461
不知归路
不知归路 2020-12-04 06:59

This may be somewhat related to Pass ILogger or ILoggerFactory to constructors in AspNet Core?, however this is specifically about Library Design, not about

相关标签:
8条回答
  • 2020-12-04 07:29

    The ILogger<T> was the actual one that is made for DI. The ILogger came in order to help implement the factory pattern much more easily, instead of you writing on your own all the DI and Factory logic, that was one of the smartest decisions in asp.net core.

    You can choose between:

    ILogger<T> if you have a need to use factory and DI patterns in your code or you could use the ILogger, to implement simple logging with no DI needed.

    given that, The ILoggerProvider is just a bridge to handle each of the registered log's messages. There is no need to use it, as it does not effect anything that you should intervene in code, It listens to the registered ILoggerProvider and handles the messages. That's about it.

    0 讨论(0)
  • 2020-12-04 07:29

    I would prefer to keep it simple and inject the non generic ILogger

    This seems to be non-default behavior - but is easily wired up with the following:

    services.AddTransient(s => s.GetRequiredService<ILoggerFactory>().CreateLogger(""));
    
    0 讨论(0)
  • 2020-12-04 07:32

    Those are all valid except for ILoggerProvider. ILogger and ILogger<T> are what you're supposed to use for logging. To get an ILogger, you use an ILoggerFactory. ILogger<T> is a shortcut to get a logger for a particular category (shortcut for the type as the category).

    When you use the ILogger to perform logging, each registered ILoggerProvider gets a chance to handle that log message. It's not really valid for consuming code to call into the ILoggerProvider directly.

    0 讨论(0)
  • 2020-12-04 07:35

    Sticking to the question, I believe ILogger<T> is the right option, considering downside of other options:

    1. Injecting ILoggerFactory force your user to give away the control of the mutable global logger factory to your class library. Moreover, by accepting ILoggerFactory your class now can write to log with any arbitrary category name with CreateLogger method. While ILoggerFactory is usually available as a singleton in DI container, I as a user would doubt why any library would need to use it.
    2. While the method ILoggerProvider.CreateLogger looks like it, it is not intended for injection. It is used with ILoggerFactory.AddProvider so the factory can create aggregated ILogger that writes to multiple ILogger created from each registered providers. This is clear when you inspect the implementation of LoggerFactory.CreateLogger
    3. Accepting ILogger also looks like the way to go, but it is impossible with .NET Core DI. This actually sounds like the reason why they needed to provide ILogger<T> at the first place.

    So after all, we have no better choice than ILogger<T>, if we were to choose from those classes.

    Another approach would be to inject something else that wraps non-generic ILogger, which in this case should be non-generic one. The idea is that by wrapping it with your own class, you take full control of how user could configure it.

    0 讨论(0)
  • 2020-12-04 07:46

    Definition

    We have 3 interfaces: ILogger, ILoggerProvider and ILoggerFactory. Let's look at the source code to find out their responsibilities:

    ILogger: is responsible to write a log message of a given Log Level.

    ILoggerProvider: is responsible to create an instance of ILogger (you are not supposed to use ILoggerProvider directly to create a logger)

    ILoggerFactory: you can register one or more ILoggerProviders with the factory, which in turn uses all of them to create an instance of ILogger. ILoggerFactory holds a collection of ILoggerProviders.

    In the example below, we are registering 2 providers (console and file) with the factory. When we create a logger, the factory uses both of these providers to create an instance of logger:

    ILoggerFactory factory = new LoggerFactory().AddConsole();    // add console provider
    factory.AddProvider(new LoggerFileProvider("c:\\log.txt"));   // add file provider
    Logger logger = factory.CreateLogger(); // <-- creates a console logger and a file logger
    

    So the logger itself, is maintaining a collection of ILoggers, and it writes the log message to all of them. Looking at Logger source code we can confirm that Logger has an array of ILoggers (i.e. LoggerInformation[]), and at the same time it is implementing ILogger interface.


    Dependency Injection

    MS documentation provides 2 methods for injecting a logger:

    1. Injecting the factory:

    public TodoController(ITodoRepository todoRepository, ILoggerFactory logger)
    {
        _todoRepository = todoRepository;
        _logger = logger.CreateLogger("TodoApi.Controllers.TodoController");
    }
    

    creates a Logger with Category = TodoApi.Controllers.TodoController.

    2. Injecting a generic ILogger<T>:

    public TodoController(ITodoRepository todoRepository, ILogger<TodoController> logger)
    {
        _todoRepository = todoRepository;
        _logger = logger;
    }
    

    creates a logger with Category = fully qualified type name of TodoController


    In my opinion, what makes the documentation confusing is that it does not mention anything about injecting a non-generic, ILogger. In the same example above, we are injecting a non-generic ITodoRepository and yet, it does not explain why we are not doing the same for ILogger.

    According to Mark Seemann:

    An Injection Constructor should do no more than receiving the dependencies.

    Injecting a factory into the Controller is not a good approach, because it is not Controller's responsibility to initialize the Logger (violation of SRP). At the same time injecting a generic ILogger<T> adds unnecessary noise. See Simple Injector's blog for more details: What’s wrong with the ASP.NET Core DI abstraction?

    What should be injected (at least according to the article above) is a non-generic ILogger, but then, that's not something that Microsoft's Built-in DI Container can do, and you need to use a 3rd party DI Library. These two documents explain how you can use 3rd party libraries with .NET Core.


    This is another article by Nikola Malovic, in which he explains his 5 laws of IoC.

    Nikola’s 4th law of IoC

    Every constructor of a class being resolved should not have any implementation other than accepting a set of its own dependencies.

    0 讨论(0)
  • 2020-12-04 07:48

    This is only possible because ILogger<T> is covariant and is just a wrapperclass that has a dependency on the LoggerFactory. If it wasn't covariant you would definitely be using the ILoggerFactory or ILogger. But ILogger should be discarded because you could be logging to any category and you would loose all context regarding the logging. I think ILoggerFactory would be the best way to go and then use CreateLogger<T>() to create a ILogger<T> inside you class. This way you really have a nice solution because as a developer you would really like to align the category with your classes to jump right to the code and not to some unrelated derived class. (You could add linesnumbers.) You can also let your derived classes use the logger that is defined by the baseclass but then also where to start looking for the source code? Besides this I can imagine you might also have any other ILogger with special purpose category (sub)names in the same class. Nothing is preventing you from have multiple ILogger's in such a case ILoggerFactory just looks cleaner.

    0 讨论(0)
提交回复
热议问题