Skip navigation links
IT services and product development
Menu
TwoLogs
IT services and product development

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 module 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.  If you do, the logging code needs the functionality of our other source code module C_Lock.

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.

The logger can temporarilly be disabled by calling SetEnabled(false).  All logging actions will from then on be discarded.  To re-enable the logging again, simply call SetEnabled(true).

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

  myLogger.Log("something");
  myLogger.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 seperately (numbers, strings, etc), or by writing it all in one go using the Logf() method and a format mask (like using the printf function).

CIndent is a helper class that you can use to add indentation to your logs.  It will automatically expand to the right number of spaces.  Simply create an indenting object, pass it as the indentation to use to the CLogger constructor, and in- and decrease it's indentation with ++ and -- at the appropriate times.  CLogger will then let each line start with the indentation as it is defined when the line is closed.  You can also pass the indentation to the logging functions as an argument.

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.  You are free to use environment variables in the file's path, like e.g. %appdata%.

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 on seperate lines.

CLogDestDebugger will pass the log data on to the active debugger.  If there is no debugger attached to your application, nothing will happen.  When you use a global debug log viewer, you can specify a line prefix to use, to make it easier to filter out the lines emitted by your own application.

CLogDestConsole will create a console window 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.

Files

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

Logging.h

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

  Version: 15
  Author:  Carl Colijn, TwoLogs
  Contact: c.colijn@twologs.com
  Source:  https://www.twologs.com/sourcecode

  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

// 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
  LPCWSTR Value() const;
  operator LPCWSTR() const;

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

  // Single indentation
  std::wstring m_sSingleIndent;

  // Total indentation
  std::wstring 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 std::wstring& sLog) = 0;
};

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

  // Dumps the given string to the log destination
  virtual void Dump(const std::wstring& sLog);

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

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

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

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

  // Dumps the given string to the log destination
  virtual void Dump(const std::wstring& sLog);

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

// Log-to-debugger destination
class CLogDestDebugger: public CLogDest {
public:
  // Con- & destructor
  CLogDestDebugger(const wchar_t* sLinePrefix = L"");
  virtual ~CLogDestDebugger();

  // Dumps the given string to the log destination
  virtual void Dump(const std::wstring& sLog);

private:
  // The line prefix to use
  std::wstring m_sLinePrefix;

  // The current line buffer
  std::wstring m_sLineBuffer;
};

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

  // Dumps the given string to the log destination
  virtual void Dump(const std::wstring& sLog);

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

  // Whether to wait before termination
  bool m_bWaitBeforeTerm;

  // Whether to close the console on termination
  bool m_bFreeConsole;
};

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

  // Enables/disables the logging
  bool GetEnabled() const;
  void SetEnabled(bool bEnabled);

  // 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

  // Logs the given log entry
  void Log(const char* sLogEntry);
  void Log(const wchar_t* sLogEntry = nullptr);
  void Log(const std::string& sLogEntry);
  void Log(const std::wstring& 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 = nullptr);
  void operator ()(const wchar_t* sLogEntry = nullptr);
  void operator ()(const std::string& sLogEntry);
  void operator ()(const std::wstring& 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 wchar_t* 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 indentation
  CIndent* m_poIndent;

  // The log buffer
  std::wstring m_sLogBuffer;

  // Whether we're enabled
  bool m_bEnabled;

  // 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;
  wchar_t m_sTimeStampFormat[20];

  // The log timer
  CHiResTimer m_oTimer;

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

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

#endif // INCLUDE_TWOLOGS_COMMON_LOGGING_H

Logging.cpp

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

  Version: 15
  Author:  Carl Colijn, TwoLogs
  Contact: c.colijn@twologs.com
  Source:  https://www.twologs.com/sourcecode

  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.

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

#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, L' ');
}
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
LPCWSTR CIndent::Value() const {
  return m_sTotalIndent.c_str();
}
CIndent::operator LPCWSTR() const {
  return m_sTotalIndent.c_str();
}




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

  CLogDest and derivatives

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

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

// Constructor
CLogDestFile::CLogDestFile(const wchar_t* sFilePath, bool bFlushOnLog):
 m_bFlushOnLog(bFlushOnLog) {
  wchar_t sExpandedFilePath[MAX_PATH];
  ExpandEnvironmentStrings(sFilePath, sExpandedFilePath, MAX_PATH);
  m_hFile = _wfopen(sExpandedFilePath, L"w");
  m_bFileOK = m_hFile != NULL;
  if (!m_bFileOK) {
    std::wstring sPrompt = L"Error opening log file:\r\n  ";
    sPrompt += sFilePath;
    MessageBox(NULL, sPrompt.c_str(), L"Error opening log file", MB_OK | MB_ICONERROR);
  } else if (m_bFlushOnLog) {
    fclose(m_hFile);
    m_sFilePath = sExpandedFilePath;
  }
}

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

