c# – Adding logging by D (SOLID). What's the best?

Question:

As far as I understand, it would be wrong to create a static field that stores the ILogger, as it entails an implicit dependency. Then, it turns out, you need to pass ILogger through the constructor to the classes that require this dependency? I found very little material on this matter, do not blame me, I will be glad to any advice or link. I really want to maintain the correct architecture that adheres to the principles of SOLID, I do not pretend to be the correct opinion, but just want to get some advice. Thanks!


I would like to add to the question. Here is a code snippet, I pass ILogger to this class as a dependency, but inside the class there are also interfaces, also passed as dependencies through the constructor. I need logging in their methods. It turns out you need to inject a dependency on ILogger into each method? It doesn't seem very pretty, how would you suggest? I removed the extra code:

class PrintCommand : ICommand
{
    private readonly IRepository repository;
    private readonly IDataExport dataExport;
    private readonly ILogger logger;

    public PrintCommand(IRepository repository, IDataExport dataExport, ILogger logger)
    {
        this.repository = repository ?? throw new ArgumentNullException(nameof(repository));
        this.dataExport = dataExport ?? throw new ArgumentNullException(nameof(dataExport));
        this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
    }

    public CommandResult Execute(string[] args)
    {
        // какой-то код

        // В методах GetProducts и GetString мне хотелось бы тоже вести лог
        var products = repository.GetProducts(shopId);
        return new CommandResult(dataExport.GetString(products));
    }
}

Thank you all very much for the answers and advice, I read Mark Siman, I will soon add an implementation / solution to somehow summarize

Answer:

If you strictly follow the SOLID principle, then you should take into account the SRP – the principle of single responsibility.

Logging is a separate responsibility that the team class should not be concerned with. The team shouldn't have a logger at all.

Instead, you can make a logging shell (decorator pattern).

We PrintCommand logger from PrintCommand :

class PrintCommand : ICommand
{
    private readonly IRepository repository;
    private readonly IDataExport dataExport;

    public PrintCommand(IRepository repository, IDataExport dataExport)
    {
        this.repository = repository ?? throw new ArgumentNullException(nameof(repository));
        this.dataExport = dataExport ?? throw new ArgumentNullException(nameof(dataExport));
    }

    public CommandResult Execute(string[] args)
    {
        return new CommandResult(...);
    }
}

Making a decorator:

class LoggingCommand : ICommand
{
    private readonly ICommand command;
    private readonly ILogger logger;

    public LoggingCommand(ICommand command, ILogger logger)
    {
        this.command = command ?? throw new ArgumentNullException(nameof(command));
        this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
    }

    public CommandResult Execute(string[] args)
    {
        // логируем входные параметры
        logger.Log(/* args */);

        var result = command.Execute(args);

        // логируем результат
        logger.Log(/* result */);

        return result;
    }
}

Usage:

IRepository repository = ...;
IDataExport dataExport = ...;
var printCommand = new PrintCommand(repository, dataExport);

ILogger logger = ...;
var loggingPrintCommand = new LoggingCommand(printCommand, logger);

Next, we pass loggingPrintCommand instead of printCommand to where ICommand is required.

This is easily accomplished with dependency injection. Mark Siman's book focuses on DI customization for decorators.


In addition to decorators, inheritance and strategy injection can be used for the same purpose. There are three main ways to extend the capabilities of classes. But decorators are convenient in that they are easy to nest one into another and they are friendly with DI.

However, in the decorator, you need to write wrappers for all methods, which is tedious and tedious. AOP – interceptors can come to the rescue, which is again well written in the book of Siman. Recommend!

Scroll to Top