Skip navigation links

Source code for C_Logging

Download

The source files for this module are listed below.  You can also download the module C_Logging as a zip archive; this archive contains all the source files and documentation.

Description

This module contains a logging class, several log destination classes and an indentation controlling class.  Together they enable you to easily add logging support to your code including time stamping and thread detection.  This can be useful when you e.g. cannot use a debugger or when you want to debug applications running on a remote computer.

Information

This code C_Logging uses our other code modules C_HiResTimer and optionally C_Lock, which are also available for download at our website.

CLog allows you to log all sorts of data to a log destination.  Log destinations can be e.g. a file, a messagebox, the debugger or a console window.  These four destination examples have already been worked out for you as a CLogDestFile, CLogDestMsgBox, CLogDestDebugger and CLogDestConsole respectively.  You can also create your own log destinations by deriving a class from CLogDest yourself.

You can instantiate a logger object of type CLogger that will be the access point for the log.  Also instantiate a CLogDest derived object that represents the logging destination, and hand over this object to the logger.  From then on you simply pass the data to the logger, and it will be appended to the log destination.  It is easiest to create a global logger and log destination object, and arrange the access to the logger via a common header file.  But you can of course also create multiple loggers, each e.g. logging to another log file.  When using only one logger in a multithreaded application, make sure you #define LOGGER_MULTITHREADED in your project settings; this way the logger will add extra code to synchronize access to the log and logging settings.

CLogger uses a line-oriented approach.  You can opt to let each log entry be placed on it's own line (easy log operations), or you can opt to specify the end of each log line by hand by calling the method EndLogLine() at the end of the log line (enables you to build up the log line piece by piece).  You can also dynamically switch between these modes by calling the method SetOneLogEntryPerLine().  If a (partial) log line has already been build up then it will be written out automatically.

You can opt to add time stamps and/or thread ID's to each log line.  The time stamps are in seconds since the logger was created, and the thread ID enables you to identify which thread performs what actions in which sequence.  When you need to insert this sort of information by hand, you can also call LogTimeStamp() and LogThreadID() manually.  There's also LogGLE(), which logs the last error according to GetLastError(); it logs the error number and associated error description.

CLogger can log individual log entries by a series of overloads of the Log() method.  This way it's more convenient to log all sorts of individual data items.  CLogger also has the () operator overloaded to allow abbreviated log actions like

  log("something");
  log(23.6)
Note that the setting OneLogEntryPerLine doesn't really play well with logging short single data items this way.

There are multiple ways to log data; either build the log line piece by piece by appending the parts (numbers, strings, etc), by writing to it via a format mask using the Logf() method (like using the printf function), or by directly writing to the log buffer that is accessible by a call to Buffer().  When you modify the buffer directly, make sure to call BufferDone() afterwards to get your log content processed.  When you append Unicode text (wide characters) to the log, it will first be transformed to ANSI and then placed in the buffer.

CIndent is a helper class that you can use to add indentation to your logs.  Simply create an indenting object, in- and decrease it's indentation with ++ and --, and pass it to the logging functions as an argument.  It will automatically expand to the right number of spaces.

CLogDestFile will write the log output to a file on disk.  You can tell it to flush the log data to file after every log write; this way you never miss a log entry might your application e.g. crash.  This will decrease logging performance, though, so you can also opt to keep the log file open and keep appending to it.

CLogDestMsgBox will simply pop up a messagebox when a log entry is written.  When you use this log destination, it is therefore better to put as much information on your log line as possible in stead of logging every single bit of information.

CLogDestDebugger will pass the log data on to the active debugger.  If there is no debugger attached to your application, nothing will happen.

CLogDestConsole will create a DOS console for you in which the log data will be written.  When you place the focus on the console, you can also press a key to insert a tagline in the log to distinguish logging phases.  Note that the tagline will be added when the next logging action occurs, not when you press the key.

The logger keeps a buffer in which the log data is appended to while building up a log line.  You can specify the buffer size in a global #define named LOGGER_LINEBUFFERSIZE; if you do not specify a log line buffer size yourself a buffer of 65535 bytes is used.  The contents of the buffer will be passed to the log destination after each log line is ended, but make sure you do not exceed the used buffer size per log line.

Files

Each file belonging to this source code module is listed below.

Logging.h

