C++ Style Logging

Started by
8 comments, last by Aprosenf 18 years, 10 months ago
Hi, I would like to write a C++ logging class for a game. Ideally at any point in my program I could write something like: global_mylog << "FramesPerSecond: " << (int)getFPS(); And in my log file write out something like: "May 29th, 2005: 11:00PM: FramesPerSecond: 60" In a word I want to write a wrapper for operator<< that prepends some arbitrary information. What I have is: class Log : public std::ofstream { public: Log(const char *name) { this->open(name, std::ios::out | std::ios::ate); } ~Log() { if(*this) this->close(); } std::ofstream& operator<<(const char* text) { *((std::ofstream*)this) << "Date/Timestamp/Whatever: " << text; return *this; } }; I would like to have this worth with any type, though -- not just with const char* as the first data type sent to the log. How can I make this work with any data type? Is this possible or am I trying to do this in a backwards fashion? How is logging commonly handled in simple C++ games? Thanks in advance for any input/comments.
Advertisement
I personally almost always just use a simple "logger" class with a Write() function. Usually Write() will be overloaded to allow for the common intrinsic types and a couple of combinations of strings and intrinsics for easier use in critical areas (e.g. floating-point triplets are a common requirement, so I often have an overload which takes a string and three floats). I'm not a huge fan of the whole << operator overloading thing though, so I'm not sure of many of the details of that approach. You will need an operator << overload for each intrinsic type of data you want to pass to your logging class, though.

There's a great article here on GameDev on RTF logging which uses a function-call style approach as well, which you may (or may not) find useful.

Wielder of the Sacred Wands
[Work - ArenaNet] [Epoch Language] [Scribblings]

Quote:Original post by cycad
*snip*

I would like to have this worth with any type, though -- not just with const char* as the first data type sent to the log. How can I make this work with any data type?


you could template it.

class Log : public std::ofstream {   Log() {}   // syntax might nto be exactly correct here   template <typename VAL_TYPE>   ostream &operator << (VAL_TYPE value)   {        *((std::ofstream*)this) << << "date: " << someDateFunc() << value;   }}// usageLog myLog;myLog << "some text" << 23 << 23.45 << someUDTWithOpDefined;


now you can use it with any type that defines a << op

Hope that helps
if you think programming is like sex, you probably haven't done much of either.-------------- - capn_midnight
You could create a new manipulator, similar to endl, but for the begining of the line. It wouldn't be automatic, but still pretty simple.
std::ostream& timestamp(std::ostream& out){   return out << GetCurrentTime() << ": ";}int main(){   std::cout << timestamp << "Frames Per Second: " << fps << endl;   return 0;}


*edit: One big advantage to this approach is control over when things get timestamped. If you just stamp everything you write, you'd end up with something like "09:15:05: Frames per Second: 09:15:05: 60", which is probably not what you want.

CM
Quote:Original post by Conner McCloud

*edit: One big advantage to this approach is control over when things get timestamped. If you just stamp everything you write, you'd end up with something like "09:15:05: Frames per Second: 09:15:05: 60", which is probably not what you want.

CM


good point...use this instead.
if you think programming is like sex, you probably haven't done much of either.-------------- - capn_midnight
Logging is one area where I prefer Macros.

Using a macro allows you to acces infomation such as the current file name line number, which you can't obtain properly in a logging funciton (they'll give you the file and line of the logging function, not the thing being logged).


