Others have explained very well the problem with singletons in general. I would just like to add a note about the specific case of Logger. I agree with you that it is usually not a problem to access a Logger (or the root logger, to be precise) as a singleton, via a static getInstance()
or getRootLogger()
method. (unless if you want to see what gets logged by the class you are testing - but in my experience I can hardly recall such cases where this was necessary. Then again, for others this might be a more pressing concern).
IMO usually a singleton logger is not a worry, since it does not contain any state relevant to the class you are testing. That is, the logger's state (and its possible changes) have no effect whatsoever on the state of the tested class. So it does not make your unit tests any more difficult.
The alternative would be to inject the logger via the constructor, to (almost) every single class in your app. For consistency of interfaces, it should be injected even if the class in question does not log anything at present - the alternative would be that when you discover at some point that now you need to log something from this class, you need a logger, thus you need to add a constructor parameter for DI, breaking all client code. I dislike both of these options, and I feel that using DI for logging would be just complicating my life in order to comply with a theoretical rule, without any concrete benefit.
So my bottom line is: a class which is used (almost) universally, but does not contain state relevant to your app, can safely be implemented as Singleton.