/*******************************************************************************

  Version: 4
  Author:  Carl Colijn, TwoLogs
  Contact: c.colijn@twologs.com
  Source:  http://www.twologs.com/en/resources/sourcecode.asp

  This code is freely distributable, as long as this comment remains intact.
  If you find this source useful, you may use this code in your own projects
  free of charge, but some acknowledgement to the author of this code is always
  appreciated :)
  The source is however distributed 'as is' without waranty and/or support, and
  may not be fit for each and every application.  Use it at your own discretion
  and at your own risk.
  The source already has undergone testing.  This doesn't mean however that all
  bugs are removed from this piece of code.  If you find one of them, please
  contact me about it.  I can however not guarantee when and if the bug will be
  fixed.

  More information about this module can be found in the accompanying HTML file.

*******************************************************************************/
#ifndef INCLUDE_TWOLOGS_COMMON_LOGGING_H
#define INCLUDE_TWOLOGS_COMMON_LOGGING_H

#include <windows.h>
#include <string>
#include "..\C_HiResTimer\HiResTimer.h"

#ifdef LOGGER_MULTITHREADED
#include "..\C_Lock\Lock.h"
#endif

#ifndef LOGGER_LINEBUFFERSIZE
#define LOGGER_LINEBUFFERSIZE 65535
#endif

// Indentation for logging
// Use ++ and -- to in- and decrease indentation
// Just pass the indentation to the log to indent the next log output
class CIndent {
public:
  // Con- & destructor
  CIndent(long nSpaceCountPerIndent);
  ~CIndent();

  // In- and outdents
  const CIndent& operator ++();
  const CIndent& operator --();

  // Returns the textual representation for the current indentation level
  LPCSTR Value() const;
  operator LPCSTR() const;

private:
  // The number of spaces per indentation
  long m_nSpaceCountPerIndent;

  // Single indentation
  std::string m_sSingleIndent;

  // Total indentation
  std::string m_sTotalIndent;
};

// Log destination base class
class CLogDest {
public:
  // Con- & destructor
  CLogDest();
  virtual ~CLogDest();

  // Dumps the given string to the log destination
  virtual void Dump(const char* sLog) = 0;
};

// Log-to-file destination
class CLogDestFile: public CLogDest {
public:
  // Con- & destructor
  CLogDestFile(const char* sFilePath, bool bFlushOnLog);
  virtual ~CLogDestFile();

  // Dumps the given string to the log destination
  virtual void Dump(const char* sLog);

private:
  // The file to log to
  FILE* m_hFile;

  // The file/path to log to
  std::string m_sFilePath;

  // Whether to flush on each log
  bool m_bFlushOnLog;
};

// Log-to-MessageBox destination
class CLogDestMsgBox: public CLogDest {
public:
  // Con- & destructor
  CLogDestMsgBox(const char* sTitle);
  virtual ~CLogDestMsgBox();

  // Dumps the given string to the log destination
  virtual void Dump(const char* sLog);

private:
  // The title of the messagebox
  std::string m_sTitle;
};

// Log-to-debugger destination
class CLogDestDebugger: public CLogDest {
public:
  // Con- & destructor
  CLogDestDebugger();
  virtual ~CLogDestDebugger();

  // Dumps the given string to the log destination
  virtual void Dump(const char* sLog);
};

// Log-to-Console destination
class CLogDestConsole: public CLogDest {
public:
  // Con- & destructor
  CLogDestConsole(DWORD nLinesToBuffer = 256, bool bWaitBeforeTerm = false);
  virtual ~CLogDestConsole();

  // Dumps the given string to the log destination
  virtual void Dump(const char* sLog);

private:
  // The in/output handle to the console
  HANDLE m_hConsoleIn;
  HANDLE m_hConsoleOut;

  // Whether to wait before termination
  bool m_bWaitBeforeTerm;
};

// Logging class
class CLogger {
public:
  // Con- & destructor
  CLogger(
    CLogDest* poLogDest,
    bool bOneLogEntryPerLine = true,
    bool bLogThreadID = false,
    bool bLogTimeStamp = false,
    int nTimeStampAccuracy = 4
  );
  virtual ~CLogger();

  // Logging settings
  void SetOneLogEntryPerLine(bool bNewSetting);  // default = true
  void SetLogThreadID(bool bNewSetting);         // default = false
  void SetLogTimeStamp(bool bNewSetting);        // default = false
  void SetTimeStampAccuracy(int nNewSetting);    // default = 4

