简体   繁体   中英

Logger dependency injection, getting calling class name and source file path

In a WinForms app, there is Logger class that is a form designed for logging, so that any class can call it.

There is a static Configuration class, inside which a Logger lives.

Previous implementation

Various classes would call the logger like so:

public class ImportController
{
    public void import()
    {
        try
        {
            // do the work...
        }
        catch (Exception ex)
        {
            Configuration.logger.log("Something failed");
            Configuration.logger.log(ex);
        }
    }
}

Current implementation

The logger implements the following interface, which was extracted from it as part of refactoring to enable unit testing calling classes through dependency injection:

public interface ILogger
{
    void (string message, [CallerMemberName] string member = "", [CallerLineNumberAttribute] int lineNumber = -1, string fileName = "");
    void (Exception ex, [CallerMemberName] string member = "", [CallerLineNumberAttribute] int lineNumber = -1, string fileName = "");
}

As can be seen, the idea is to have it automatically log the calling class name and source file path.

The following is an example of an attempt to inject a logger into all classes that use it, in this instance the ImportController from above:

public class ImportControllerLogger
{
    public void log(string message, [CallerMemberName] string member = "", [CallerLineNumber] int line_num = -1, string filename = "")
    {
        Configuration.log.log(string message, "ImportController", lineNumber, @"Controllers\ImportController.cs");
    }

    public void log(Exception exception, [CallerMemberName] string member = "", [CallerLineNumber] int line_num = -1, string filename = "")
    {
        Configuration.log.log(exception, "ImportController", lineNumber, @"Controllers\ImportController.cs");
    }
}

public class ImportController
{
    ILogger _logger;

    public ImportController(ILogger logger)
    {
        this._logger = logger;
    }

    public void import()
    {
        try
        {
            // do the work...
        }
        catch (Exception ex)
        {
            _logger.log("Something failed");
            _logger.log(ex);
        }
    }
}

Questions

Is this the correct approach to decouple the logger from all classes that use it?

It seems it might be better to create a single "LoggerHelper" class, that abstracts away the logger so that any class can make a call to it, instead of creating such a class for every calling class. How can the name of the calling class and source file path for the calling class be logged, in a proper way, without resorting to manually specifying it for each class? It worked in the previous implementation with the attributes.

I also had to implement something like that. The code is simplified.

  • ILogger
public interface ILogger
{
    event EventHandler<LogEventArgs> OnLogAdded;

    Type Type { get; }

    void Log(string message);
}
  • Logger
public class Logger : ILogger
{
    public Type Type { get; }

    public Logger(Type type)
    {
        Type = type;
    }

    public event EventHandler<LogEventArgs> OnLogAdded;

    public void Log(string message)
    {
        EventHandler<LogEventArgs> handler = OnLogAdded;
        handler?.Invoke(this, new LogEventArgs(message));
    }
}
  • LogProvider
public static class LogProvider 
{
    private static List<ILogger> loggers = new List<ILogger>();

    public static ILogger CreateLogger<T>()
    {
        if (loggers.Select(x => x.Type.Equals(typeof(T))).Count() > 0)
        {
            throw new Exception($"There is allready a logger for the type {typeof(T)}");
        }
        ILogger logger = new Logger(typeof(T));
        logger.OnLogAdded += OnLogAdded;
        loggers.Add(logger);
        return logger;
     }

    private static void OnLogAdded(object sender, LogEventArgs e)
    {
        //add log to your config
    }
}

And you can use it like this:

public class SampleView
{
    private ILogger logger = LogProvider.CreateLogger<SampleView>();

    public SampleView()
    {
        logger.Log("TestLog");
    }
}

I don't know if this is the best implementation, but it works like a charm.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM