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());
}