  // Retrieves a writable buffer to append log entries to
  // Call BufferDone to mark when you're done with the buffer
  char* Buffer();
  void BufferDone();

  // Gets how many bytes are still free in the buffer
  DWORD FreeBufferSize();

  // Logs the given log entry
  void Log(const char* sLogEntry = NULL);
  void Log(const wchar_t* sLogEntry);
  void Log(const std::string& sLogEntry);
  void Log(long nLogEntry, long nRadix = 10);
  void Log(unsigned long nLogEntry, long nRadix = 10);
  void Log(bool bLogEntry);
  void operator ()(const char* sLogEntry = NULL);
  void operator ()(const wchar_t* sLogEntry);
  void operator ()(const std::string& sLogEntry);
  void operator ()(long nLogEntry);
  void operator ()(unsigned long nLogEntry);
  void operator ()(bool bLogEntry);

  // Logs the formatted argument list to the buffer
  void Logf(const char* sFormat, ...);

  // Logs a time stamp
  void LogTimeStamp();

  // Logs the current thread's ID
  void LogThreadID();

  // Logs the GetLastError
  void LogGLE();

  // Ends the log's current line, adding time stamps and/or thread ID's
  void EndLogLine();

private:
  // The log destination
  CLogDest* m_poLogDest;

  // The log buffer
  char m_sLogBuffer[LOGGER_LINEBUFFERSIZE + 1];
  DWORD m_nBufferUsed;

  // The next position to log to in the buffer
  char* m_sNextLogPos;

  // Whether to put each log entry on it's own line
  bool m_bOneLogEntryPerLine;

  // Whether to log a thread ID to each line
  bool m_bLogThreadID;

  // Whether to log a time stamp to each line
  bool m_bLogTimeStamp;

  // The timestamp format
  long m_nTimeStampAccuracy;
  char m_sTimeStampFormat[20];

  // The log timer
  CHiResTimer m_oTimer;

#ifdef LOGGER_MULTITHREADED
  // Access to the log buffer
  CLock m_oBufferLock;
#endif

  // Makes sure the buffer end point is in sync
  void SyncBufferEndPoint();

  // Creates a new time stamp header
  void CreateTimeStampHeader();
};

#endif // INCLUDE_TWOLOGS_COMMON_LOGGING_H

Logging.cpp

#include "Logging.h"
#include <stdio.h>

/*******************************************************************************

  CIndent

*******************************************************************************/

// Con- & destructor
CIndent::CIndent(long nSpaceCountPerIndent):
 m_nSpaceCountPerIndent(nSpaceCountPerIndent) {
  // Calculate a single indentation
  m_sSingleIndent.assign(m_nSpaceCountPerIndent, ' ');
}
CIndent::~CIndent() {
}

// In- and outdents
const CIndent& CIndent::operator ++() {
  m_sTotalIndent += m_sSingleIndent;
  return *this;
}
const CIndent& CIndent::operator --() {
  if (m_sTotalIndent.size() > 0) {
    m_sTotalIndent.erase(0, m_nSpaceCountPerIndent);
  }
  return *this;
}

// Returns the textual representation for the current indentation level
LPCSTR CIndent::Value() const {
  return m_sTotalIndent.c_str();
}
CIndent::operator LPCSTR() const {
  return m_sTotalIndent.c_str();
}




/*******************************************************************************

  CLogDest and derivatives

*******************************************************************************/

// Con- & destructor
CLogDest::CLogDest() {
}
CLogDest::~CLogDest() {
}

// Constructor
CLogDestFile::CLogDestFile(const char* sFilePath, bool bFlushOnLog):
 m_bFlushOnLog(bFlushOnLog) {
  m_hFile = fopen(sFilePath, "w");
  if (m_bFlushOnLog) {
    fclose(m_hFile);
    m_sFilePath = sFilePath;
  }
}

// Destructor
CLogDestFile::~CLogDestFile() {
  if (!m_bFlushOnLog)
    fclose(m_hFile);
}

// Dumps the given string to the log destination
void CLogDestFile::Dump(const char* sLog) {
  if (m_bFlushOnLog)
    m_hFile = fopen(m_sFilePath.c_str(), "a+");
  fprintf(m_hFile, sLog);
  if (m_bFlushOnLog)
    fclose(m_hFile);
}

