If you want more complex behavior
Something struck me recently. Many companies advertise: "... complex solutions..." I suppose simple solutions to complex problems don't sell. What I want is dead simple behavior that cannot be messed up, either now or somewhere far along the line.
Major thing that is painfully missing in such debates is what constitutes a "module". C++ only knows compilation unit and process.
If we define per-system logging, things become much clearer. If we want Physics, Input and Renderer systems to have modular logging, then each of these has its own. What does this mean? Several ways:
struct System {
public const char * LOG_SYSTEM_NAME = "System";
};
..
struct System {
Logger logger;
};
System::Logger logger = "System";
...
The thing we need in common is a tag unique to system which we append to logs. Logs are typically text files on disk. Tag must be immutable since it will outlive the process by far, so whatever can be done while the code is running is just the birth of a log.
It may seem like deciding upon this tag is the big challenge now, making sure it's unique, but the tag itself, its location and name will be implicitly determine by logical dependencies of the project itself. If suddenly Input wants to log as Renderer, there is something weird going on and in reverse - Input will never need access to Renderer log. If it does, we got emergent complexity of InputRenderer, which is more of a design issue to worry than how the log is stored.
And by implication - if all tags are stored in some global file, we succumb to the God Class or "#include <everywhere.h>" problem, where modularity was lost already - a single change in that file affects everything.
--------
Log aggregation needs to be addressed next. At simplest level each system logs to its own file. We might choose each run to create its own instance in which case filename needs to be stateful in system implementation. Something like:
Logger logger("SYSTEM", Date.now());
Problem of uniqueness now becomes an issue (the static/global/singleton problem). Maybe we add process or machine ID to that. Benefit of this - ability to directly store all logs.
A minor triviality that needs to be solved is how to initialize such system, we might want log location to be externally specified. Again, passing a Config struct or having command-line parser set some common fields works, but one can wrap this into full IoC design. As mentioned, IoC is most intrusive and may not enable third-party libraries to use it.
-------
Advanced logging:
Let's say our logging is not boring, and we track a bunch of stuff, user actions, transactions, etc... with goal of running ETL. The idea of logging straight to database might be tempting in this case and is often done for web services but it's just implementation detail. Typical stumbling blocks here will involve configuration parsing and sequential initialization (config comes from database but to talk to database we need to read config, or database connection may drop or database may become unresponsive).
With the last two issues we come back to state. Stateful loggers are hard and if they hiccup, the application stops.
For this reason we want whatever "tags" our loggers use to be immutable per some unique context (see process, etc.). This way, logging can continue without regard to state (database/file are state) and we can even make use of fragments.
Also, see various logging services provided by OS which prevent logs from overflowing, but useful logs might not be
Then there's fancy fast stuff, such as K/V database (redis, mongo, etc...) which may be good fit with stateless loggers but may be at odds with ETL. Dumb SQL table or flat files might still be simplest way.
-------
Q: "I know, I'll log as XML or HTML"
A: "Any time your application exits unexpectedly (the time you need logs most) the log will not be valid XML and HTML" Don't add complexity - XML/HTML are stateful - the depth and nesting need to be properly terminated which can only happen under ideal circumstances.
Q: "But good logging system will make debugging easy"
A: "No, it will never log the correct information, use debugger or printf, the data will be discarded after each run anyway"
Q: "So, which option is the best"
A: "Depends on goals and how pragmatical one is"
--------
PS: If logging, start each line/entry with date/time in standardized format: YYYYMMDDThhmmss.msecZ. The reason - they are naturally ordered and can be compared using any strcmp algorithm to produce correct ordering. It makes searching easy (str > "2009" && str < "2010") will return expected range using just about any text mangling tool.