5

I was reading about the disadvantages of singleton patterns. A valid use of singleton suggested in many forums is the Logging application. I was wondering why this is a valid use of the pattern. Aren't we maintaing the state information in memory throughout the application?

Why not just use a function:

class Logger
{
    public static void Log(string message)
    {
         //Append to file

    }
}
Community
  • 1
  • 1
Nemo
  • 20,986
  • 9
  • 41
  • 56

5 Answers5

5

To answer "why not just use a function": this code works incorrectly in multi-thread logging. If two threads try to write the same file, an exception will be thrown. And this is why it's good to use singleton for logging. In this solution, we have a thread safe singleton container, other threads push messages(logs) into the container safely. And the container(always a thread-safe queue) writes the messages/logs into a file/db/etc one by one.

Cheng Chen
  • 39,413
  • 15
  • 105
  • 159
2

It is better to declare interface:

interface ILogger
{
    public void Log(string message);
}

Then implement specific type of logger

class FileLogger : ILogger
{
    public void Log(string message)
    {
         //Append to file
    }
}

class EmptyLogger : ILogger
{
    public void Log(string message)
    {
         //Do nothing
    }
}

And inject where required. You will inject EmptyLogger in tests. Using singleton will make testing harder, because you'll have to save to file in tests too. If you want to test if class makes correct log entries, you can use mock and define expectations.

About injection:

public class ClassThatUsesLogger
{
    private ILogger Logger { get; set; }
    public ClassThatUsesLogger(ILogger logger) { Logger = logger }
}

ClassThatUsesLogger takes FileLogger in production code:

classThatUsesLogger = new ClassThatUsesLogger(new FileLogger());

In tests it takes EmptyLogger:

classThatUsesLogger = new ClassThatUsesLogger(new EmptyLogger());

You inject different loggers in different scenarios. There are better ways to handle injections, but you'll have to do some reading.

EDIT

Remember you can still use singleton in your code, as others suggested, but you should hide its usage behind interface to loosen dependency between a class and specific implementation of logging.

LukLed
  • 30,174
  • 17
  • 80
  • 106
  • I didn't understand the inject part. Could you please elaborate? – Nemo Mar 16 '12 at 02:42
  • A class that requires the logger, but does not create the logger, has one passed in during the construction of a class (or most commonly via the constructor) is referred to as having the logger *injected* into the class. http://en.wikipedia.org/wiki/Dependency_injection – Erik Philips Mar 16 '12 at 02:54
1

In most circumstances the Singleton design pattern is not recommended, because it is a kind of Global State, hides dependencies (making APIs less obvious) and also hard to test.

Logging is not one of those circumstances. This is because logging does not affect the execution of your code. That is, as explained here: http://googletesting.blogspot.com/2008/08/root-cause-of-singletons.html :

your application does not behave any different whether or not a given logger is enabled. The information here flows one way: From your application into the logger.

You probably still don't want to use Singleton pattern though. Not quite at least. This is because there's no reason to force a single instance of a logger. What if you wanted to have two log files, or two loggers that behaved differently and were used for different purposes?

So all you really want for logger is to make it easily accessible from everywhere when you need it. Basically, logging is a special circumstances where the best way to go is to have it globally accessible.

  1. The easy way is to simply have a static field in your application that contains the instance of logger:

    public final static LOGGER = new Logger();
    
  2. Or if your logger is created by a Factory:

    public final static LOGGER = new LoggerFactory().getLogger("myLogger");
    
  3. Or if your logger is created by a DI container:

    public final static LOGGER = Container.getInstance("myLogger");
    
  4. You could make your logger implementation be configurable, either through a config file, that you can set to "mode = test" when you are doing testing, so that the logger in those cases can behave accordingly, either not logging, or logging to the console.

    public final static LOGGER = new Logger("logConfig.cfg");
    
  5. You could also make the logger's behavior be configurable at runtime. So when running tests you can simply set it up as such: LOGGER.setMode("test");

  6. Or if you don't make the static final, you can simply replace the static LOGGER with a test logger or mocked logger in the setup of your test.

  7. Something slightly fancier you can do that is close to a Singleton pattern but not quite is:

    public class Logger
    {
        private static Logger default;
        public static getDefault()
        {
            if(default == null)
            {
                throw new RuntimeException("No default logger was specified.");
            }
            return default;
        }
    
        public static void setDefault(Logger logger)
        {
            if(default != null)
            {
                throw new RuntimeException("Default logger already specified.");
            }
            default = logger;
        }
    
        public Logger()
        {
        }
    }
    
    public static void main(String [] args)
    {
        Logger.setDefault(new Logger());
    }
    
    @Test
    public void myTest()
    {
        Logger.setDefault(new MockedLogger());
    
        // ... test stuff
    }
    
Didier A.
  • 3,650
  • 2
  • 35
  • 38
1

I'm not sure what you are referring to when you ask about state information remaining in memory, but one reason to favour singleton over static for logging is that singleton still allows you to both
(1) program to abstractions (ILogger) and
(2) adhere to the dependency inversion principle by practicing dependency injection.

You can't inject your static logging method as a dependency (unless you want to pass something like Action<string> everywhere), but you can pass a singleton object, and you can pass different implementations like NullLogger when writing unit tests.

Jay
  • 51,986
  • 8
  • 91
  • 118
1

A singleton logger implementation allows for you to control easily how often your logging is being flushed to disk or the db. If you have multiple instances of the logger then they could all be trying to write at the same time which could cause collisions or performance issues. The singleton allows this to be managed so that you only flush to the store during quiet times and all your messages are kept in order.

Mike Parkhill
  • 5,223
  • 1
  • 26
  • 35
  • And I agree with @Jay and LukLed that you should be defining your logger as an interface to allow for inversion of control. It will make your testing much easier. – Mike Parkhill Mar 16 '12 at 02:52