// Constructor
CLogDestMsgBox::CLogDestMsgBox(const char* sTitle):
 m_sTitle(sTitle) {
}

// Destructor
CLogDestMsgBox::~CLogDestMsgBox() {
}

// Dumps the given string to the log destination
void CLogDestMsgBox::Dump(const char* sLog) {
  MessageBox(NULL, sLog, m_sTitle.c_str(), MB_OK);
}

// Constructor
CLogDestDebugger::CLogDestDebugger() {
}

// Destructor
CLogDestDebugger::~CLogDestDebugger() {
}

// Dumps the given string to the log destination
void CLogDestDebugger::Dump(const char* sLog) {
  OutputDebugString(sLog);
}

// Constructor
CLogDestConsole::CLogDestConsole(DWORD nLinesToBuffer, bool bWaitBeforeTerm):
 m_bWaitBeforeTerm(bWaitBeforeTerm) {
  AllocConsole();
  m_hConsoleIn = GetStdHandle(STD_INPUT_HANDLE);
  m_hConsoleOut = GetStdHandle(STD_OUTPUT_HANDLE);
  COORD oBufferSize;
  oBufferSize.X = 80;
  oBufferSize.Y = nLinesToBuffer;
  SetConsoleScreenBufferSize(m_hConsoleOut, oBufferSize);
  SetConsoleTitle("LOG WINDOW - Press a character to insert it's mark line");
}

// Destructor
CLogDestConsole::~CLogDestConsole() {
  if (m_bWaitBeforeTerm) {
    MessageBox(NULL, "Exit logger", "Exit logger", MB_ICONINFORMATION);
  }
  CloseHandle(m_hConsoleIn);
  CloseHandle(m_hConsoleOut);
  FreeConsole();
}

// Dumps the given string to the log destination
void CLogDestConsole::Dump(const char* sLog) {
  // Look if to insert a mark line first
  DWORD nNumEvents;
  GetNumberOfConsoleInputEvents(m_hConsoleIn, &nNumEvents);
  if (nNumEvents > 0) {
    INPUT_RECORD* aoEvents = new INPUT_RECORD[nNumEvents];
    DWORD nNumEventsRead;
    ReadConsoleInput(m_hConsoleIn, aoEvents, nNumEvents, &nNumEventsRead);
    for (DWORD nEventNr = 0; nEventNr < nNumEventsRead; ++nEventNr) {
      if (aoEvents[nEventNr].EventType == KEY_EVENT) {
        if (!aoEvents[nEventNr].Event.KeyEvent.bKeyDown) {
          // Yes -> do so
          std::string sTagLine = "\n";
          char sTagLineChar = aoEvents[nEventNr].Event.KeyEvent.uChar.AsciiChar;
          for (int nPartNr = 0; nPartNr < 16; ++nPartNr) {
            sTagLine += "= ";
            sTagLine += sTagLineChar;
            sTagLine += " =";
          }
          sTagLine += "\n";
          DWORD nWritten;
          WriteConsole(
            m_hConsoleOut,
            sTagLine.c_str(),
            sTagLine.size(),
            &nWritten,
            NULL
          );
        }
      }
    }
    delete[] aoEvents;
  }

  // And write out the new stuff
  DWORD nWritten;
  WriteConsole(m_hConsoleOut, sLog, strlen(sLog), &nWritten, NULL);
}




/*******************************************************************************

  CLogger

*******************************************************************************/

// Buffer access
#ifdef LOGGER_MULTITHREADED
#define LOGGER_GETLOCKACCESS \
  CLockController oLocker(m_oBufferLock)
#else
#define LOGGER_GETLOCKACCESS
#endif

// Constructor
CLogger::CLogger(
  CLogDest* poLogDest,
  bool bOneLogEntryPerLine,
  bool bLogThreadID,
  bool bLogTimeStamp,
  int nTimeStampAccuracy
):
 m_poLogDest(poLogDest),
 m_nBufferUsed(0),
 m_bOneLogEntryPerLine(bOneLogEntryPerLine),
 m_bLogThreadID(bLogThreadID),
 m_bLogTimeStamp(bLogTimeStamp),
 m_nTimeStampAccuracy(nTimeStampAccuracy) {
  // Initialize the logging buffer
  m_sNextLogPos = m_sLogBuffer;
  *m_sLogBuffer = '\0';

  // Make sure the string always ends in a '\0'
  m_sLogBuffer[LOGGER_LINEBUFFERSIZE] = '\0';

  // Create the time stamp header
  CreateTimeStampHeader();

  // And log the starting time of this session
  SYSTEMTIME dNow;
  GetLocalTime(&dNow);
  char sToday[256];
  GetDateFormat(LOCALE_USER_DEFAULT, 0, &dNow, NULL, sToday, 255);
  char sNow[256];
  GetTimeFormat(LOCALE_USER_DEFAULT, 0, &dNow, NULL, sNow, 255);
  m_oTimer.Start();
  Logf("New session started on %s at %s\n", sToday, sNow);
  if (!m_bOneLogEntryPerLine) {
    EndLogLine();
  }
}

