Logging - if not a singleton, what?

Started by
4 comments, last by nife87 16 years, 3 months ago
Ok, so what exactly is a good, low bug-potential, safe way, to create a logger? I've seen opinions about singletons being ok because "they fit the criteria" and really really bad for other reasons that never seem to stick around in my head because I'm really focused on other things and I suppose they have never come back to haunt me yet (well I haven't really ever used them). Thanks. Cheers -Scott
Advertisement
It depends on your exact requirements, but a global variable generally works well. Note that C++'s built-in logger is a global variable. Note also that a logger does NOT fit the criteria for a singleton: It is not logically impossible for one to create multiple loggers (in fact, people tend to do it a lot).
Globals are OK, even though the fact a singleton is a global is often an argument levied against the singleton pattern (really, the argument is that a singleton is no better than a global.)

That said, there are a few alternate approaches.

One is to simply create a logger class which can be instantiated at an appropriate scope, for example, a texture cache has a logger, the renderer has another logger. Each can open their own file. In some cases, the logger might be better off as a static class member, for example, when many of that class will exist and need access to logging facilities (for example if your enemy class required logging.)

If you want all logging to go to a single file, you can implement the logger classes with an internal, private static member which owns and is responsible for the log file. In this case, the logger classes essentially present an interface into the single, internal logging system. This is probably the only real way to have a single file without polluting the global namespace.

If your application supports multi-threading, you will have to add the appropriate measures to the internal logging system, but at least its confined to one place.

throw table_exception("(? ???)? ? ???");

Quote:Original post by ravyne2001
One is to simply create a logger class which can be instantiated at an appropriate scope, for example, a texture cache has a logger, the renderer has another logger. Each can open their own file. In some cases, the logger might be better off as a static class member, for example, when many of that class will exist and need access to logging facilities (for example if your enemy class required logging.)
It's a fine approach, but that particular reasoning is flawed. It's the tail wagging the dog. First decide whether you want one file or a lot of files, then figure out how to implement it. Don't tailor your feature set to your architectural choices. It eats at the programmer's soul.
Quote:If you want all logging to go to a single file, you can implement the logger classes with an internal, private static member which owns and is responsible for the log file. In this case, the logger classes essentially present an interface into the single, internal logging system. This is probably the only real way to have a single file without polluting the global namespace.
a.k.a. a scoped global. Really, namespace pollution is something of a bugaboo. If you feel like it, put the logger class in an anonymous namespace and the logger variable in global scope. Presto: exactly as much namespace pollution as having the class global and the variables local.
Quote:If you want all logging to go to a single file, you can implement the logger classes with an internal, private static member which owns and is responsible for the log file. In this case, the logger classes essentially present an interface into the single, internal logging system. This is probably the only real way to have a single file without polluting the global namespace.


This looks close enough to some singleton implementations.

A related architecture would be to have segmented isolated log dumps. That is, each "loggable" class would log to its own logger along with its time stamp and scope ID.

When you want to generate the final log, your logger simply scans all the sources and aggregates the log results that match your query (eg. like keeping log entries for a specific time frame only, or a selection of scope ID's). Obviously you'll only need to do this when you're just about to write the log file (or present it to the user).

One way to implement this scheme is to add the logging functionality to your "source" classes's base, or define a new ILogger interface.

Check out War to the Core the world domination space MOBA-RTS hybrid.
Join us on Discord.
Into sci-fi novels? Then check out Spectral Legends.

My logger (not a singleton, but could be kept in one or just as a global variable) just keeps track of logs (actually, they are log dispatchers), log targets (the actual loggers) and a default log (which, by default, will buffer all its messages and log them to the new default log, once it has been replaced, should one choose to do so).
Log (log dispatchers) are just log holders - they dispatch the messages, they are supposed to log, to their log targets (if they have any assigned, that is).
Log targets - they can log to anything, anywhere (files, consoles, whatever - the user can register new log target types) and can be shared among multiple logs (since the logger itself owns both logs and log targets and only hands out weak pointers).
Every subsystem may have a log attached (no logs are attached by default, meaning that the user decides, who, where, when and what is logged) - log targets makes it possible to subsystems logging to multiple targets (or even multiple logs).
Log messages are just standard text + source + type objects (where both logs and log targets will only log/dispatch log messages relevant to their specified logging level).

It sounds a bit complex, but IMO it is actually quite simple to utilize and gives me the opportunity to decide the logging process very detailed without much effort. I just wanted a little bit more from my logger :)

This topic is closed to new replies.

Advertisement