As a SD developer, when debugging the code or data, we very often need to follow up with more or less accuracy the progress of SD execution, about the whole game or only some specific component.
As a SD maintainer, getting asked to analyse and fix bugs reported by our end-users, we also need to know more or less precisely what happened before the notified bug in order to try and repeat it (end-users often forget important details when reporting bugs).
From the above, we clearly see that this need often changes, as the issue to examine and analyse changes :
And so that we need to be able to filter the events / follow-up information along these 2 axis taken independently.
A good old answer to this need is to set up a logging API, enabling the developer to log the useful follow-up information / events as textual messages into an output stream (aka the printf design pattern ;-).
In order to ship the above-described multi-axis filtering features, each tracing / logging "functions" must enable the developer to specify :
Note: A component here can be taken as a real one in the application : a module, a library, ... But we can also extend the concept to any "code geography" object that we'd need to filter information (example : a specific car in the game, as a cross-real-component object).
And as SD is a multi-threaded software, each logged event / information must at least be time-stamped (to get an idea of the order of operations).
At run-time, the developer must be able to specify, in a simple manner, and without recompiling any code (through an XML configuration file),
Through a simple trick, this criticity level threshold allows the developer to filter the traces along the 2 requested axis : of course the criticity level itself, but also the component axis (a criticity level threshold higher that the maximum ... actually completely inhibits output for the specified component).
The API must also enable the developer to specify by code the filtering settings for each component : as default ones, or simply for changing values when needed while the game is running.
Being able to customise output formatting would be also good.
We'll use the logger concept :
The logging system is mainly implemented through the GfLogger? class (source:trunk/src/libs/tgf/tgf.h) ; here's a sum-up of it's public API :
class TGF_API GfLogger { public: //! Trace level / criticity : enum or integer if > eDebug (ascending level means descending criticity). enum { eFatal = 0, eError, eWarning, eInfo, eTrace, eDebug }; //! Change criticity level threshold. void setLevelThreshold(int nLevel); //! Change output lines header columns selection. enum { eNone=0, eTime=0x01, eLogger=0x02, eLevel=0x04, eAll=eTime|eLogger|eLevel }; void setHeaderColumns(unsigned bfHdrCols); //! Change output stream ("stdout" or "stderr" or a file name). void setStream(const std::string& strPathname); //! Tracing functions (name gives the trace level / criticity). void fatal(const char *pszFmt, ...); // Warning : This one calls exit(1) at the end ! void error(const char *pszFmt, ...); void warning(const char *pszFmt, ...); void info(const char *pszFmt, ...); void trace(const char *pszFmt, ...); void debug(const char *pszFmt, ...); //! Generic tracing function (you must specify the level, enum or integer if > eDebug). void message(int nLevel, const char *pszFmt, ...); //! Instance getter (you can't readily instanciate loggers). static GfLogger* instance(const std::string& name); };
Note that it is only a sum-up : there are other methods useful for run-time logging configuration (and of course other "technical" ones you can ignore as a "simple" user).
To log an information in the code of a specific component, you simply have to use one of the "tracing" functions above (fatal, error, warning, info, ... etc), and of course the logger instance that is dedicated to the component to follow-up.
First example, not recommended (the one-line way ; easy but : verbose syntax, not very efficient at run-time) :
// Needs a '#include <tgf.h>' line above ! GfLogger::instance("MyLogger")->info("Something interesting happened (x=%d, y=%d, name=%s)\n", x, y, name);
Second example = the recommended way (shorter syntax, the most efficient at run-time ; but : a bit more code) :
Declare the logger instance in some .h file of the component to follow-up (let's say "MyComponentTools.h") :
// Needs a '#include <tgf.h>' line above ! extern GfLogger* PMyLog; #define MyLog (*PMyLog)
Define the logger instance in some .cpp source file of the component (whatever) :
// Needs a '#include "MyComponentTools.h"' line above ! GfLogger* PMyLog = GfLogger::instance("MyLogger");
Log events / information where needed in the code
// Needs a '#include "MyComponentTools.h"' line above ! MyLog.info("Something interesting happened (x=%d, y=%d, name=%s)\n", x, y, name);
That way (nothing more needed), you should already see your trace line in the game console while running it (provided that your trace line added in the code is on the way :-).
Now, as you probably noticed, you never called the new operator for your "MyLogger?" logger, and you never specify it's criticity level threshold ; it's because :
If your logger is not declared in <user settings>/config/logging.xml, no problem : it'll get default settings when automatically instantiated (criticity level threshold = debug, output stream = stderr, all header columns).
If you don't like these default settings, simply add a section for your logger in <user settings>/config/logging.xml.
Note: When specifying a file as the target stream, only specify its name (no path supported) ; it'll then be re-created at each game start-up in the <user settings> folder.
And at any time in the code, when relevant, you can even override these settings through the setLevelThreshold, setStream and setHeaderColumns methods.
Mainly for backward compatibility reasons, the logging system automatically creates a default logger, named "Default" ; it serves as :
It is exported by the tgf library, as the GfLogDefault logger instance, so you can use it this way, as an example :
// Needs a '#include <tgf.h>' line above ! GfLogDefault.debug("Something interesting happened (x=%d, y=%d, name=%s)\n", x, y, name);
which does exactly the same as the now deprecated :
// Deprecated : use GfLogDefault.debug ! // Needs a '#include <tgf.h>' line above ! GfLogDebug("Something interesting happened (x=%d, y=%d, name=%s)\n", x, y, name);
Unlike other loggers, the default logger is initialised through 2 separated steps :
And these "XML settings" can also be overridden through the command line, when using the -e/--tracelevel and -t/-tracestream switches (use the -h switch to get help about these).