// Destructor
CLogger::~CLogger() {
  LOGGER_GETLOCKACCESS;
  if (*m_sLogBuffer != '\0')
    Log();
}

// Logging settings
void CLogger::SetOneLogEntryPerLine(bool bNewSetting) {
  LOGGER_GETLOCKACCESS;
  m_bOneLogEntryPerLine = bNewSetting;
  if (m_bOneLogEntryPerLine) {
    if (*m_sLogBuffer != '\0') {
      EndLogLine();
    }
  }
}
void CLogger::SetLogThreadID(bool bNewSetting) {
  LOGGER_GETLOCKACCESS;
  m_bLogThreadID = bNewSetting;
}
void CLogger::SetLogTimeStamp(bool bNewSetting) {
  LOGGER_GETLOCKACCESS;
  m_bLogTimeStamp = bNewSetting;
}
void CLogger::SetTimeStampAccuracy(int nNewSetting) {
  LOGGER_GETLOCKACCESS;
  m_nTimeStampAccuracy = nNewSetting;
  CreateTimeStampHeader();
}

// Retrieves a writable buffer to append log entries to
char* CLogger::Buffer() {
  LOGGER_GETLOCKACCESS;
  return m_sNextLogPos;
}

void CLogger::BufferDone() {
  LOGGER_GETLOCKACCESS;
  SyncBufferEndPoint();
}

// Gets how many bytes are still free in the buffer
DWORD CLogger::FreeBufferSize() {
  LOGGER_GETLOCKACCESS;
  return LOGGER_LINEBUFFERSIZE - m_nBufferUsed;
}

// Logs a time stamp
void CLogger::LogTimeStamp() {
  LOGGER_GETLOCKACCESS;
  double dNow = m_oTimer.GetInterval();
  char sTimeStamp[128];
  sprintf(sTimeStamp, m_sTimeStampFormat, dNow);
  Log(sTimeStamp);
}

// Logs the current thread's ID
void CLogger::LogThreadID() {
  DWORD nThreadID = GetCurrentThreadId();
  char sThreadID[30];
  ltoa(nThreadID, sThreadID, 16);
  Log(sThreadID);
}

// Logs the GetLastError
void CLogger::LogGLE() {
  // Log the error number
  DWORD nErrorCode = GetLastError();
  char sErrorCode[30];
  _ultoa(nErrorCode, sErrorCode, 10);
  std::string sErrorDescription = sErrorCode;
  sErrorDescription += " - ";

  // Get the GetLastError text
  LPSTR sError = NULL;
  if (0 < FormatMessage(
          FORMAT_MESSAGE_ALLOCATE_BUFFER |
            FORMAT_MESSAGE_FROM_SYSTEM |
            FORMAT_MESSAGE_IGNORE_INSERTS,
          NULL,
          nErrorCode,
          MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
          (LPTSTR)&sError,
          0,
          NULL)) {
    // Done -> use it
    sErrorDescription += sError;

    // And kill it
    LocalFree(sError);
  } else {
    // Couldn't find -> specify something
    sErrorDescription += "<unknown error>";
  }

  // And log the error
  Log(sErrorDescription);
}

