Logging Displayers
The IDisplayer Interface class
The NLMISC::IDisplayer interface class is at the core of this system. All of the following classes implement this interface:
- CFileDisplayer - Log to a file.
- CMemDisplayer - Logs strings into memory to be written to a custom logger.
- CLightMemDisplayer - Logs only the message into memory to be written to a custom logger.
- CMsgBoxDisplayer - MS Windows Only! Logs to a NeL assert-style message box.
- CStdDisplayer - Logs to the stdout variable. Functions in both MS Windows and Linux.
- CWindowDisplayer - MS Windows Only! Logs to a Service-style window.
- CNetDisplayer - Logs to a NLNET-based logging server.
To implement the NLMISC::IDisplayer class you really need only to implement the following method on the IDisplayer interface:
virtual void doDisplay (const NLMISC::CLog::TDisplayInfo &args, const char *message)
The "message" carries the actual message logged by the call, ala nlinfo and friends. It's the most important variable in the method call. If nothing else you should handle this member. Note that the CLightMemDisplayer does this, it ignores the "args" member entirely and logs only the "message". This is not to say that the "args" member is not important. It contains a wealth of useful information. All of the information that creates the header of a NeL log entry is retreived from the "args" member. The bold text is an example of the header:
2005/03/12 14:50:29 INF 4440 MS system_info.cpp 511 NLMISC::*CSystemInfo *getProcessorFrequency : SI: CSystemInfo: Processor frequency is 1599 MHz
Here's the TDisplayInfo structure:
struct TDisplayInfo { TDisplayInfo() : Date(0), LogType(CLog::LOG_NO), ThreadId(0), FileName(NULL), Line(-1), FuncName(NULL) {} time_t Date; // the current date and time. TLogType LogType; // the type of log (NO, ERROR, INFO, DEBUG, etc) std::string ProcessName; // the name of the process running uint ThreadId; // the ID of the thread this was logged in const char *FileName; // the file name in the code where the logging occurred sint Line; // the line in the code that the logging occurred const char *FuncName; // the name of the function that made the logging entry std::string CallstackAndLog; // contains the callstack and a log with not filter of N last line (only in error/assert log type) };
Here's an example as to how you could use this structure in the "doDisplay" method:
std::string str; str+= "Test Displayer: "; if(args.Date != 0) { str +=dateToHumanString(args.Date); } str += " "; str += message; std::cout << str.c_str();
And this is what the output would look like if the message passed to it was "test message":
Test Displayer: 2005/03/12 14:50:29 test message
Looking at the CFileDisplayer source will give you a good feeling for how you can write your formatting code, the rest (for example the std::cout call) is totally dependent upon your implementation. For example I wrote a displayer to output logging information into a WxWidget dialog box. I copied CFileDisplayer and changed it so that the pointer dlgDebug was saved in the constructor and changed the file output code to:
dlgDebug->AppentText(str.c_str());
You can see the full code to my wxWidgets displayer at the bottom of the article.
Using IDisplayer Utility Methods
The IDisplayer interface also implements a handful of useful static methods for converting data types stored in the TDisplayInfo structure.
Note that all four methods return a "const char *".
static const char * logTypeToString (CLog::TLogType logType, bool longFormat=false) static const char * dateToHumanString () static const char * dateToHumanString (time_t date) static const char * dateToComputerString (time_t date)
The logTypeToString method converts "args.LogType" to a human readable string. If you look at the TLogType you will see that it is an enum with members like:
- LOG_NO
- LOG_ERROR
- etc.
So the logTypeToString method will return a value related to that. It derives it's information from the NLMISC::LogTypeToString variable, if you look at it you have two options, a long logging format (Warning, Error) or a short logging format (WRN, ERR). By default the logTypeToString method will return a short format.
The dateToHumanString method has two implementations. Both return a format like "2005/03/12 14:50:29." The first, with no arguments, converts the current date into the human readable format. The second, accepting ''date'' as an argument, converts the specified date into the human readable format.
Finally dateToComputerString converts the argument date into a format like ''784551148''
Also note that as part of the IDisplayer constructor the name of the displayer is saved in a public string called ''DisplayerName'' in case you need to find out what type or kind of displayer you are currently interacting with. The name is also used in creating the mutex the displayer works in as well as identifying it if you need to remove a displayer from a CLog logging object.
Using A Custom Displayer
As described in the logging\ article, you can add these displayers with the following code:
NLMISC::InfoLog->addDisplayer(pointerToDisplayer);
You can also create custom CLog implementations and subscribe your displayer to it.
A list of valid NLMISC default logging objects:
- NLMISC::ErrorLog
- NLMISC::WarningLog
- NLMISC::InfoLog
- NLMISC::DebugLog
- NLMISC::AssertLog
If you look at the CLog class you will see the methods regarding adding, removing and otherwise manipulating assigned displayers:
// add a new displayer to the list, if bypassFilter is true it adds the displayer to a list that ignores logging filters. void addDisplayer (IDisplayer *displayer, bool bypassFilter=false) // return the first displayer using the specified name. IDisplayer * getDisplayer (const char *displayerName) // remove a displayer based upon its pointer. void removeDisplayer (IDisplayer *displayer) // remove a displayer based upon its name (which is why DisplayerName is important) void removeDisplayer (const char *displayerName) // returns true if the specified pointer is attached to the logging object. bool attached (IDisplayer *displayer) const // returns true if there are no displayers on the logging object. bool noDisplayer () const
Example WxWidgets Displayer
This displayer takes logging information and formats it like the file logging output and appends it to a TextCtrl on a custom dialog box.
wxDisplayer.h
class CWxDisplayer : virtual public NLMISC::IDisplayer { public: CWxDisplayer(dlgDebug *dlgDebug, bool eraseLastLog = false, const char *displayerName = "", bool raw = false); CWxDisplayer(); ~CWxDisplayer (); void setParam (dlgDebug *dlgDebug, bool eraseLastLog = false); protected: virtual void doDisplay ( const NLMISC::CLog::TDisplayInfo& args, const char *message ); private: dlgDebug *m_DlgDebug; bool _NeedHeader; uint _LastLogSizeChecked; bool _Raw; };
wxDisplayer.cpp
CWxDisplayer::CWxDisplayer(dlgDebug *dlgDebug, bool eraseLastLog, const char *displayerName, bool raw) : NLMISC::IDisplayer (displayerName), _NeedHeader(true), _LastLogSizeChecked(0), _Raw(raw) { setParam(dlgDebug,eraseLastLog); } CWxDisplayer::CWxDisplayer() : IDisplayer (""), _NeedHeader(true), _LastLogSizeChecked(0), _Raw(false) { ; } CWxDisplayer::~CWxDisplayer() { ; } void CWxDisplayer::setParam (dlgDebug *dlgDebug, bool eraseLastLog) { m_DlgDebug=dlgDebug; //dlgDebug->dlgDbgText->WriteText("test"); } void CWxDisplayer::doDisplay ( const NLMISC::CLog::TDisplayInfo& args, const char *message ) { bool needSpace = false; std::string str; if(m_DlgDebug==NULL) return; if (args.Date != 0 && !_Raw) { str += dateToHumanString(args.Date); needSpace = true; } if (args.LogType != NLMISC::CLog::LOG_NO && !_Raw) { if (needSpace) { str += " "; needSpace = false; } str += logTypeToString(args.LogType); needSpace = true; } // Write thread identifier if ( args.ThreadId != 0 && !_Raw) { if (needSpace) { str += " "; needSpace = false; } str += NLMISC::toString(args.ThreadId); needSpace = true; } if (!args.ProcessName.empty() && !_Raw) { if (needSpace) { str += " "; needSpace = false; } str += args.ProcessName; needSpace = true; } if (args.FileName != NULL && !_Raw) { if (needSpace) { str += " "; needSpace = false; } str += NLMISC::CFile::getFilename(args.FileName); needSpace = true; } if (args.Line != -1 && !_Raw) { if (needSpace) { str += " "; needSpace = false; } str += NLMISC::toString(args.Line); needSpace = true; } if (args.FuncName != NULL && !_Raw) { if (needSpace) { str += " "; needSpace = false; } str += args.FuncName; needSpace = true; } if (needSpace) { str += " : "; needSpace = false; } str += message; m_DlgDebug->dlgDbgText->AppendText(str.c_str()); }