|
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.
Dean Harding.
|
Currently browsing [mlogger.zip] (76,329 bytes) - [logger/logger/internal.h] - (2,074 bytes)
//-----------------------------------------------------------------------------
// INTERNAL.H
// By Dean Harding
//
// Copyright (c) 2000/2001 Dean Harding
//-----------------------------------------------------------------------------
#pragma once
//-----------------------------------------------------------------------------
#include <windows.h>
#include <tchar.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#ifdef _DEBUG
#define ASSERT(x) assert(x)
#else
#define ASSERT(x) (x)
#endif
//-----------------------------------------------------------------------------
#include "logger.h"
#include "item.h"
#include "queue.h"
//-----------------------------------------------------------------------------
// worker thread that does all the actual logging work. See worker.cpp for
// it's implementation.
DWORD WINAPI LoggerWorkerThread( void *pData );
//-----------------------------------------------------------------------------
// 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;
// current process name, as set by SetName()
TCHAR *m_szProcName;
// current log file name, as set by SetLogFile()
TCHAR *m_szLogFile;
public:
CLoggerImpl( TCHAR *szLogFile );
~CLoggerImpl();
void Release();
void SetFormat( DWORD dwFormat );
DWORD GetFormat();
void SetName( TCHAR *szName );
TCHAR *GetName();
void Print( TCHAR *szFormat, ... );
};
//-----------------------------------------------------------------------------
// End of INTERNAL.H
//-----------------------------------------------------------------------------
|
|
Currently browsing [mlogger.zip] (76,329 bytes) - [logger/logger/item.h] - (1,210 bytes)
//-----------------------------------------------------------------------------
// ITEM.H
// By Dean Harding
//
// Copyright (c) 2000/2001 Dean Harding
//-----------------------------------------------------------------------------
// This represents one item to be written to the log file.
//-----------------------------------------------------------------------------
#pragma once
//-----------------------------------------------------------------------------
// Note, anything not specified by dwFormat is ignored.
class CLoggerItem
{
public:
TCHAR *szLine;
TCHAR *szLogFile;
TCHAR *szProcName;
DWORD dwThreadId;
FILETIME ftTimestamp;
DWORD dwFormat;
CLoggerItem( DWORD dwFormat, TCHAR *szLine, TCHAR *szLogFile,
TCHAR *szProcName, DWORD dwThreadId, FILETIME ftTimestamp )
: dwFormat( dwFormat ), szLine( szLine ), szLogFile( szLogFile )
, szProcName( szProcName ), dwThreadId( dwThreadId )
, ftTimestamp( ftTimestamp )
{}
~CLoggerItem()
{
delete[] szLine;
}
};
//-----------------------------------------------------------------------------
// End of ITEM.H
//-----------------------------------------------------------------------------
|
|
Currently browsing [mlogger.zip] (76,329 bytes) - [logger/logger/logger.cpp] - (1,458 bytes)
//-----------------------------------------------------------------------------
// LOGGER.CPP
// By Dean Harding
//
// Copyright (c) 2000/2001 Dean Harding
//-----------------------------------------------------------------------------
#include "internal.h"
HANDLE g_hThread = NULL;
//-----------------------------------------------------------------------------
void CLogger::Initialize()
{
if( g_hThread != NULL )
{
// initialize has already been called.
return;
}
DWORD dwThreadId;
// create the thread that will actually do the logging
g_hThread = CreateThread( NULL, 0, LoggerWorkerThread, NULL, 0, &dwThreadId );
}
//-----------------------------------------------------------------------------
void CLogger::Shutdown()
{
if( g_hThread == NULL )
{
// Initialize hasn't been called, or Shutdown has already been called.
return;
}
// add a NULL item to the queue. That the thread's queue to quit.
g_LoggerQueue.AddItem( NULL );
WaitForSingleObject( g_hThread, INFINITE );
CloseHandle( g_hThread );
g_hThread = NULL;
}
//-----------------------------------------------------------------------------
CLogger *CLogger::Attach( TCHAR *szLogFile )
{
return new CLoggerImpl( szLogFile );
}
//-----------------------------------------------------------------------------
// End of LOGGER.CPP
//-----------------------------------------------------------------------------
|
|
Currently browsing [mlogger.zip] (76,329 bytes) - [logger/logger/logger.h] - (2,481 bytes)
//-----------------------------------------------------------------------------
// LOGGER.H
// By Dean Harding
//
// Copyright (c) 2000/2001 Dean Harding
//-----------------------------------------------------------------------------
#pragma once
#include <tchar.h>
//-----------------------------------------------------------------------------
#ifdef LOGGER_EXPORTS
#define LOGGER_API __declspec(dllexport)
#else
#define LOGGER_API __declspec(dllimport)
#endif
//-----------------------------------------------------------------------------
// 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.
static void Initialize();
// Shutdown the logger. Any loggers that have been created will become
// invalid!
static void Shutdown();
// Attach to the logger. This just creates a new CLogger class for you to
// use.
static CLogger *Attach( TCHAR *szLogFile = _T("errors.log") );
// When you're finished logging, call this to destroy the CLogger class.
virtual void Release() = 0;
// Sets/Gets the current things that are printed along with actual line.
virtual void SetFormat( DWORD dwFormat ) = 0;
virtual DWORD GetFormat() = 0;
// 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)
virtual void SetName( TCHAR *szName ) = 0;
virtual TCHAR *GetName() = 0;
// this is the big one! This actually writes a line to the log file.
virtual void Print( TCHAR *szFormat, ... ) = 0;
};
//-----------------------------------------------------------------------------
// End of LOGGER.H
//-----------------------------------------------------------------------------
|
|
Currently browsing [mlogger.zip] (76,329 bytes) - [logger/logger/loggerimpl.cpp] - (2,944 bytes)
//-----------------------------------------------------------------------------
// LOGGERIMPL.CPP
// By Dean Harding
//
// Copyright (c) 2000/2001 Dean Harding
//-----------------------------------------------------------------------------
#include "internal.h"
//-----------------------------------------------------------------------------
CLoggerImpl::CLoggerImpl( TCHAR *szLogFile )
: m_szLogFile( NULL ), m_szProcName( NULL ), m_dwFormat( 0 )
{
m_szLogFile = new TCHAR[_tcslen( szLogFile ) + 1];
_tcscpy( m_szLogFile, szLogFile );
SetName( NULL );
}
//-----------------------------------------------------------------------------
CLoggerImpl::~CLoggerImpl()
{
delete[] m_szProcName;
delete[] m_szLogFile;
}
//-----------------------------------------------------------------------------
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();
delete this;
}
//-----------------------------------------------------------------------------
void CLoggerImpl::SetFormat( DWORD dwFormat )
{
m_dwFormat = dwFormat;
}
//-----------------------------------------------------------------------------
DWORD CLoggerImpl::GetFormat()
{
return m_dwFormat;
}
//-----------------------------------------------------------------------------
void CLoggerImpl::SetName( TCHAR *szName )
{
if( m_szProcName != NULL )
{
delete[] m_szProcName;
}
if( szName != NULL )
{
m_szProcName = new TCHAR[_tcslen(szName) + 1];
_tcscpy( m_szProcName, szName );
}
else
{
TCHAR szBuffer[MAX_PATH];
GetModuleFileName( NULL, szBuffer, MAX_PATH );
TCHAR *p = (TCHAR *)(szBuffer + _tcslen(szBuffer));
while( *(p - 1) != '\\' && p != szBuffer ) p--;
m_szProcName = new char[_tcslen(p) + 1];
_tcscpy( m_szProcName, p );
}
}
//-----------------------------------------------------------------------------
TCHAR *CLoggerImpl::GetName()
{
return m_szProcName;
}
//-----------------------------------------------------------------------------
void CLoggerImpl::Print( TCHAR *szFormat, ... )
{
FILETIME ftTimestamp = { 0 };
DWORD dwThreadId = 0;
TCHAR *szBuffer = new TCHAR[LOG_MAX_LINE];
va_list args;
va_start( args, szFormat );
_vstprintf( szBuffer, szFormat, args );
va_end( args );
if( (m_dwFormat & LOG_TIMESTAMP) != 0 )
{
GetSystemTimeAsFileTime( &ftTimestamp );
}
if( (m_dwFormat & LOG_THREADID) != 0 )
{
dwThreadId = GetCurrentThreadId();
}
CLoggerItem *pItem = new CLoggerItem( m_dwFormat, szBuffer, m_szLogFile, m_szProcName, dwThreadId, ftTimestamp );
g_LoggerQueue.AddItem( pItem );
}
//-----------------------------------------------------------------------------
// End of LOGGERIMPL.CPP
//-----------------------------------------------------------------------------
|
|
Currently browsing [mlogger.zip] (76,329 bytes) - [logger/logger/queue.h] - (2,613 bytes)
//-----------------------------------------------------------------------------
// 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
//-----------------------------------------------------------------------------
template< class T >
class CQueue
{
protected:
struct SQueueData
{
T *pData;
SQueueData *pNext;
SQueueData *pPrev;
};
SQueueData *m_pTop;
SQueueData *m_pBottom;
// 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.
inline void AddItem( T* pItem );
// returns true if the queue is empty.
inline bool IsEmpty();
// blocks until the queue is empty
inline void WaitForEmpty();
};
//-----------------------------------------------------------------------------
#include "queue.inl"
//-----------------------------------------------------------------------------
// End of QUEUE.H
//-----------------------------------------------------------------------------
|
|
Currently browsing [mlogger.zip] (76,329 bytes) - [logger/logger/worker.cpp] - (5,959 bytes)
//-----------------------------------------------------------------------------
// 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"
//-----------------------------------------------------------------------------
CQueue<CLoggerItem>g_LoggerQueue;
//-----------------------------------------------------------------------------
// 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 );
//-----------------------------------------------------------------------------
// opens a file. If access is denied, keep trying
HANDLE OpenFile( TCHAR *szFileName );
//-----------------------------------------------------------------------------
DWORD WINAPI LoggerWorkerThread( void *pData )
{
TCHAR szBuffer[1024];
DWORD dwNumWritten;
g_hFileLock = CreateMutex( NULL, FALSE, g_szFileLock );
while( true )
{
CLoggerItem *pItem = g_LoggerQueue.GetNext( true );
// if it's a "NULL" item, then that's our queue to quit!
if( pItem == NULL )
break;
HANDLE hFile = OpenFile( pItem->szLogFile );
DWORD dwFileSize = GetFileSize( hFile, NULL );
if( dwFileSize > (256 * 1024) )
{
// if the file is bigger than 256KB, then rename it and make a new
// one
hFile = RenameFile( hFile, pItem->szLogFile );
}
// seek to the end of the file
SetFilePointer( hFile, 0, 0, FILE_END );
// if they want a timestamp, format that for them
if( (pItem->dwFormat & LOG_TIMESTAMP) != 0 )
{
FILETIME ftLocalTime;
SYSTEMTIME stLocalTime;
FileTimeToLocalFileTime( &pItem->ftTimestamp, &ftLocalTime );
FileTimeToSystemTime( &ftLocalTime, &stLocalTime );
_stprintf( szBuffer, _T("%2d/%02d/%04d %2d:%02d:%02d.%03d "),
stLocalTime.wDay, stLocalTime.wMonth, stLocalTime.wYear,
stLocalTime.wHour, stLocalTime.wMinute, stLocalTime.wSecond,
stLocalTime.wMilliseconds );
WriteFile( hFile, szBuffer, _tcslen( szBuffer ), &dwNumWritten, NULL );
}
// if they want a process name & thread id
if( (pItem->dwFormat & LOG_PROCNAME) != 0 && (pItem->dwFormat & LOG_THREADID) != 0 )
{
_stprintf( szBuffer, _T("(%s:%d) "), pItem->szProcName, pItem->dwThreadId );
WriteFile( hFile, szBuffer, _tcslen( szBuffer ), &dwNumWritten, NULL );
}
// if it's just the process name
else if( (pItem->dwFormat & LOG_PROCNAME) != 0 )
{
_stprintf( szBuffer, _T("(%s)"), pItem->szProcName );
WriteFile( hFile, szBuffer, _tcslen( szBuffer ), &dwNumWritten, NULL );
}
// if it's just the thread id
else if( (pItem->dwFormat & LOG_THREADID) != 0 )
{
_stprintf( szBuffer, _T("(%d)"), pItem->dwThreadId );
WriteFile( hFile, szBuffer, _tcslen( szBuffer ), &dwNumWritten, NULL );
}
// now write the actual line
WriteFile( hFile, pItem->szLine, _tcslen( pItem->szLine ), &dwNumWritten, NULL );
// and an end-of-line
WriteFile( hFile, _T("\r\n"), _tcslen( _T("\r\n") ), &dwNumWritten, NULL );
// and close the file!
CloseHandle( hFile );
delete pItem;
}
CloseHandle( g_hFileLock );
return 0;
}
//-----------------------------------------------------------------------------
HANDLE RenameFile( HANDLE hFile, TCHAR *szFileName )
{
ASSERT( WaitForSingleObject( g_hFileLock, INFINITE ) == WAIT_OBJECT_0 );
// now that we own the file lock, we can close the file, rename and make a
// new one!!
CloseHandle( hFile );
TCHAR szBuffer[MAX_PATH];
TCHAR szNewFile[MAX_PATH];
FILETIME ftCurrent, ftLocal;
SYSTEMTIME stLocal;
TCHAR *p = (TCHAR *)(szFileName + _tcslen(szFileName) );
while( *(p - 1) != '\\' && p != szFileName ) p--;
GetSystemTimeAsFileTime( &ftCurrent );
FileTimeToLocalFileTime( &ftCurrent, &ftLocal );
FileTimeToSystemTime( &ftLocal, &stLocal );
_stprintf( szBuffer, _T("%04d-%02d-%2d %02d-%02d-%02d %s"),
stLocal.wYear, stLocal.wMonth, stLocal.wDay,
stLocal.wHour, stLocal.wMinute, stLocal.wSecond,
p );
_tcsncpy( szNewFile, szFileName, (p - szFileName) );
p = szNewFile + (p - szFileName);
*p = '\0';
_tcscat( szNewFile, szBuffer );
MoveFile( szFileName, szNewFile );
ReleaseMutex( g_hFileLock );
return OpenFile( 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 );
hFile = CreateFile(
szFileName,
GENERIC_WRITE,
FILE_SHARE_READ,
NULL,
OPEN_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL );
Sleep( 0 );
ReleaseMutex( g_hFileLock );
}
return hFile;
}
//-----------------------------------------------------------------------------
// End of WORKER.CPP
//-----------------------------------------------------------------------------
|
|
Currently browsing [mlogger.zip] (76,329 bytes) - [logger/logger_test/logger_test.cpp] - (1,762 bytes)
//-----------------------------------------------------------------------------
// LOGGER_TEST.CPP
// By Dean Harding
//
// Copyright (c) 2000/2001 Dean Harding
//-----------------------------------------------------------------------------
// This is just a little console application which creates a number of threads
// and writes stuff to the log file. It's just to test logger.dll
//-----------------------------------------------------------------------------
#include <windows.h>
#include <stdio.h>
#include <conio.h>
#include <iostream.h>
#include "..\logger\logger.h"
//-----------------------------------------------------------------------------
int main( int argc, char **argv )
{
cout << "Hello World!!" << endl;
CLogger::Initialize();
CLogger *logger = CLogger::Attach( _T("log\\errors.log") );
if( logger == NULL )
{
cout << "Could not attack to logger." << endl;
return 1;
}
logger->SetFormat( LOG_PROCNAME | LOG_TIMESTAMP | LOG_THREADID );
DWORD dwProcessId = GetCurrentProcessId();
DWORD dwRun = 1;
while( !kbhit() )
{
cout << "Process: " << dwProcessId << ", Run: " << dwRun << endl;
logger->Print( "Process: %d, Run: %d", dwProcessId, dwRun );
dwRun ++;
// make it so the log file doesn't get too big, too fast...
// if you remove this, then expect a large lot of log files :)
//Sleep( 100 );
}
cout << "Flushing log file..." << endl;
logger->Release();
logger = NULL;
cout << "Finishing logging..." << endl;
CLogger::Shutdown();
getch();
return 0;
}
//-----------------------------------------------------------------------------
// End of LOGGER_TEST.CPP
//-----------------------------------------------------------------------------
|
|
The zip file viewer built into the Developer Toolbox made use
of the zlib library, as well as the zlibdll source additions.
|