Sign in to follow this  

Making a logger accessable to all classes?

This topic is 4836 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

There's two ways to do this.

One is to do what Telastyn says, and use the
"extern" keyword for each of your files


logger g_logger in your main file, then any other file that uses it, use
extern logger g_logger

to declare it.
--
The other, more OO way is to use a flywheel / singleton.

Each class will assume it owns it's own verson of your logger, but that logger points to a single instance of logger. There's a singleton tutorial on this website.

Share this post


Link to post
Share on other sites
Instead of having a single global Logger object, you could make the Logger class a singleton:


class Logger
{
public:

// The single Logger object
static Logger logger;

// ...

private:

// Make constructors private to prevent object construction
Logger();
Logger(const Logger&);

// ...
};



Note that this is only a very simple way to implement a singleton, and is definitely not the best way to do things. If you haven't already, you should read up on singletons.

Share this post


Link to post
Share on other sites
One of the ways I sometimes like to handle singletons is through a "handle" which you use it through:

template <typename SingletonT> class Singleton
{
static SingletonT singleton;
public:
static SingletonT & operator*( void ) { reutrn singleton; }
static SingletonT * operator->( void ) { return &singleton; }
};
template <typename SingletonT> SingletonT Singleton::singleton;

class MySingletonClass
{
//...
};
typedef Singleton< MySingletonClass > MySingleton;

void function_that_uses_singleton ( void )
{
MySingleton mysingleton;
mysingleton->CallFunction();
(*mysingleton).CallFunction();
}

Share this post


Link to post
Share on other sites
wow, thats tricky.... Ive been trying to get it working, but I got a small problem when compiling on the overload operators lines:

c:\Documents and Settings\Colin\My Documents\Colin's Projects\Narcotik\CSingleton.h(15): error C2802: static member 'operator *' has no formal parameters
c:\Documents and Settings\Colin\My Documents\Colin's Projects\Narcotik\CSingleton.h(15): error C2333: 'CSingleton<SingletonT>::operator`*'' : error in function declaration; skipping function body
c:\Documents and Settings\Colin\My Documents\Colin's Projects\Narcotik\CSingleton.h(16): error C2801: 'operator ->' must be a non-static member
c:\Documents and Settings\Colin\My Documents\Colin's Projects\Narcotik\CSingleton.h(16): error C2333: 'CSingleton<SingletonT>::operator`->'' : error in function declaration; skipping function body


Any idea?

Share this post


Link to post
Share on other sites
operator -> and operator * cannot be statics, however the error I recall is something informing you of that. Possibly the templates are messing with the compiler's errors or something, but I am sure that operator -> and operator * can't be statics.

edit: Well DUUUUUUUUUUUUUUUUUUUURRRRR it has it right there, I must be blind. sorry for the stupidity.

Share this post


Link to post
Share on other sites
my logger is not OO, and i dont think it needs to be!
This is mutch cleaner!

iLog.h

#define ILOGFILENAME "iLog.txt"

iLogInit(bool do_we_log);
iLog(char *c);
iLogNum(float f);

iLog.cpp

bool log;

iLogInit(bool do_we_log){
log = do_we_log;
//creates/clears the log file
};
iLog(char *c){
if(!log)return;
//opens the file, and logs the string (0-terminated)
};
iLogNum(float f){
if(!log)return;
//opens the file, and logs the number(converted to string)
};

This is so easy and clean, only include iLog.h anywhere you want to log something, no globals, no templates, no singeltons, just a simple logger... Its slow, but you dont want to log something that runs 1000000 times a sec, no problem....
You can easely create a macro, witch uses the _LINE_ and _FILE_ strings automaticly, and so on...

Share this post


Link to post
Share on other sites
Using a singleton the way you suggest instead of a global is just syntactic difference IMO, it's not more OO or anything. As long as you're not actually passing a logger instance (in this case: the only instance) to each user of the logger, you're just doing global variables with different syntax (which is not necessarily bad).

@uncutno:
It's not a good idea to open the file, write and close in each logger call. This'll reduce performance, especially when outputting complex data.

Share this post


Link to post
Share on other sites
Q: Coder
It's not a good idea to open the file, write and close in each logger call. This'll reduce performance, especially when outputting complex data.

This is the cons of this method.

The pros:
Its bullit safe, if your app craches, the file is closed, and the log file wont be touched, you got simple noOO functioncalls instead of a singelton... Why do we need a singelton in this problem? The logger dont need to have any stored variables?

If you miss the Init func, the new log will be added to the old log, no problem.

If i understand this right, there is no reason for a log file to be 1 MB large, you are suposed to read it.... If you load 1000000 enteties from a file, you are only interested in the ones that messed things up. add a logical filter around the log filter!

I use my logfile for testing, and in my app, you have to press a key combination at the start for it to start to log, i dont want end users to se the logfile, but they can if needed!

If you use the logger all the time, you get alot of reading ahead, my logger can easely produce hundreds of pages in seconds..

If you got this supercomplex data you want to log, add the func:
iLogSuperComplexData(SuperComplexDataClass *a);
Now you got all the bennefits!

My argument is: dont optimize or overcomplex things because of speed or design when you dont need to... a logger is a perfect example of "dont need to"...

Share this post


Link to post
Share on other sites
You could have your objects know who owns them and have their owner look after logging.