// Dumps the given string to the log destination
void CLogDestFile::Dump(const std::wstring& sLog) {
  if (m_bFileOK) {
    if (m_bFlushOnLog) {
      m_hFile = _wfopen(m_sFilePath.c_str(), L"a+");
    }
    fwprintf(m_hFile, sLog.c_str());
    if (m_bFlushOnLog) {
      fclose(m_hFile);
    }
  }
}

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

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

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

// Constructor
CLogDestDebugger::CLogDestDebugger(const wchar_t* sLinePrefix):
 m_sLinePrefix(sLinePrefix == nullptr? L"": sLinePrefix),
 m_sLineBuffer(m_sLinePrefix) {
}

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

// Dumps the given string to the log destination
void CLogDestDebugger::Dump(const std::wstring& sLog) {
  // Add the new content
  m_sLineBuffer += sLog;

  // And emit all lines present already as separate lines
  size_t nNextLinePos;
  while ((nNextLinePos = m_sLineBuffer.find_first_of(L"\r\n")) != m_sLineBuffer.npos) {
    bool bWasN = m_sLineBuffer[nNextLinePos] == L'\n';
    m_sLineBuffer[nNextLinePos] = L'\0';
    OutputDebugString(m_sLineBuffer.c_str());
    if (
      !bWasN &&
      nNextLinePos < m_sLineBuffer.size() - 1 &&
      m_sLineBuffer[nNextLinePos + 1] == L'\n'
    ) {
      ++nNextLinePos;
    }
    m_sLineBuffer.erase(0, nNextLinePos + 1);
    m_sLineBuffer.insert(0, m_sLinePrefix);
  }
}

// Constructor
CLogDestConsole::CLogDestConsole(DWORD nLinesToBuffer, bool bWaitBeforeTerm, bool bMaximize, bool bFreeConsole, bool bPreserveForegroundWindow):
 m_bWaitBeforeTerm(bWaitBeforeTerm),
 m_bFreeConsole(bFreeConsole) {
  // Look who's the foreground window now, if needed
  HWND hForegroundWnd;
  if (bPreserveForegroundWindow) {
    hForegroundWnd = GetForegroundWindow();
  }

  // Ensure we have a console
  AllocConsole();

  // Use it
  m_hConsoleIn = GetStdHandle(STD_INPUT_HANDLE);
  m_hConsoleOut = GetStdHandle(STD_OUTPUT_HANDLE);

  // Config the console
  COORD oBufferSize;
  oBufferSize.X = 80;
  oBufferSize.Y = nLinesToBuffer;
  SetConsoleScreenBufferSize(m_hConsoleOut, oBufferSize);
  SetConsoleTitle(L"LOG WINDOW - Press a character to insert it's mark line");

  // Make sure it shows
  HWND hConsoleWnd = GetConsoleWindow();
  ShowWindow(hConsoleWnd, bMaximize? SW_SHOWMAXIMIZED: SW_SHOWNORMAL);

  // But do not make us the new foreground window, if needed
  if (bPreserveForegroundWindow) {
    SetForegroundWindow(hForegroundWnd);
  }
}

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

// Dumps the given string to the log destination
void CLogDestConsole::Dump(const std::wstring& 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 && aoEvents[nEventNr].Event.KeyEvent.uChar.AsciiChar != '\t') {
          // Yes -> do so
          std::wstring sTagLine = L"\n";
          wchar_t sTagLineChar = aoEvents[nEventNr].Event.KeyEvent.uChar.AsciiChar;
          for (int nPartNr = 0; nPartNr < 16; ++nPartNr) {
            sTagLine += L"= ";
            sTagLine += sTagLineChar;
            sTagLine += L" =";
          }
          sTagLine += L"\n";
          DWORD nWritten;
          WriteConsole(
            m_hConsoleOut,
            sTagLine.c_str(),
            sTagLine.size(),
            &nWritten,
            nullptr
          );
        }
      }
    }
    delete[] aoEvents;
  }

  // And write out the new stuff
  DWORD nWritten;
  WriteConsole(m_hConsoleOut, sLog.c_str(), sLog.size(), &nWritten, nullptr);
}




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

  CLogger

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

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

// Constructor
CLogger::CLogger(
  CLogDest* poLogDest,
  CIndent* poIndent,
  bool bOneLogEntryPerLine,
  bool bLogThreadID,
  bool bLogTimeStamp,
  int nTimeStampAccuracy
):
 m_poLogDest(poLogDest),
 m_poIndent(poIndent),
 m_bEnabled(true),
 m_bOneLogEntryPerLine(bOneLogEntryPerLine),
 m_bLogThreadID(bLogThreadID),
 m_bLogTimeStamp(bLogTimeStamp),
 m_nTimeStampAccuracy(nTimeStampAccuracy) {
  // Create the time stamp header
  CreateTimeStampHeader();

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

// Destructor
CLogger::~CLogger() {
  LOGGER_GETLOCKACCESS;
  if (m_sLogBuffer.size() > 0) {
    Log();
  }
}

// Enables/disables the logging
bool CLogger::GetEnabled() const {
  return m_bEnabled;
}
void CLogger::SetEnabled(bool bEnabled) {
  if (m_bEnabled != bEnabled) {
    m_bEnabled = true;
    if (bEnabled) {
      Log(L"Logging enabled.");
    } else {
      Log(L"Logging disabled.");
    }
    if (!m_bOneLogEntryPerLine) {
      EndLogLine();
    }
    m_bEnabled = bEnabled;
  }
}

// Logging settings
void CLogger::SetOneLogEntryPerLine(bool bNewSetting) {
  LOGGER_GETLOCKACCESS;
  m_bOneLogEntryPerLine = bNewSetting;
  if (m_bOneLogEntryPerLine) {
    if (m_sLogBuffer.size() > 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();
}

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

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

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

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

    // And kill it
    LocalFree(sError);
  } else {
    // Couldn't find -> specify something
    sErrorDescription += L"<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) {
  int nNumBytes = strlen(sLogEntry);
  size_t nMaxNumCharsNeeded = nNumBytes * 3 + 1;
  std::wstring sConverted(nMaxNumCharsNeeded, L'\0');
  int nNumCharsWritten = MultiByteToWideChar(
    CP_THREAD_ACP,
    MB_ERR_INVALID_CHARS,
    sLogEntry,
    nNumBytes,
    const_cast<wchar_t*>(sConverted.c_str()),
    nMaxNumCharsNeeded
  );
  sConverted.resize(nNumCharsWritten);
  Log(sConverted);
}
void CLogger::Log(const wchar_t* sLogEntry) {
  LOGGER_GETLOCKACCESS;
  if (m_bEnabled) {
    if (sLogEntry != nullptr) {
      m_sLogBuffer += sLogEntry;
    }
    if (m_bOneLogEntryPerLine) {
      EndLogLine();
    }
  }
}
void CLogger::Log(const std::string& sLogEntry) {
  Log(sLogEntry.c_str());
}
void CLogger::Log(const std::wstring& sLogEntry) {
  LOGGER_GETLOCKACCESS;
  if (m_bEnabled) {
    m_sLogBuffer += sLogEntry;
    if (m_bOneLogEntryPerLine) {
      EndLogLine();
    }
  }
}
void CLogger::Log(long nLogEntry, long nRadix) {
  std::wstring sLogEntry(60, L'\0');
  _ltow(nLogEntry, const_cast<wchar_t*>(sLogEntry.c_str()), nRadix);
  Log(sLogEntry);
}
void CLogger::Log(unsigned long nLogEntry, long nRadix) {
  std::wstring sLogEntry(60, L'\0');
  _ultow(nLogEntry, const_cast<wchar_t*>(sLogEntry.c_str()), nRadix);
  Log(sLogEntry);
}
void CLogger::Log(bool bLogEntry) {
  Log(bLogEntry? L"true": L"false");
}
void CLogger::operator ()(const wchar_t* sLogEntry) {
  Log(sLogEntry);
}
void CLogger::operator ()(const char* sLogEntry) {
  Log(sLogEntry);
}
void CLogger::operator ()(const std::string& sLogEntry) {
  Log(sLogEntry);
}
void CLogger::operator ()(const std::wstring& 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 wchar_t* sFormat, ...) {
  va_list auArgs;
  va_start(auArgs, sFormat);
  long nBufferSize = 1023;
  std::wstring sBuffer(nBufferSize, L'\0');
  long nUsedSize = -1;
  do {
    nUsedSize = _vsnwprintf(
      const_cast<wchar_t*>(sBuffer.c_str()),
      nBufferSize,
      sFormat,
      auArgs
    );
    if (nUsedSize == -1) {
      nBufferSize *= 2;
      sBuffer.resize(nBufferSize);
    } else {
      sBuffer.resize(nUsedSize);
    }
  } while (nUsedSize == -1);
  va_end(auArgs);
  Log(sBuffer);
}

// 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_bEnabled && m_sLogBuffer.size() > 0) {
    // Yes -> look if a logger is available
    if (m_poLogDest != nullptr) {
      // Yes -> log any time stamp, if needed
      std::wstring sLogLine;
      if (m_bLogTimeStamp) {
        double dNow = m_oTimer.GetInterval();
        wchar_t sTimeStamp[128];
        swprintf(sTimeStamp, m_sTimeStampFormat, dNow);
        sLogLine += sTimeStamp;
        sLogLine += L"\t";
      }

      // Log any thread ID, if needed
      if (m_bLogThreadID) {
        DWORD nThreadID = GetCurrentThreadId();
        wchar_t sThreadID[30];
        _ltow(nThreadID, sThreadID, 16);
        sLogLine += sThreadID;
        sLogLine += L"\t";
      }

      // Log any indentation
      if (m_poIndent != nullptr) {
        sLogLine += m_poIndent->Value();
      }

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

    // And clear the buffer again
    m_sLogBuffer.clear();
  }
}

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