- Using globals is always wrong. They create a mess with initialization ordering, threading and dependencies.
- A free function would open the door for dozens of globals. Enable/Disable logging to OutputDebugString() - you need a global flag. Write log to a file? You need another global flag and a global variable storing the file handle.
- Singletons, global variables in disguise, rob you of all flexibility (like logging networking related stuff in file 1 and graphics related stuff in file 2) and gets in the way of unit testing.
- Passing some semi-global application class around is just hugely increasing dependencies since now you don't know which objects a class will look up through the application class (the service locator anti-pattern).
The way you're doing it (constructor injection) is quite alright, though I agree that having to pass your logger everywhere you want to create an instance of class X tends to have a negative impact on usability and adds complexity to the interface.
Some other options:
- Equip your classes with a SetLogger() method. By default, the logger is NULL, therefore no logging is performed. If you want to log something, you simply assign the logger to the class post construction.
- Do not add logging to your classes directly, wrap them in a logging wrapper (eg. a LoggedRenderer around your Renderer). Obviously can only log calls and results and with some effort exceptions leaving the renderer.
- Add a Log event (as in signals/slots) to your class. This is just another variant of the SetLogger() idea, of course.
- Forfeit logging altogether. I've done this in my code. I'm going all-out with assertions for pedantically checking internal states and exceptions for any usage error. Since the point of logging is to help you find the cause of errors quickly, by not letting any inconsistency, bad return code or missing capability slip under the carpet you remove the need for logging.