I'm not sure I can add anything to what has already been said about making a logging class thread-safe. As has been stated, to do this you must synchronize access to the resource, i.e., the log file, so that only one thread attempts to log to it at a time. The C# lock
keyword is the proper way to do this.
However, I will address (1) the singleton approach and (2) the usability of the approach that you ultimately decide to use.
(1) If your application writes all of its log messages to a single log file, then the singleton pattern is definitely the route to go. The log file will be opened at startup and closed at shutdown, and the singleton pattern fits this concept of operations perfectly. As @dtb pointed out, though, remember that making a class a singleton does not guarantee thread safety. Use the lock
keyword for that.
(2) As for the usability of the approach, consider this suggested solution:
public class SharedLogger : ILogger
{
public static SharedLogger Instance = new SharedLogger();
public void Write(string s)
{
lock (_lock)
{
_writer.Write(s);
}
}
private SharedLogger()
{
_writer = new LogWriter();
}
private object _lock;
private LogWriter _writer;
}
Let me first say that this approach is generally ok. It defines a singleton instance of SharedLogger
via the Instance
static variable and prevents others from instantiating the class by virtue of the private constructor. This is the essence of the singleton pattern, but I would strongly recommend reading and following Jon Skeet's advice regarding singletons in C# before going too far.
However, what I want to focus on is the usability of this solution. By 'usability,' I'm referring to the way one would use this implementation to log a message. Consider what the invocation looks like:
SharedLogger.Instance.Write("log message");
That whole 'Instance' part just looks wrong, but there's no way to avoid it given the implementation. Instead, consider this alternative:
public static class SharedLogger : ILogger
{
private static LogWriter _writer = new LogWriter();
private static object _lock = new object();
public static void Write(string s)
{
lock (_lock)
{
_writer.Write(s);
}
}
}
Notice that the class is now static, which means that all of its members and methods have to be static. It's not substantively different from the earlier example, but consider its usage.
SharedLogger.Write("log message");
That's much simpler to code against.
The point is not to denigrate the former solution, but to suggest that the usability of whatever solution you choose is an important aspect not to be overlooked. A good, usable API can make code simpler to write, more elegant, and easier to maintain.