If you have a log class something like this:
class Log{   //...   void logLine(const char *File, int Line, const std::string &text)   {     //pseudo code     write timestamp.     write file name     write line number.     write message.     write newline.   }   //...}


And you define a macro like this:
#define LOG(msg){std::stringstream stream;stream << msg;globalLog.logLine_FILE__,__LINE__,stream.str());}



Now you can write:
LOG("Frames per second" << (int)getFPS());


You can write any type T that has a
std::ostream& operator << (std::ostream&,T&); overload.

You can use stream manipulators, like oct and hex.

You can put in extra endls as well if you want.

If you use VC++ you can also modify your macro to log the function name as well using __FUNC__.

You don't have to worry about specifically writing your tamestamp either as it's done automatically.
personally I prefer __PRETTY_FUNCTION__ over __FUNC__
The problem with using ostreams for logging is that it's not going to be threadsafe. For example, if you have:

Thread 1:
log << "Hello" << " World" << std::endl;

Thread 2:
log << "This is" << " a test" << std::endl;

Then you can end up, in your log file, with:

HelloThis is World
a test

depending on how the threads are scheduled. Nitage's method is thread safe because it evaluates the complete string before passing it to your "logLine" method. The problem with his method, though, is that you end up creating lots of temporary stringstream objects, which you may not want to do.

The other way you can do it is lock your "log" object when the first component of the line comes in, and unlock it when the std::endl comes in. But then the problem is that if you forget the std::endl, you'll end up blocking your log file until all other threads are finished. You're also locking more than you really need, since you don't need to lock the log object while you're compositing the string, just when you write the completed line to the file.

Anyway, this is all moot if you're completely single-threaded, but just so you know...
Heh, logging - one of my favourite topics :-)

I've written 3 generations of loggers up to now, that were used in my games, and next generation will definitely use operator<< overloading.
Why? you ask. Because I'm tired of writing things like:

// real life exampleif (!fileExists(filename.c_str()))   {    string tmp("Couldn't load UI from file (probably missing): ");    tmp += filename;    logError2("GUI", tmp.c_str());    return false;         }    


when I could simply say:

 if (!fileExists(filename.c_str()))  log << "GUI" << "Couldn't load UI from file (probably missing): " << filename;


Second one is certainly simpler, easier and faster to write - and those 3 things matter very much when writing logger.
After all, if thought about logging more advanced things make you cry, then you won't use logger as much as you would really need - and who wants logger that isn't used? :-)

---

Anyway, I think I'll post here my own logging class. Yup, you may use it if you want, though you shouldn't - something much much better may be written very easily. Just remember to plan everything beforehand... I didn't, multiple logfiles support was added 2 months after original version, and some functions look very awfull because of that :-/

File: logger.h

/*! Changes output of logfile, so that log, printVal etc. will work for this newly selected.*/#define logAttachTo(x) SC::Logger::get().OpenLogFile(x);#define logAttatchToPrevious SC::Logger::get().OpenPreviousLogFile();#define logClose SC::Logger::get().CloseLogFile(); #define logDumpStdHeader SC::Logger::get().DumpStdHeader();#define logDumpStdFooter SC::Logger::get().DumpStdFooter();//@}// ------------------------------------------------- //@{/*! Normal log message and error message, without prefix.*/#define log(x) SC::Logger::get().LogToFile(__FILE__,__FUNCTION__,__LINE__,0,x);#define logError(x) SC::Logger::get().LogError(__FILE__,__LINE__,0,x);//@}// -------------------------------------------------//@{/*! Log and error message with prefix (format: s prefix x).*/#define log2(s,x) SC::Logger::get().LogToFile(__FILE__,__FUNCTION__,__LINE__,s,x);#define logError2(s,x) SC::Logger::get().LogError(__FILE__,__LINE__,s,x);//@}// ------------------------------------------------- //@{/*! Normal, unprefixed log function, but it prints only when __SC_DEBUG__ is defined.*/#ifdef __SC_DEBUG__#define debug(x) SC::Logger::get().LogToFile(__FILE__, __FUNCTION__, __LINE__, 0, x);#else#define debug(x) ;#endif//@}// ------------------------------------------------- //@{/*! Prints value of variable x in form: "x" = x*/#define printVal(x) SC::Logger::get().PrintValToFile(__FILE__,__LINE__,#x,x);#define printTxt(x) SC::Logger::get().PrintTxtToFile(__FILE__,__LINE__,#x,x);//@}// ------------------------------------------------- //@{/*! For starting and ending of groups (ie. INIT engine etc.). Be extra careful, and don't forget to match calls to one of those.*/#define beginG(x) SC::Logger::get().BeginGroup(x);#define endG SC::Logger::get().EndGroup();//@}// ------------------------------------------------- namespace SC {  //! -------------------------------------------------  //! This class shouln't be used directly by user, instead use appropriate macros //! -------------------------------------------------   class Logger : public Singleton <Logger> {  public:         Logger() { }   virtual ~Logger() { }   // -------------------------------------------------             bool Init();   void Shutdown();      void LogDetails(bool _ld) { logDetails = _ld; }   // -------------------------------------------------             void LogToFile(const char * _file, const char * _function, ulint _line, const char * _sender, const char * _msg);   void LogToFile(const char * _file, const char * _function, ulint _line, const char * _sender,                  const std :: string & _msg);   void LogError(const char * _file, ulint _line, const char * _sender, const char * _err);// -------------------------------------------------          void PrintValToFile(const char * _file, ulint _line, const char * _name, slint _value );      void PrintValToFile(const char * _file, ulint _line, const char * _name, ulint _value );   void PrintValToFile(const char * _file, ulint _line, const char * _name, ssint _value );      void PrintValToFile(const char * _file, ulint _line, const char * _name, usint _value );   void PrintValToFile(const char * _file, ulint _line, const char * _name, float _value );            // -------------------------------------------------             void PrintTxtToFile(const char * _file, ulint _line,                       const char * _name, const char * _txt );                          void BeginGroup(const char * _groupName);   void EndGroup();   // -------------------------------------------------          void DumpStdHeader();   void DumpStdFooter();                          // -------------------------------------------------                                 bool OpenLogFile(const char * _name);      void OpenPreviousLogFile();   void CloseLogFile(bool _permission = false);           private:            struct logfileData    {     std :: string name; //!< name of logfile     ulint logLines; //!< how many lines were written up to now     std :: stack <std :: string> groups; //!< assosciated group entries    };              // -------------------------------------------------                 // first: name of logifle, second is it's data   typedef std :: map <std :: string, logfileData*> LogFilesGroup;   typedef std :: map <std :: string, logfileData*> :: iterator LogFilesItor;   typedef std :: pair < std :: string, logfileData*> LogFilePair;       // -------------------------------------------------         //! log file   RawFile log;   bool logDetails;      //! logfiles packed together   LogFilesGroup logfiles;      //! actually opened logfile   logfileData * actual;      //! stack of all previously opened logfiles   std :: stack <logfileData*> logfileHistory;    };}