// Logs the buffer's content, optionally also including the
// given log entry
void CLogger::Log(const char* sLogEntry) {
  LOGGER_GETLOCKACCESS;
  if (sLogEntry != NULL) {
    strncat(m_sNextLogPos, sLogEntry, LOGGER_LINEBUFFERSIZE - m_nBufferUsed);
    SyncBufferEndPoint();
  }
  if (m_bOneLogEntryPerLine) {
    EndLogLine();
  }
}
void CLogger::Log(const wchar_t* sLogEntry) {
  // Convert the wide string to narrow, if needed
  LOGGER_GETLOCKACCESS;
  if (sLogEntry != NULL) {
    WideCharToMultiByte(
      CP_ACP,
      0,
      sLogEntry,
      -1,
      m_sNextLogPos,
      LOGGER_LINEBUFFERSIZE - m_nBufferUsed,
      NULL,
      NULL
    );
    SyncBufferEndPoint();
  }
  if (m_bOneLogEntryPerLine) {
    EndLogLine();
  }
}
void CLogger::Log(const std::string& sLogEntry) {
  Log(sLogEntry.c_str());
}
void CLogger::Log(long nLogEntry, long nRadix) {
  LOGGER_GETLOCKACCESS;
  ltoa(nLogEntry, m_sNextLogPos, nRadix);
  SyncBufferEndPoint();
  if (m_bOneLogEntryPerLine) {
    EndLogLine();
  }
}
void CLogger::Log(unsigned long nLogEntry, long nRadix) {
  LOGGER_GETLOCKACCESS;
  _ultoa(nLogEntry, m_sNextLogPos, nRadix);
  SyncBufferEndPoint();
  if (m_bOneLogEntryPerLine) {
    EndLogLine();
  }
}
void CLogger::Log(bool bLogEntry) {
  Log(bLogEntry? "true": "false");
}
void CLogger::operator ()(const char* sLogEntry) {
  Log(sLogEntry);
}
void CLogger::operator ()(const wchar_t* sLogEntry) {
  Log(sLogEntry);
}
void CLogger::operator ()(const std::string& sLogEntry) {
  Log(sLogEntry);
}
void CLogger::operator ()(long nLogEntry) {
  Log(nLogEntry);
}
void CLogger::operator ()(unsigned long nLogEntry) {
  Log(nLogEntry);
}
void CLogger::operator ()(bool bLogEntry) {
  Log(bLogEntry);
}

// Logs the formatted argument list to the buffer
void CLogger::Logf(const char* sFormat, ...) {
  LOGGER_GETLOCKACCESS;
  va_list auArgs;
  va_start(auArgs, sFormat);
  vsnprintf(
    m_sNextLogPos,
    LOGGER_LINEBUFFERSIZE - m_nBufferUsed,
    sFormat,
    auArgs
  );
  va_end(auArgs);
  SyncBufferEndPoint();
  if (m_bOneLogEntryPerLine) {
    EndLogLine();
  }
}

// Ends the log's current line, adding time stamps and/or thread ID's
void CLogger::EndLogLine() {
  // Look if we need to log something
  LOGGER_GETLOCKACCESS;
  if (*m_sLogBuffer != '\0') {
    // Yes -> look if a logger is available
    if (m_poLogDest != NULL) {
      // Yes -> log any time stamp, if needed
      if (m_bLogTimeStamp) {
        double dNow = m_oTimer.GetInterval();
        char sTimeStamp[128];
        sprintf(sTimeStamp, m_sTimeStampFormat, dNow);
        m_poLogDest->Dump(sTimeStamp);
        m_poLogDest->Dump("\t");
      }

      // Log any thread ID, if needed
      if (m_bLogThreadID) {
        DWORD nThreadID = GetCurrentThreadId();
        char sThreadID[30];
        ltoa(nThreadID, sThreadID, 16);
        m_poLogDest->Dump(sThreadID);
        m_poLogDest->Dump("\t");
      }

      // And log the contents of the buffer on it's own line
      m_poLogDest->Dump(m_sLogBuffer);
      m_poLogDest->Dump("\n");
    }

    // And clear the buffer again
    *m_sLogBuffer = '\0';
    m_sNextLogPos = m_sLogBuffer;
    m_nBufferUsed = 0;
  }
}

// Makes sure the buffer end point is in sync
void CLogger::SyncBufferEndPoint() {
  // Look if new text has been added at all
  if (*m_sNextLogPos != '\0') {
    // Yes -> find it ourselves
    DWORD nNewNumChars = strlen(m_sNextLogPos);

    // And update our buffer status
    m_nBufferUsed += nNewNumChars;
    m_sNextLogPos += nNewNumChars;
  }
}

// Creates a new time stamp header
void CLogger::CreateTimeStampHeader() {
  sprintf(m_sTimeStampFormat, "%%0.%df", m_nTimeStampAccuracy);
}