Sign in to follow this  
schupf

Logging class (without singleton?)

Recommended Posts

Hello, Im writing a little engine which will have this structure: I have a Root object, which acts kinda as a factory. Here is an (pseudo code) example:
Root r = createRoot();
SceneManager sc = r->createSceneManager();
Entity e = sc->createEntity("robot.mesh");
Now I need to log at various parts in my engine (in Root methods, SceneManager methods, Entity Methods) and so on. I wonder how I could implement this logging. Currently I only see 2 possibilites: The Root object stores a pointer to a Logger and passes the Logger to all created objects. Thus EVERY class whose methods ever need to log something needs a pointer to the logger. Another problem is that all created objects wouldnt recognice is if I did change the logger. The other possibility is to make the logger a singleton. Then every class could just log with Logger::getInstance().Log(...). The problem with this approach is: Well, I just dont like singletons and try to avoid them whenever I can. Any suggestions how I could provide a logging feature for all my classes are highly appreciated!

Share this post


Link to post
Share on other sites
Just create some log object at the appropriate scope and use it. This is all I do, on those rare cases where I need logging.

#include "Log.h"

void SomeFunction() {
Log log("somefile");
log << "Hello there."
}

(Yes, the obvious implementation involves opening and closing the backing file often. You can work around this, or just eat the cost since logging is 'slow' anyway, and you probably want to flush the stream constantly regardless to ensure that you always get the most updated file contents, especially if you're logging around a crash).

Share this post


Link to post
Share on other sites
Quote:

I also thought about a global variable. But... aren't global variables even more evil than singletons?

No, they're less evil.

Logging is a mostly-trivial problem. Do the simplest thing that works for you. Don't over-engineer the thing.

Share this post


Link to post
Share on other sites
I've written a handful of loggers. They end up a single, globally defined object, every time.

This is because, in my cases at least, logging has always been a debugging aid, not an integrated part of the functionality. I might decide that any part of the code, at any level, has a sudden need to log. The next day it will be different. As a result, adding logging code doesn't fit neatly into a design process. It's not compartmentalized. And that's a situation that often drives the creation of singletons.

If what you're calling logging is really an audit trail, something that's a critical part of the application and not something you'll ultimately turn off, then it deserves a more formal place in the design.

Share this post


Link to post
Share on other sites
Hehe, yes thats true.

I can't use your approach jpetrie, because my logger is a little bit more compley. For example I can add various Appenders to my logger and thus creating a logger everytime I need to log is no option.

So I guess I just go for the global variable approach.

Share this post


Link to post
Share on other sites
Quote:
Original post by ScottMayo
If what you're calling logging is really an audit trail, something that's a critical part of the application and not something you'll ultimately turn off, then it deserves a more formal place in the design.


Well, I think it just a nice feature to get some consistent feedback from the engine. I will definitely use logging in debug mode AND release mode, though my logging in debug mode will be more verbose (in debug mode I also log the function, source file etc).
Nonetheless I still tend to use a global variable (because passing logger pointers to billions of objects isnt elegant either and I just hate singletons;)

Share this post


Link to post
Share on other sites
Hm, one more question that is related to my problem:
Sooner or later I will need some additional classes which are heavily access from various places. For example a class "TextureManager" which loads and pools textures.
Would you make such "complex" classes like TextureManager also accessible through a global variable, or would you make it a singleton or ...?

Share this post


Link to post
Share on other sites
Quote:
Original post by schupf

I can add various Appenders to my logger


Which is a mandatory killer feature why exactly?

Log4j defines an insanely bloated and confusing to configure logging system, one which log4cpp attempted to copy, but ended up with static initialization order fiasco and two memory leaks.

If you go the ScottMayo way, you not only don't need appenders, but don't need or want any kind of complex state. A global fstream is enough, just << the logs. Or std::clog...

If you go the full complete logging system, then you need to establish logging hierarchy first, then determine various categories and subsystems through which you'll be logging, and so on...

In the end, each of your systems will inevitably end up doing something like this:
class Foo {
Foo(Logger & parent)
: log("FooSystem", parent)
{
log << "Foo just started logging";
}
Logger log;
};
for every logical system.

Neither is more correct or more evil.

But the biggest design disaster is trying to copy log4j's design concepts to C++. The concepts just don't mix. And while doable, they end up in a rather clumsy API.

Share this post


Link to post
Share on other sites
As stated earlier, if each of your classes needed to log what they are doing, they could each have their own instance of a logger. If you want them all to log to a singular place (an in-game console, perhaps) then you can create a single logger, and pass it to any class that needs it.

Ex:

logger lgr("somefile");

window wnd("Blah", 1024, 768, false);

renderer rdr(&lgr, &wnd); // The renderer uses the logger.






This might seem tedious if you want to do a lot of logging, but if you really think about the design of your code you might find a way where only a couple of modules really need to know about the logger.

If you really wanted easy access to the logger from anywhere, a singleton is not necessary. You can just make a global instance of the logger.

Edit: looking at your sample code... Why not just have a logger in the root class. In createSceneManager() or createWhateverSubSystem() just pass it the logger.


SceneManager* Root::createSceneManager() {
return new SceneManager(&logger);
}

Share this post


Link to post
Share on other sites
Quote:
Original post by Antheus
Which is a mandatory killer feature why exactly?

Cause its smart. I just have to write some lines and can log into console, various files and so on.

Quote:
c_olin
Edit: looking at your sample code... Why not just have a logger in the root class. In createSceneManager() or createWhateverSubSystem() just pass it the logger.

The SceneManager can create dozens of different objects (cameras, entities, lights and so on). And most of them could need logging (a little warning if the mesh couldnt be loaded completely, a little warning if one camera parameter was wrong and so on). Passing a logger pointer to ALL objects seems really tedious to me. And actually something like a Logger doesnt belong to a class Entity or Camera, its more a "global" feature.

Share this post


Link to post
Share on other sites
Quote:
Original post by schupf
Quote:
Original post by Antheus
Which is a mandatory killer feature why exactly?

Cause its smart. I just have to write some lines and can log into console, various files and so on.

Quote:
c_olin
Edit: looking at your sample code... Why not just have a logger in the root class. In createSceneManager() or createWhateverSubSystem() just pass it the logger.

The SceneManager can create dozens of different objects (cameras, entities, lights and so on). And most of them could need logging (a little warning if the mesh couldnt be loaded completely, a little warning if one camera parameter was wrong and so on). Passing a logger pointer to ALL objects seems really tedious to me. And actually something like a Logger doesnt belong to a class Entity or Camera, its more a "global" feature.



It's not smart when every single one of your code modules now depends on that log system to exist exactly how it is. Does your scene manager *need* to know anything at all about the loggers' appender functionality?

No it doesn't.

It only needs a pure interface to say "I have a string, do what you want with it"

In essence, each code module (i.e. detachable sections of your engine, such as Scene Hierarchy, Graphics, Audio, Input, and even smaller levels if need be) should have an exposed function pointer or interface class which the game/application can then replace. Much the same how many middlewear libraries allow you to replace their Malloc/Free/Assert etc.

Compartmentalized code is much more maintainable in the long term. Aim for that.

Share this post


Link to post
Share on other sites
Quote:
Original post by scarypajamas
I've got a logging tutorial on my website. It uses a singleton though.

No offense, but to me this looks like taking the concept of the standard output stream, restrict it to a single type (file), denying access to most of its methods and operators by encapsulation and then articially limiting the number of possible instances. That's a lot of added complexity for less functionality. [smile]

Share this post


Link to post
Share on other sites
Well, if we're already dumping loggers: one more (the three files starting with "log")

Some bla bla about the "concept" behind it: http://festini.device-zero.de/blog/ (need to scroll down.. still didn't fix the structure)

Concepts:
-log topics (user defined, like graphics, network, general)
-log targets (console, file, server.. any number per topic)
-log level/filter (one per target)
-not so safe printf-syntax
-global log functions

Meaning: you include the header, a file with your log topics, init the logger somewhere (reads config file) and just use it. No singletons, no instance passing. What for? Logging is supposed to be simple and convenient. Make it a pain to use and nobody will log anything.

It's probably still over-engineered ,-)

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this