Because it has state. An overly generic logger will likely have at least a file-pointer as state, perhaps also a "warning level" to ignore, a verbose flag, etc... You might even have one part of your app plugged into a network logger and another part plugged into a local file logger.That still raises the obvious question: why is your logger an object/class to begin with?
Though I know this isn't what you're getting at
In my new engine core (here and here), I follow ApochPiQ's advice and only provide logging via a global/free function. Despite that, I can still filter out different engine sub-systems, and later I can still add features like network/disk logging, browser-display/formatting, etc...
eiInfoGroup( FooBar, true );//declare and enable log filter "FooBar" eiInfo(FooBar, "couldn't frobnicate %d", foo );//Print to log, or don't depending on filters.
Can you name one? I've been trying for a while to find a justification, but my current engine still has zero. I know that in theory, singletons or monostates are the solution to the problem of "2 instances are always an error", but I'm not sure that this problem actually exists in the real world...Singletons used incorrectly are a bad idea, but Singletons do have their place. There are times when having two instances of a class is an error and may/will cause your program to malfunction.
Not saying you should, but free-functions can still have hidden state. e.g. in the CPP that implements the function, you can have a file-static variable that holds the file pointer. From the outside, it acts like a free function, but internally, it's basically a singleton with it's instantiation policy hidden.Wouldn't a free function require opening and closing of the log file? Would that cause any type of slowdown compared to a class which opens the file on creation and closes before destruction?
N.B. I've seen some loggers continuously open/close the file on purpose, because it ensures that if your program crashes, the log is always written out correctly (normally, the latest log data would be in a buffer, and maybe not yet on disk).
However, for the last few years, no game engine that I've worked on has logged straight to disk like this. The last ~3 engines I've used, all have a "companion" developer application that you run alongside the game -- this usually just sits down in your system tray, and communicates with the game over a socket. When the game needs to perform any dev duties, like logging, the game sends the data over the socket to this companion app. The game doesn't need to know how to rebuild assets or write logs, it just needs to know how to ask the companion/dev app how to do these things.
As above, I think logging should be implemented a a simple global function -- but if this was anything other that logging, then you're doing it correctly.The thing is, we pretty much pass in LogSys class to EVERYTHING!
For emphasis, 99.9% of the time, I don't allow people to use new/malloc in my engine -- they've got to be passed an allocator object if they want to allocate memory. Imagine all the classes which I've got to pass around allocators between! Yet, I still believe this approach is more correct, as it makes your code slightly more functional and have no hidden dependencies/side-effects.