This section of the archives stores flipcode's complete Developer Toolbox collection,
featuring a variety of mini-articles and source code contributions from our readers.

Multi-Threaded Logging Library
Submitted by

This is a high performance, multi-threaded logging library which I've made,
which some people may find useful. The main features are:

Multiple threads and *multiple processes* can access the same log file at
the same time. That means if you have a number of related programs, they
can all use the same log file without problems.

The logger automatically prepends useful information to each line before
it outputs it, things like a timestamp, process name and thread ID.

The logger doesn't let the log file get too big. Once it hits 256KB, it
makes a backup and starts with a fresh file. This is useful because if you
try and open a huge file in notepad, it can take days :) Also, it makes it
easy to find entries that happened weeks or months ago (since the backups
include the current date/time in the filename)

High performance. Actual writing to the file is done in a separate
thread, so you don't have to wait for the I/O to complete before logging
another line (it'll get added to a queue if you try and log a line and it's
already writing a line) or continuing on with your program.

If you like this library, the don't forget to head on over to my home page:http://www.codeka.com - it's a little empty now, but I'll be adding to it as
I go. I'll post any updates to this library there.

//-----------------------------------------------------------------------------
// This is the main queue that all the loggers add to and the worker thread
// reads from. I know, I know, it's a global variable - so kill me!
extern CQueue<CLoggerItem>g_LoggerQueue;

//-----------------------------------------------------------------------------
// this is the class that actually implements the functionality of CLogger.
// see loggerimpl.cpp for it's implementation.
class CLoggerImpl : public CLogger
{
protected:
// current format, as set by SetFormat()
DWORD m_dwFormat;

//-----------------------------------------------------------------------------
// The logger can print a number of things along with the actual line it's
// logging. These are what it can also print
#define LOG_TIMESTAMP 1 // the time the line was logged
#define LOG_PROCNAME 2 // the name of the process that logged the
// line. This can be set with
// CLogger::SetName()
#define LOG_THREADID 4 // the thread ID that logged this line
//-----------------------------------------------------------------------------
// Maximum length of a line
#define LOG_MAX_LINE 256

//-----------------------------------------------------------------------------
class LOGGER_API CLogger
{
public:
// Initialize the logger. This must be called before any calls to Attach()
// (and it must have completed before any calls to Attach as well.
staticvoid Initialize();

// Shutdown the logger. Any loggers that have been created will become
// invalid!
staticvoid Shutdown();

// Attach to the logger. This just creates a new CLogger class for you to
// use.
static CLogger *Attach( TCHAR *szLogFile = _T("errors.log") );

// set/get the name displayed by LOG_PROCNAME. If NULL is given, then we
// use the name of the executable this process is running from (eg
// myapp.exe)
virtualvoid SetName( TCHAR *szName ) = 0;
virtual TCHAR *GetName() = 0;

// this is the big one! This actually writes a line to the log file.
virtualvoid Print( TCHAR *szFormat, ... ) = 0;
};

//-----------------------------------------------------------------------------
// End of LOGGER.H
//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
void CLoggerImpl::Release()
{
// wait for the queue to empty. That's the only way we know that none of
// our data members are still being used.
g_LoggerQueue.WaitForEmpty();

//-----------------------------------------------------------------------------
// QUEUE.H
// By Dean Harding
//
// Copyright (c) 2000/2001 Dean Harding
//-----------------------------------------------------------------------------
// I needed a fast queue system, where it's very fast *adding* to the *end* of
// the queue, and *removing* from the beginning (i.e. FIFO). I don't know of
// any STL container that have those properties, so I made my own.
//
// It's just a doubly-linked list :)
//
// Also, this class is thread safe (a thread can add to the queue while another
// is getting stuff from the top) And finally, you can block on a GetNext()
// call until data is added. Note that this only works when there is one
// *and only one* thread using GetNext(). That's OK - that's exactly the
// functionality we want!
//-----------------------------------------------------------------------------
#pragma once

// We just lock the whole queue when we want to add/remove to/from it,
// because that's nice and easy, and besides, the things we do on the queue
//should be simple enough that it won't really matter.
HANDLE m_hQueueLock;

// When we add an item queue, this even it fired to tell anything waiting
// in GetNext().
HANDLE m_hAddEvent;

// This is used by WaitForEmpty(). When GetNext() removes the last item
// from the queue, this is signalled.
HANDLE m_hEmptyEvent;
public:
inline CQueue();
inline ~CQueue();

// Get the first item off the top of the queue. If the queue is empty
// and bBlock is false, NULL is returned. If the queue is empty and bBlock
// is true, then the function blocks until the queue is added to.
inline T* GetNext( bool bBlock = false );

// Add an item to the queue. If there's a blocking call to GetNext() then
// that call will wake up and return this item.
inlinevoid AddItem( T* pItem );

//-----------------------------------------------------------------------------
// WORKER.CPP
// By Dean Harding
//
// Copyright (c) 2000/2001 Dean Harding
//-----------------------------------------------------------------------------
// This is the thread that actually does all the work. It waits on the
// queue for more items, whenever a new item is added, it writes that item
// to a file!
//-----------------------------------------------------------------------------
#include "internal.h"

//-----------------------------------------------------------------------------
// While this lock is set, no threads from other processes will try and open
// the file. This is for things like when renaming a large file or such
HANDLE g_hFileLock = NULL;
const TCHAR g_szFileLock[] = _T("dhLoggerFileLock");

//-----------------------------------------------------------------------------
// renames an open file, and returns a handle to a new file of the same name.
HANDLE RenameFile( HANDLE hFile, TCHAR *szFileName );

//-----------------------------------------------------------------------------
HANDLE OpenFile( TCHAR *szFileName )
{
// open the file, keep trying until it's not INVALID_HANDLE_VALUE
// There has to be a better way than this. I mean, I want to be able
// to block the thread until the file is available again. I don't know
// if there's any API for that...
HANDLE hFile = INVALID_HANDLE_VALUE;
while( hFile == INVALID_HANDLE_VALUE )
{
// we only try to open the file if we own the hFileLock mutex.
ASSERT( WaitForSingleObject( g_hFileLock, INFINITE ) == WAIT_OBJECT_0 );