File: logger.cpp

#include <string>#include "SDL.h"#include "SDL_ttf.h"#include "SDL_image.h"#include "tinyxml.h"#include "logger.h"#include "../Misc/libraryStuff.h"#include "../Misc/misc.h"#include "../../Layer1/MemoryMgr/mmgr.h"// -------------------------------------------------const char InGroupSeparator[]  = { ' ', ' ' };const char GroupStart[] = { " START  " }; const char GroupEnd[] =  { " END    " };const char LineNumberSeparator = ':';const char OneToTenLineNumSeparator[] = { ' ', LineNumberSeparator, ' ', 0 }; // 0 is the null terminatorconst char TenAndUpperNumSeparator[] = { LineNumberSeparator, ' ', 0 }; // --//--const char SenderSeparator[] = " > ";//#define __DEBUG_LOGGER__// -------------------------------------------------// ------------------------------------------------- start Initbool SC :: Logger :: Init() {  actual = 0;  logDetails = false;    if (!OpenLogFile("log.txt")) return false;    DumpStdHeader();  return true; } // ------------------------------------------------- end Init// ------------------------------------------------- start Shutdownvoid SC :: Logger :: Shutdown() {  SC_ASSERT((strcmp("log.txt", log.GetFilename()) == 0))    if (log.IsOK())   {  	DumpStdFooter();	CloseLogFile(true);    }   } // ------------------------------------------------- end Shutdown// ------------------------------------------------- start OpenLogFilebool SC :: Logger :: OpenLogFile(const char * _name) {  LogFilesItor it = logfiles.find(_name);    if ( it == logfiles.end() )    {    // new logfile        logfileData * ptr = new logfileData();    SC_ASSERT(ptr)    if (!ptr) return false;        log.Close();    log.Open(_name, fm_Writing, ft_Text, fc_Overwrite, false);        if (!log.IsOK())     {      // couldn't open logfile!      // well, there's sth screwy going around, so better return to default logfile      // but first, cleanup      SC_ASSERT(!"couldn't open logfile!");            delete ptr;            // prevent infinite recursions (rather not possible but who knows...)      if (!strcmp(_name, "log.txt"))       return false;                  OpenLogFile("log.txt");      // false, couse it's _this_ operation that was failure, not OpenLogFile line above      return false;      }           ptr->name = _name;    ptr->logLines = 1;    actual = ptr;            logfileHistory.push(actual);        logfiles.insert( LogFilePair(_name, ptr));        #ifdef __DEBUG_LOGGER__    log2("Log open new", _name);    #endif    return true;   }    else   {             log.Close();          log.Open(_name, fm_Writing, ft_Text, fc_OpenExisting, false);         if (!log.IsOK())     {      SC_ASSERT("Couldn't open already existing logfile.")      actual = 0;      // prevent infinite recursions       if (strcmp(_name, "log.txt") == 0)       return false;      SC_ASSERT(!"Probably you changed active directory and tried to log message to logfile previously created in"      " old directory");      OpenLogFile("log.txt");      return false;      }         #ifdef __DEBUG_LOGGER__    log("Log open old");    #endif         actual = it->second;    logfileHistory.push(actual);            return true;   } }// ------------------------------------------------- end OpenLogFile// ------------------------------------------------- start CloseLogFilevoid SC :: Logger :: CloseLogFile(bool _permission) {  SC_ASSERT(actual)    #ifdef __DEBUG_LOGGER__  log2("Log close", log.GetFilename());  #endif    if ((!strcmp(log.GetFilename(), "log.txt")) && (!_permission) )   return;    if (log.IsOK())   {    LogFilesItor it = logfiles.find(log.GetFilename());        if ( it == logfiles.end() )     {      // now, THAT'S weird...!      SC_ASSERT(0)      logError2("Logger", "Critical internal bug in logger, please contact with author.")      return;     }         delete (it->second);        SC_ASSERT(logfileHistory.size())                if (!_permission)     {      logfiles.erase(it);      OpenPreviousLogFile();      return;     }     log.Close();      }  }// ------------------------------------------------- end CloseLogFile// ------------------------------------------------- start OpenPreviousLogFilevoid SC :: Logger :: OpenPreviousLogFile() {  SC_ASSERT(actual)  SC_ASSERT(logfileHistory.size())    logfileHistory.pop();    SC_ASSERT(logfileHistory.size())    #ifdef __DEBUG_LOGGER__  log2("Log open previous", logfileHistory.top()->name.c_str());  #endif    OpenLogFile(logfileHistory.top()->name.c_str());  logfileHistory.pop(); }// ------------------------------------------------- end OpenPreviousLogFile// ------------------------------------------------- start DumpStdHeadervoid SC :: Logger :: DumpStdHeader() {  if (log.IsOK())   {	log.WriteLine("--------------------------------------");	log.WriteLine(" ");	log.WriteLine(" Logfile created by ", false);	log.WriteLine(GetLibraryNameAndVersion(), false);	log.WriteLine(", which is using:");// ------------------------------------------------- SDL    SDL_version version;    SDL_VERSION(&version)    std :: string versionStr = " SDL ";    versionStr += IntToString(version.major);    versionStr += ".";    versionStr += IntToString(version.minor);    versionStr += ".";    versionStr += IntToString(version.patch);// ------------------------------------------------- SDL_TTF    TTF_VERSION(&version)    versionStr +=  ";  SDL_TTF ";    versionStr += IntToString(version.major);    versionStr += ".";    versionStr += IntToString(version.minor);    versionStr += ".";    versionStr += IntToString(version.patch);// ------------------------------------------------- SDL_Image    SDL_IMAGE_VERSION(&version)    versionStr +=  ";  SDL_Image ";    versionStr += IntToString(version.major);    versionStr += ".";    versionStr += IntToString(version.minor);    versionStr += ".";    versionStr += IntToString(version.patch);// ------------------------------------------------- TinyXML    versionStr +=  ";  TinyXML ";    versionStr += IntToString(TIXML_MAJOR_VERSION);    versionStr += ".";    versionStr += IntToString(TIXML_MINOR_VERSION);    versionStr += ".";    versionStr += IntToString(TIXML_PATCH_VERSION);// -------------------------------------------------    log.WriteLine(versionStr.c_str());    log.WriteLine(" ");        log.WriteLine(" Engine compilation date: ", false);    log.WriteLine(GetLibraryCompilationTimeAndDate());    // ------------------------------------------------- // Visual requires other function to get time. #ifdef  SC_COMPILER_MINGW    // get the time of application start    time_t startTime;    time( &startTime );        log.WriteLine(" Application start: ", false);    log.WriteLine( ctime(&startTime) );     #endif// -------------------------------------------------   	#ifdef __SC_DEBUG__     log.WriteLine(" ! SC was compiled in debug mode !");     log.WriteLine(" ");	#endif    	log.WriteLine("--------------------------------------");	log.WriteLine(" ");    } } // ------------------------------------------------- end DumpStdHeader// ------------------------------------------------- start DumpStdFootervoid SC :: Logger :: DumpStdFooter() {  if (log.IsOK())   {    log.WriteLine(" ");    log.WriteLine("--------------------------------------");    log.WriteLine(" EOF");             } } // ------------------------------------------------- end DumpStdFooter// ------------------------------------------------- start LogToFile const char *void SC :: Logger :: LogToFile(const char * _file, const char * _function, ulint _line, const char * _sender,                               const char * _msg) {  SC_ASSERT(_msg);  SC_ASSERT(log.IsOK());  SC_ASSERT(actual);      const char * ptr = ulintToString(actual->logLines); // perform conversion  SC_ASSERT(ptr);    std :: string tmp("        ");       tmp += ptr;  if (actual->logLines < 10) tmp += OneToTenLineNumSeparator; // just to keep logfile clean and tidy   else tmp += TenAndUpperNumSeparator;     if (!actual->groups.empty())   {    for (usint i = 0 ; i < actual->groups.size(); ++i)     tmp += "   ";      }    if (_sender)    {        tmp += _sender;    tmp += SenderSeparator;   }     tmp += _msg;      #ifdef __SC_DEBUG__    if (logDetails)   {      // add information about filename and source code line number    	tmp += "\t\t(";	tmp += _file;	tmp += ", ";	tmp += _function;	tmp += ", ";	tmp += ulintToString(_line);	tmp += ")";   }    #endif    if (!(actual->logLines % 5)) tmp +="\n"; // for every 5 lines add space        log.WriteLine(tmp.c_str()); // final flushing of buffer      delete ptr;   ++(actual->logLines); } // ------------------------------------------------- end LogToFile const char * // ------------------------------------------------- start LogToFile string  void SC :: Logger :: LogToFile(const char * _file, const char * _function, ulint _line, const char * _sender,                                const std :: string & _msg) {  LogToFile(_file, _function, _line, _sender, _msg.c_str()); }  // ------------------------------------------------- end LogToFile string // ------------------------------------------------- start LogError void SC :: Logger :: LogError(const char * _file, ulint _line, const char * _sender, const char * _err) {  SC_ASSERT(_err);  SC_ASSERT(log.IsOK());  SC_ASSERT(actual);    const char * ptr = ulintToString(actual->logLines);  SC_ASSERT(ptr);    std :: string tmp(" ERROR: ");    tmp += ptr;  if (actual->logLines < 10) tmp += OneToTenLineNumSeparator;   else tmp += TenAndUpperNumSeparator;     if (!actual->groups.empty())   {    for (usint i = 0 ; i < actual->groups.size(); ++i)     tmp += "   ";      }     if (_sender)    {        tmp += _sender;    tmp += SenderSeparator;   }       tmp += _err;    #ifdef __SC_DEBUG__    if (logDetails)   {    	tmp += "\t\t(";	tmp += _file;	tmp += ", ";	tmp += ulintToString(_line);	tmp += ")";   }  #endif    if (!(actual->logLines % 5)) tmp +="\n";            log.WriteLine(tmp.c_str());      delete ptr;   ++(actual->logLines);   } // ------------------------------------------------- end LogError // ------------------------------------------------- start PrintValToFile slint  void SC :: Logger :: PrintValToFile(const char * _file, ulint _line, const char * _name, slint _value ) {   SC_ASSERT(_name);  SC_ASSERT(log.IsOK());  SC_ASSERT(actual);    const char * ptr = ulintToString(actual->logLines);  SC_ASSERT(ptr);    std :: string tmp(" PRINT: ");      tmp += ptr;  if (actual->logLines < 10) tmp += OneToTenLineNumSeparator;   else tmp += TenAndUpperNumSeparator;    if (!actual->groups.empty())   {    for (usint i = 0 ; i < actual->groups.size(); ++i)     tmp += "   ";      }      tmp += _name;  tmp += " == ";  const char * convertedValue = slintToString(_value);  tmp += convertedValue;  delete [] convertedValue;    #ifdef __SC_DEBUG__    if (logDetails)   {  	tmp += "\t\t(";	tmp += _file;	tmp += ", ";	tmp += ulintToString(_line); // it may couse memory leaks... or not! don't know	tmp += ")";   }  #endif    if (!(actual->logLines % 5)) tmp +="\n";        log.WriteLine(tmp.c_str());      delete [] ptr;   ++(actual->logLines);    }                           // ------------------------------------------------- end PrintValToFile slint// ------------------------------------------------- start PrintValToFile ulint void SC :: Logger :: PrintValToFile(const char * _file, ulint _line, const char * _name, ulint _value ) {  PrintValToFile(_file, _line, _name, (slint) _value);  } // ------------------------------------------------- end PrintValToFile ulint// ------------------------------------------------- start PrintValToFile usint void SC :: Logger :: PrintValToFile(const char * _file, ulint _line, const char * _name, usint _value ) {  PrintValToFile(_file, _line, _name, (ulint) _value);  } // ------------------------------------------------- end PrintValToFile usint// ------------------------------------------------- start PrintValToFile ssint void SC :: Logger :: PrintValToFile(const char * _file, ulint _line, const char * _name, ssint _value ) {  PrintValToFile(_file, _line, _name, (slint) _value);  } // ------------------------------------------------- end PrintValToFile ssint // ------------------------------------------------- start PrintValToFile float  void SC :: Logger :: PrintValToFile(const char * _file,                                 ulint _line,                                 const char * _name,                                 float _value ) {   SC_ASSERT(_name);  SC_ASSERT(log.IsOK());  SC_ASSERT(actual);    const char * ptr = ulintToString(actual->logLines);  SC_ASSERT(ptr);    std :: string tmp(" PRINT: ");      tmp += ptr;  if (actual->logLines < 10) tmp += OneToTenLineNumSeparator;   else tmp += TenAndUpperNumSeparator;    if (!actual->groups.empty())   {    for (usint i = 0 ; i < actual->groups.size(); ++i)     tmp += "   ";      }      tmp += _name;  tmp += " == ";  tmp += floatToString(_value, 15);    #ifdef __SC_DEBUG__    if (logDetails)   {  	tmp += "\t\t(";	tmp += _file;	tmp += ", ";	tmp += ulintToString(_line); // it may couse memory leaks... or not! don't know	tmp += ")";   }  #endif    if (!(actual->logLines % 5)) tmp +="\n";        log.WriteLine(tmp.c_str());      delete [] ptr;   ++(actual->logLines);    }                            // ------------------------------------------------- end PrintValToFile float // ------------------------------------------------- start PrintTxtToFile void SC :: Logger :: PrintTxtToFile(const char * _file,                                 ulint _line,                                 const char * _name,                                 const char * _txt ) {   SC_ASSERT(_name);  SC_ASSERT(log.IsOK());  SC_ASSERT(actual);    const char * ptr = ulintToString(actual->logLines);  SC_ASSERT(ptr);    std :: string tmp(" TEXT:  ");     tmp += ptr;  if (actual->logLines < 10) tmp += OneToTenLineNumSeparator;   else tmp += TenAndUpperNumSeparator;     if (!actual->groups.empty())   {    for (usint i = 0 ; i < actual->groups.size(); ++i)     tmp += "   ";      }     tmp += _name;  tmp += " == ";  tmp += _txt;    #ifdef __SC_DEBUG__    if (logDetails)   {  	tmp += "\t\t(";	tmp += _file;	tmp += ", ";	tmp += ulintToString(_line);	tmp += ")";   }	    #endif    if (!(actual->logLines % 5)) tmp +="\n";        log.WriteLine(tmp.c_str());      delete ptr;   ++(actual->logLines);    }                       // ------------------------------------------------- end PrintTxtToFile // ------------------------------------------------- start BeginGroup void SC :: Logger :: BeginGroup(const char * _groupName) {  SC_ASSERT(_groupName)  SC_ASSERT(actual);     actual->groups.push(_groupName);     std :: string tmp(GroupStart);          if ((actual->logLines-1) % 5)   // when start of group has cross over with every-five-line-newline   tmp.insert(0, "\n"); // there would be ugly 2 blank lines before it          for (usint i = 1 ; i < actual->groups.size(); ++i)   tmp += "   ";      tmp += _groupName;       tmp += "\n";     log.WriteLine(tmp.c_str());    } // ------------------------------------------------- end BeginGroup // ------------------------------------------------- start EndGroup void SC :: Logger :: EndGroup() {        SC_ASSERT(actual);  SC_ASSERT(actual->groups.size())     std :: string tmp(GroupEnd);              if ((actual->logLines-1) % 5)   // when end of group has cross over with every-five-line-newline   tmp.insert(0, "\n"); // there would be ugly 2 blank lines before it     for (usint i = 1 ; i < actual->groups.size(); ++i)   tmp += "   ";     tmp += actual->groups.top();       tmp += "\n";      actual->groups.pop();              log.WriteLine(tmp.c_str());    }  // ------------------------------------------------- end EndGroup


