Where does your logging system live?

Started by
4 comments, last by CadetUmfer 15 years, 5 months ago
I'm trying to figure out the best way to implement a logging system into my engine. At the moment, I've got separate modules such as FileIO and Renderer in the core layer of the engine. What would be the best place to put the logging system? I don't really want to make it global, as the engine is going to be multi-threaded. Should I just pass it in to the constructor of each of the modules?
Advertisement
It really depends on your own decisions.

For recording logging messages, I prefer to have a singleton or global or toolbox object (please don't argue about this, it has been debated many times, and logging systems are the classic example of singletons) such that any piece of code can emit a message that includes it's source, subsystem, priority, severity, and so on. All logging through the entire application should use this logging functionality. The class members should be inline functions which can be (if necessary) replaced with empty functions in particular builds.

For handling messages, I prefer to have a message bus or broadcast system, where interested modules register with the logging system and receive all events that meet their criteria.


The fact that it is multithreaded should be unimportant. A properly implemented multithreaded system should have any synchronization issues resolved during design. There are many different lockless ways to implement such a system.
I don't see why singletons / globals would be any less useless for logging system than for most other purposes. In my applications, I just pass a reference to my logging system to any components that need to write log messages.
I tend to make one logging interface per engine module. The game (that collects the parts into a cohesive piece) then configures the logging. Usually they all point to the same target, but some might have different priorities or other decorators as I need them.
Something like this:
Logger::Logger(  const std::string & system,   Logger & parent = Logger::getDefault(),   Config & cfg = Config::getDefault())


class FooSystem {  FooSystem(Application & app = Application::getDefault())    : log("FooSystem", app.masterLog(), app.config())  {}protected:  Logger log;};


References/pointers/smart pointers used as appropriate.
Loggers are hierarchical, configured through configuration file passed.
There is no mutable global instances, Application holds the conceptual global state, it's passed through instances.

In above case, FooSystem::logger would be child of master logger, on creation it loads parameters from application configuration's [FooSystem] section.

The approach lends itself well to full stack allocation, it plays nicely with RAII, and similar.

I deliberately show the singletons (::getDefault()) as default parameter values in the example. The design is perfectly fine for either globals or fully auto-allocated objects.


But basic concepts are simple:
- logger is initialized via RAII
- all major classes are configurable (via config file, defaults object, or some other way)
- I try to maintain strict hierarchy by passing instances via constructors, resulting in clean relations

YMMV
I have an ILogListener interface, with a virtual Write function. Then a Log namespace with a Write function and an AddListener function that takes an ILogListener and adds it to a collection. Log.Write writes iterates through the registered listeners and passes the message to them. Basically, what .NET does.

Can't see why a Singleton would help here.
Anthony Umfer

This topic is closed to new replies.

Advertisement