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.
public interface ILogger
{
event EventHandler<LogEventArgs> OnLogAdded;
Type Type { get; }
void Log(string message);
}
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));
}
}
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.