Sample output:

--------------------------------------  Logfile created by Sculpture Clay 1.01, which is using: SDL 1.2.8;  SDL_TTF 2.0.6;  SDL_Image 1.2.4;  TinyXML 2.3.3  Engine compilation date: May 29 2005 16:42:01 Application start: Mon May 30 17:03:11 2005 ! SC was compiled in debug mode ! --------------------------------------  START  ENGINE INIT        1 :    TextureMgr > Succesfully created.        2 :    FontMgr > Succesfully created.        3 :    Renderer > Successfully created.        4 :    ParticleEngine > Successfully created.        5 :    GUI > Succesfully created.        6 :    SC > Everything went OK. END    ENGINE INIT        7 : Config > Opened and parsed config file: config.cfg        8 : Input > Successfully initialized keyboard (323 virtual keys) and mouse.        9 : Kernel > Starting application part.        10: Kernel > Removed all tasks from the list.        11: Renderer > Closed SDL and OpenGL video subsystems. START  ENGINE SHUTDOWN        12:    SC > Shutting down engine...        13:    GUI > Controls states are being saved to file:         14:    FontMgr > Released font: Graphics/Fonts/systemMenu.ttf        15:    FontMgr > Released font: Graphics/Fonts/courier.ttf        16:    GUI > Succesfull release of controls.        17:    GUI > Succesfull save operation.        18:    GUI > Succesfully destroyed.        19:    FontMgr > Succesfully destroyed.        20:    ParticleEngine > Successfull shutdown.        21:    TextureMgr > Succesfully destroyed.        22:    Renderer > Successfull shutdown. END    ENGINE SHUTDOWN -------------------------------------- EOF



Next version will have: iostream like syntax (<<), native support for multiple logfiles, levels of logging, history of functions call (Unreal like), message boxes for some logs etc., tied with console and multiple windows (each log can open new window with it's own console and show what's written there at runtime), main logfile: "appName.log", html formatting (colors, bold, underline, italic), other stuff :-)
You could also do something like:

class Logger{public:	template <class T>	ofstream & operator << (T obj)	{		logfile << timestamp() << obj;				return logfile;	}	private:	ofstream logfile;};


This way, you can do something like

Logger log;
log << "foo " << 1 << ' ' << point3d << endl;

And it would only print one timestamp - the first call to operator << prints the time stamp and the first object (in this case, the string "foo "), and then it returns the ofstream object (NOT the Logger object), so the rest of the calls just look like

logfile << 1 << ' ' << point3d << endl;

This topic is closed to new replies.

Advertisement