C++ Style Logging
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.
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.
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.
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
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.
*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
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.
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:
And you define a macro like this:
Now you can write:
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.
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.
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...
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:
when I could simply say:
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
File: logger.cpp
Sample output:
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 :-)
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:
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;
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
Popular Topics
Advertisement