struct CanLog {
void Log(const std::string&);
};

struct Owner : CanLog {
};

struct SomeClass {
SomeClass(Owner* owner) : m_owner(owner) {
}
void Log(const std::string& message) {
m_owner->Log(message);
}
private:
Owner* m_owner;
};



This way means you can have multiple logs, you can add extra information to the log if you want, it is more modular as you aren't relying upon global variables and singletons, this makes testing easier etc.

Obviously there is the memory overhead of a pointer to the owner for any class that wants to be able to Log. You may already have this in your design so it won't be any new burden. Also, if there is a deep hierarchy of ownership (not necessarily inheritance), then there will be multiple function calls until you actually log something.

Or you can pass context as a parameter into a function which will allow you to log. E.g.

void MakeSoup(const Kitchen& kitchen);

where the kitchen supports logging.

Share this post


Link to post
Share on other sites
the logger i wrote is a static member of my engine and i provided static methods to write log entries. i'm sure there are downsides to this, but i haven't encountered any as of yet.

Share this post


Link to post
Share on other sites
Quote:
Original post by AyCee
the logger i wrote is a static member of my engine and i provided static methods to write log entries. i'm sure there are downsides to this, but i haven't encountered any as of yet.

One benefit of a singleton over static members is ability to use virtual functions. With static members you can't suddenly decide you want to log to a database instead of a file. With a singleton (if it's properly designed), you can. Not much of a problem for a game engine logger though, as those practically never change.

Share this post


Link to post
Share on other sites
Quote:
Original post by CoffeeMug
Quote:
Original post by AyCee
the logger i wrote is a static member of my engine and i provided static methods to write log entries. i'm sure there are downsides to this, but i haven't encountered any as of yet.

One benefit of a singleton over static members is ability to use virtual functions. With static members you can't suddenly decide you want to log to a database instead of a file. With a singleton (if it's properly designed), you can. Not much of a problem for a game engine logger though, as those practically never change.


There is a hidden message in CoffeeMug's post:

(static data + static functions) != singleton

Singleton is defined to be:

Quote:

Ensures a class only has one instance, and provide a global point of access to it.

Share this post


Link to post
Share on other sites
uncutno:

Regarding opening and closing a file to ensure that the contents are wrote to disk; It's a lot easier to use the flush command to write the data to disk. If you're using the c++ filestreams then it's just myfilestream.flush(); iirc.

There's no excuse not to leave the file open while the program runs.

Share this post


Link to post
Share on other sites
Quote:
Original post by Kibble
operator -> and operator * cannot be statics, however the error I recall is something informing you of that. Possibly the templates are messing with the compiler's errors or something, but I am sure that operator -> and operator * can't be statics.

edit: Well DUUUUUUUUUUUUUUUUUUUURRRRR it has it right there, I must be blind. sorry for the stupidity.


doh! now why the heck would they go and implement such a silly limitation as that? (in seriousness, I _am_ curious...)

To get the template mojo I posted to work, remove "static" from the decleration of both operators... also, you'll want to replace:

template <typename SingletonT> SingletonT Singleton::singleton;


with:

template <typename SingletonT> SingletonT Singleton<SingletonT>::singleton;


and this time I blurdy tested it, and it compiles and runs ;-).

Share this post


Link to post
Share on other sites
But a monostate has the same flexibility as a singleton - you change the underlying function's implementation and the rest of the world is none the wiser. Smells like polymorphism to me.

Share this post


Link to post
Share on other sites
Quote:
Original post by antareus
But a monostate has the same flexibility as a singleton - you change the underlying function's implementation and the rest of the world is none the wiser. Smells like polymorphism to me.


That's nowhere close to polymorphism. Changing some code you wrote that sits inside a global function and accesses global variables is... well it's plain C coding. There's nothing remotely polymorphic about it. Everything is decided at compile-time. By putting a class around it, all you really gain is a namespace and the ability to hide those global variables from the rest of the code. Other than that, it's plain C coding, and in poor style because you have [edit]a lot of[/edit] global variables.

Polymorphism decides which of several functions to call at run-time. Of course, you can do the same thing with function pointers in straight C, but the code to do that is a lot messier. It's nice to have the C++ compiler take care of that for you, along with all the nice inheritance logic implemented so you don't have to deal with it. (Just because almost any language feature can be duplicated in almost any other language doesn't mean it's worth doing it.)

Share this post


Link to post
Share on other sites
Oh yeah, I almost forgot my original point. When creating a singleton, it is much better to have a static pointer to your class than a static object. You can't control the order in which global object's constructors/deconstructors get called, which can cause problems if you're not careful. If you have a static pointer to an object, then you can initialize/uninitialize/re-initialize it when you need to, and in the proper order.

And as stated above, you get the ability to use virtual functions. So you can write a CFileLogger, a CDatabaseLogger, a CSyslogLogger, and so on, and switch between them if you want at run-time.

As a side-note, loggers should always have different logging levels. For normal runs you may set the logging level fairly low, but then crank it up when you're hunting something specific down (without having to recompile). This is especially useful when the product goes out into the field and an end-user has a problem. Macros that check the logging level before calling the logging function can make it pretty safe to put logging calls in all but the most performance-sensitive code.

Share this post


Link to post
Share on other sites

This topic is 4836 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

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