Sign in to follow this  
ewil

C++ Style Logging

Recommended Posts

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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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;
}
}

// usage
Log myLog;
myLog << "some text" << 23 << 23.45 << someUDTWithOpDefined;





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

Hope that helps

Share this post


Link to post
Share on other sites
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

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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...

Share this post


Link to post
Share on other sites
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 example

if (!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 terminator
const char TenAndUpperNumSeparator[] = { LineNumberSeparator, ' ', 0 }; // --//--
const char SenderSeparator[] = " > ";

//#define __DEBUG_LOGGER__

// -------------------------------------------------

// ------------------------------------------------- start Init
bool SC :: Logger :: Init()
{
actual = 0;
logDetails = false;

if (!OpenLogFile("log.txt")) return false;

DumpStdHeader();
return true;
}

// ------------------------------------------------- end Init

// ------------------------------------------------- start Shutdown

void SC :: Logger :: Shutdown()
{
SC_ASSERT((strcmp("log.txt", log.GetFilename()) == 0))

if (log.IsOK())
{
DumpStdFooter();
CloseLogFile(true);
}

}

// ------------------------------------------------- end Shutdown

// ------------------------------------------------- start OpenLogFile

bool 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 CloseLogFile

void 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 OpenPreviousLogFile

void 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 DumpStdHeader

void 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 DumpStdFooter

void 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 :-)

Share this post


Link to post
Share on other sites
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;

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