├── test ├── CMakeLists.txt ├── test.vcproj.lnt └── test.cpp ├── doc ├── html │ └── download.jpg └── make-doxygen.bat ├── src ├── socket.cpp ├── platform.cpp ├── checkpoint.cpp ├── statics.cpp ├── api.cpp ├── timer.cpp ├── mutex.cpp ├── message.cpp ├── unittest.cpp ├── lobject.cpp ├── node.cpp ├── formatter.cpp ├── target.cpp ├── topic.cpp └── lstring.cpp ├── .travis.yml ├── .gitattributes ├── include ├── logog.hpp ├── checkpoint.hpp ├── timer.hpp ├── message.hpp ├── api.hpp ├── statics.hpp ├── mutex.hpp ├── const.hpp ├── formatter.hpp ├── socket.hpp ├── thread.hpp ├── string.hpp ├── unittest.hpp ├── node.hpp ├── platform.hpp ├── target.hpp ├── object.hpp ├── topic.hpp └── macro.hpp ├── readme.markdown ├── CMakeLists.txt └── .gitignore /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable( test test.cpp ) 2 | 3 | -------------------------------------------------------------------------------- /doc/html/download.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwbyrd/logog/HEAD/doc/html/download.jpg -------------------------------------------------------------------------------- /src/socket.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * \file socket.cpp 3 | */ 4 | 5 | #include "logog.hpp" 6 | 7 | namespace logog { 8 | bool sb_AvoidLinkError4221_socket_cpp = false; 9 | } 10 | 11 | -------------------------------------------------------------------------------- /src/platform.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * \file platform.cpp 3 | */ 4 | 5 | #include "logog.hpp" 6 | 7 | namespace logog { 8 | 9 | bool sb_AvoidLinkError4221_platform_cpp = false; 10 | } 11 | 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: cpp 2 | compiler: 3 | - gcc 4 | - clang 5 | script: 6 | mkdir build && 7 | mkdir build/Debug && 8 | mkdir build/Release && 9 | cd build/Debug && 10 | cmake -DCMAKE_BUILD_TYPE=Debug ../.. && 11 | make && 12 | ctest -VV -C Debug && 13 | cd ../Release && 14 | cmake -DCMAKE_BUILD_TYPE=Release ../.. && 15 | make && 16 | ctest -VV -C Release 17 | 18 | 19 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /test/test.vcproj.lnt: -------------------------------------------------------------------------------- 1 | /* -dConfiguration= ... none provided */ 2 | -D_UNICODE // 27: CharacterSet = "1" 3 | -DUNICODE // 27: CharacterSet = "" 4 | -i"..\include" // 47: AdditionalIncludeDirectories = "..\include" 5 | -DWIN32 // 48: PreprocessorDefinitions = "WIN32;_DEBUG;_CONSOLE" 6 | -D_DEBUG // 48: PreprocessorDefinitions = "" 7 | -D_CONSOLE // 48: PreprocessorDefinitions = "" 8 | -D_MT // 51: RuntimeLibrary = "3" 9 | -D_DEBUG // 51: RuntimeLibrary = "" 10 | -D_DLL // 51: RuntimeLibrary = "" 11 | .\test.cpp // 180: RelativePath = ".\test.cpp" 12 | -------------------------------------------------------------------------------- /src/checkpoint.cpp: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * \file checkpoint.cpp 4 | */ 5 | 6 | #include "logog.hpp" 7 | 8 | namespace logog { 9 | 10 | Checkpoint::Checkpoint( const LOGOG_LEVEL_TYPE level, 11 | const LOGOG_CHAR *sFileName , 12 | const int nLineNumber, 13 | const LOGOG_CHAR *sGroup, 14 | const LOGOG_CHAR *sCategory, 15 | const LOGOG_CHAR *sMessage, 16 | const double dTimestamp ) : 17 | TopicSource( level, sFileName, nLineNumber, sGroup, sCategory, sMessage, dTimestamp ) 18 | { 19 | } 20 | 21 | int Checkpoint::Send( const Topic &node ) 22 | { 23 | /* Optionally update our own timestamp before we send on our information */ 24 | if (( m_TopicFlags & TOPIC_TIMESTAMP_FLAG ) != 0 ) 25 | m_tTime = GetGlobalTimer().Get(); 26 | 27 | return TopicSource::Send( node ); 28 | } 29 | } 30 | 31 | -------------------------------------------------------------------------------- /include/logog.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file logog.hpp Main include file for logog logging functionality. Include this file to enable logging for your application. 3 | */ 4 | 5 | #ifndef __LOGOG_HPP__ 6 | #define __LOGOG_HPP__ 7 | 8 | /** \def LOGOG_UNICODE 9 | ** Define this macro to enable Unicode support in logog. The logog library works either in Unicode mode or not -- 10 | ** attempting to mix the two log types will have unexpected results. 11 | **/ 12 | // #define LOGOG_UNICODE 1 13 | 14 | #include "const.hpp" 15 | #include "platform.hpp" 16 | #include "statics.hpp" 17 | #include "object.hpp" 18 | #include "timer.hpp" 19 | #include "mutex.hpp" 20 | #include "string.hpp" 21 | #include "node.hpp" 22 | #include "topic.hpp" 23 | #include "formatter.hpp" 24 | #include "target.hpp" 25 | // #include "socket.hpp" 26 | #include "checkpoint.hpp" 27 | #include "api.hpp" 28 | #include "message.hpp" 29 | #include "macro.hpp" 30 | #include "thread.hpp" 31 | #include "unittest.hpp" 32 | 33 | #endif // __LOGOG_HPP_ 34 | -------------------------------------------------------------------------------- /doc/make-doxygen.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | setlocal 3 | 4 | rem Change the current directory to the directory where the batch is located 5 | pushd %~dp0 6 | 7 | rem Doxygen starts one directory up from here 8 | cd .. 9 | 10 | rem Try to find Doxygen 11 | SET "DOXYGEN_EXE=MISSING" 12 | 13 | rem Tests to see if doxygen is in the path 14 | rem Thanks to Raymon Chen for this one http://blogs.msdn.com/b/oldnewthing/archive/2005/01/20/357225.aspx 15 | rem 16 | rem Tries to find Doxygen in the PATH variable 17 | for %%i in (doxygen.exe) do @if NOT "%%~$PATH:i"=="" SET "DOXYGEN_EXE=%%~$PATH:i" 18 | 19 | IF "%DOXYGEN_EXE%"=="MISSING" ( 20 | rem This is to be backward compatible with the original build script 21 | IF EXIST bin\win32\dev\doxygen.exe ( 22 | SET "DOXYGEN_EXE=bin\win32\dev\doxygen.exe" 23 | ) ELSE ( 24 | echo Documentation not generated (could not find doxygen.exe in your path) 25 | goto cleanup 26 | ) 27 | ) 28 | 29 | rem Run doxygen 30 | "%DOXYGEN_EXE%" 31 | 32 | :cleanup 33 | 34 | popd 35 | endlocal 36 | -------------------------------------------------------------------------------- /readme.markdown: -------------------------------------------------------------------------------- 1 | logog 2 | ----- 3 | 4 | logog is a highly portable C++ library to facilitate logging of real-time events in performance-oriented applications, such as games. It is especially appropriate for projects that have constrained memory and constrained CPU requirements. 5 | 6 | See http://johnwbyrd.github.com/logog/ for more information and complete documentation. 7 | 8 | General support and discussion is available at http://groups.google.com/group/logog . Development support and discussion is available at http://groups.google.com/group/logog-devel . 9 | 10 | This project can be built using CMake, available from http://www.cmake.org . Local documentation can be generated with doxygen, available from http://www.doxygen.org . 11 | 12 | The project is built regularly using Travis. The current status of the build is [![Build Status](https://travis-ci.org/johnwbyrd/logog.svg?branch=master)](https://travis-ci.org/johnwbyrd/logog). 13 | 14 | The license agreement for logog is viewable at http://johnwbyrd.github.com/logog/license.html . 15 | -------------------------------------------------------------------------------- /src/statics.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * \file statics.cpp 3 | */ 4 | 5 | #include "logog.hpp" 6 | 7 | namespace logog { 8 | 9 | Statics::Statics() 10 | { 11 | s_pAllNodes = NULL; 12 | s_pAllSubscriberNodes = NULL; 13 | s_pAllFilterNodes = NULL; 14 | s_pAllTargets = NULL; 15 | s_pTimer = NULL; 16 | s_pDefaultFormatter = NULL; 17 | s_pDefaultFilter = NULL; 18 | s_pStringSearchMutex = NULL; 19 | s_pMessageCreationMutex = NULL; 20 | s_pfMalloc = NULL; 21 | s_pfFree = NULL; 22 | s_pSelf = this; 23 | s_nSockets = 0; 24 | } 25 | 26 | void Statics::Reset() 27 | { 28 | DestroyGlobalTimer(); 29 | DestroyDefaultFormatter(); 30 | s_pDefaultFilter = NULL; // This will be destroyed on the next step 31 | DestroyAllNodes(); 32 | DestroyStringSearchMutex(); 33 | DestroyMessageCreationMutex(); 34 | s_pfMalloc = NULL; 35 | s_pfFree = NULL; 36 | s_nSockets = 0; 37 | } 38 | 39 | Statics::~Statics() 40 | { 41 | Reset(); 42 | } 43 | 44 | Statics s_Statics; 45 | 46 | Statics &Static() 47 | { 48 | return s_Statics; 49 | } 50 | 51 | void DestroyStatic() 52 | { 53 | s_Statics.~Statics(); 54 | } 55 | 56 | } 57 | 58 | -------------------------------------------------------------------------------- /src/api.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * \file api.cpp 3 | */ 4 | 5 | #include "logog.hpp" 6 | 7 | #include 8 | 9 | namespace logog { 10 | 11 | static int s_nInitializations = 0; 12 | 13 | void Initialize( INIT_PARAMS *params ) 14 | { 15 | if ( s_nInitializations++ == 0 ) 16 | { 17 | if ( params == NULL ) 18 | { 19 | Static().s_pfMalloc = malloc; 20 | Static().s_pfFree = free; 21 | } 22 | else 23 | { 24 | if ( params->m_pfMalloc != NULL ) 25 | { 26 | Static().s_pfMalloc = params->m_pfMalloc; 27 | Static().s_pfFree = params->m_pfFree; 28 | } 29 | else 30 | { 31 | Static().s_pfMalloc = malloc; 32 | Static().s_pfFree = free; 33 | } 34 | } 35 | 36 | // Let's allocate a default filter here. 37 | GetFilterDefault(); 38 | 39 | // Socket::Initialize(); 40 | } 41 | } 42 | 43 | void Shutdown( ) 44 | { 45 | if ( --s_nInitializations == 0 ) 46 | { 47 | // Socket::Shutdown(); 48 | 49 | #ifdef LOGOG_DESTROY_STATIC_AREA 50 | delete &( Static() ); 51 | #else 52 | Static().Reset(); 53 | #endif 54 | 55 | #ifdef LOGOG_LEAK_DETECTION 56 | ReportMemoryAllocations(); 57 | #endif 58 | } 59 | } 60 | } 61 | 62 | -------------------------------------------------------------------------------- /include/checkpoint.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file checkpoint.hpp Representations of a program counter reaching a specific point in code. 3 | */ 4 | 5 | #ifndef __LOGOG_CHECKPOINT_HPP__ 6 | #define __LOGOG_CHECKPOINT_HPP__ 7 | 8 | namespace logog 9 | { 10 | /** A checkpoint is a topic that fires when a specific section of code is executed. The first time a bit of 11 | ** code is executed, a Checkpoint is instanced, and when the code is executed again, the Checkpoint is 12 | ** reused. 13 | **/ 14 | class Checkpoint : public TopicSource 15 | { 16 | public: 17 | Checkpoint( const LOGOG_LEVEL_TYPE level = LOGOG_LEVEL_ALL, 18 | const LOGOG_CHAR *sFileName = NULL, 19 | const int nLineNumber = 0, 20 | const LOGOG_CHAR *sGroup = NULL, 21 | const LOGOG_CHAR *sCategory = NULL, 22 | const LOGOG_CHAR *sMessage = NULL, 23 | const double dTimestamp = 0.0f ); 24 | 25 | /** Sends the node in question. Optionally updates the timestamp in this checkpoint before sending the node. */ 26 | virtual int Send( const Topic &node ); 27 | 28 | }; 29 | } 30 | 31 | #endif // __LOGOG_CHECKPOINT_HPP_ 32 | -------------------------------------------------------------------------------- /include/timer.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file timer.hpp Time management. 3 | */ 4 | 5 | #ifndef __LOGOG_TIMER_HPP__ 6 | #define __LOGOG_TIMER_HPP__ 7 | 8 | namespace logog 9 | { 10 | /** A value for a high resolution timer on this platform. Time representations are in seconds. */ 11 | typedef double LOGOG_TIME; 12 | 13 | /** A high-resolution timer. Reports in seconds. */ 14 | class Timer : public Object 15 | { 16 | public: 17 | Timer(); 18 | 19 | /** Returns the offset from the time since the creation of the timer, or the time set by the most 20 | ** recent Set() call. Time is assumed to be a value in LOGOG_TIME seconds. 21 | ** \sa LOGOG_TIME 22 | **/ 23 | LOGOG_TIME Get(); 24 | 25 | /** Sets the current time for this timer. */ 26 | void Set( LOGOG_TIME time ); 27 | 28 | protected: 29 | #ifdef LOGOG_FLAVOR_WINDOWS 30 | /** Windows only. Stores the number of high resolution timer ticks per second. */ 31 | double m_fTicksPerMicrosecond; 32 | #endif 33 | /** Zero, if no calls to Set() have been made; else the value of the previous call to Set(). */ 34 | LOGOG_TIME m_fStartTime; 35 | }; 36 | 37 | extern Timer &GetGlobalTimer(); 38 | extern void DestroyGlobalTimer(); 39 | 40 | } 41 | 42 | #endif // __LOGOG_TIMER_HPP_ 43 | -------------------------------------------------------------------------------- /include/message.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file message.hpp Messages; items transmitted to a log. 3 | */ 4 | 5 | #ifndef __LOGOG_MESSAGE_HPP__ 6 | #define __LOGOG_MESSAGE_HPP__ 7 | 8 | namespace logog 9 | { 10 | 11 | /** A message is a piece of text that's actually transmitted to outputs. Messages can be asked to 12 | ** Transmit() themselves once they are created. 13 | **/ 14 | class Message : public Checkpoint 15 | { 16 | public: 17 | Message( const LOGOG_LEVEL_TYPE level = LOGOG_LEVEL_ALL, 18 | const LOGOG_CHAR *sFileName = NULL, 19 | const int nLineNumber = 0, 20 | const LOGOG_CHAR *sGroup = NULL, 21 | const LOGOG_CHAR *sCategory = NULL, 22 | const LOGOG_CHAR *sMessage = NULL, 23 | const double dTimestamp = 0.0f, 24 | bool *bIsCreated = NULL ); 25 | 26 | virtual ~Message(); 27 | 28 | /** Causes this checkpoint to republish itself to all existing filters after 29 | * unpublishing itself. This can be necessary if the message within this 30 | * message has changed in such a way that the downstream Filter objects 31 | * might react differently to it. 32 | */ 33 | virtual bool Republish(); 34 | 35 | Mutex m_Transmitting; 36 | bool *m_pbIsCreated; 37 | }; 38 | 39 | extern Mutex &GetMessageCreationMutex(); 40 | extern void DestroyMessageCreationMutex(); 41 | 42 | } 43 | 44 | 45 | #endif // __LOGOG_MESSAGE_HPP_ 46 | -------------------------------------------------------------------------------- /src/timer.cpp: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * \file timer.cpp 4 | */ 5 | 6 | #include "logog.hpp" 7 | 8 | namespace logog { 9 | 10 | Timer::Timer() 11 | { 12 | m_fStartTime = 0.0f; 13 | 14 | #ifdef LOGOG_FLAVOR_WINDOWS 15 | LARGE_INTEGER TicksPerSecond; 16 | QueryPerformanceFrequency( &TicksPerSecond ); 17 | m_fTicksPerMicrosecond = (DOUBLE)TicksPerSecond.QuadPart * 0.000001; 18 | #endif 19 | Set( 0.0f ); 20 | } 21 | 22 | //! [TimerGet] 23 | logog::LOGOG_TIME Timer::Get() 24 | { 25 | #ifdef LOGOG_FLAVOR_WINDOWS 26 | LARGE_INTEGER liTime; 27 | QueryPerformanceCounter( &liTime ); 28 | 29 | double dusec; 30 | dusec =( liTime.QuadPart / m_fTicksPerMicrosecond ); 31 | 32 | return ( dusec / 1000000.0f ) - m_fStartTime; 33 | #endif 34 | 35 | #ifdef LOGOG_FLAVOR_POSIX 36 | #ifdef LOGOG_TARGET_PS3 37 | LOGOG_PS3_GET_TIME; 38 | #else // LOGOG_TARGET_PS3 39 | // General Posix implementation 40 | timeval tv; 41 | gettimeofday( &tv, 0 ); 42 | return ((double) (tv.tv_sec) + ((double)(tv.tv_usec ) / 1000000.0 ) - m_fStartTime); 43 | #endif // LOGOG_TARGET_PS3 44 | #endif 45 | } 46 | //! [TimerGet] 47 | 48 | void Timer::Set( LOGOG_TIME time ) 49 | { 50 | m_fStartTime = time + Get(); 51 | } 52 | 53 | Timer &GetGlobalTimer() 54 | { 55 | Statics *pStatic = &Static(); 56 | 57 | #ifdef LOGOG_INTERNAL_DEBUGGING 58 | if ( pStatic == NULL ) 59 | LOGOG_INTERNAL_FAILURE; 60 | #endif 61 | 62 | if ( pStatic->s_pTimer == NULL ) 63 | pStatic->s_pTimer = new Timer(); 64 | 65 | return *(pStatic->s_pTimer ); 66 | } 67 | 68 | void DestroyGlobalTimer() 69 | { 70 | Statics *pStatic = &Static(); 71 | Timer *pGlobalTimer = pStatic->s_pTimer; 72 | 73 | if ( pGlobalTimer != NULL ) 74 | delete pGlobalTimer; 75 | 76 | pStatic->s_pTimer = NULL; 77 | } 78 | 79 | } 80 | 81 | -------------------------------------------------------------------------------- /src/mutex.cpp: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * \file mutex.cpp 4 | */ 5 | 6 | #include "logog.hpp" 7 | 8 | namespace logog { 9 | 10 | Mutex::Mutex() LOGOG_MUTEX_CTOR( m_Mutex ) 11 | { 12 | LOGOG_MUTEX_INIT(&m_Mutex); 13 | } 14 | 15 | Mutex::Mutex( const Mutex & ) 16 | { 17 | LOGOG_MUTEX_INIT(&m_Mutex); 18 | } 19 | 20 | Mutex & Mutex::operator = (const Mutex &) 21 | { 22 | LOGOG_MUTEX_INIT(&m_Mutex); 23 | return *this; 24 | } 25 | 26 | Mutex::~Mutex() 27 | { 28 | LOGOG_MUTEX_DELETE(&m_Mutex); 29 | } 30 | 31 | void Mutex::MutexLock() 32 | { 33 | LOGOG_MUTEX_LOCK(&m_Mutex); 34 | } 35 | 36 | void Mutex::MutexUnlock() 37 | { 38 | LOGOG_MUTEX_UNLOCK(&m_Mutex); 39 | } 40 | 41 | ScopedLock::ScopedLock( Mutex &mutex ) 42 | { 43 | m_pMutex = &mutex; 44 | m_pMutex->MutexLock(); 45 | } 46 | 47 | ScopedLock::~ScopedLock() 48 | { 49 | m_pMutex->MutexUnlock(); 50 | } 51 | 52 | #ifdef LOGOG_LEAK_DETECTION 53 | Mutex s_AllocationsMutex; 54 | void LockAllocationsMutex() 55 | { 56 | s_AllocationsMutex.MutexLock(); 57 | } 58 | void UnlockAllocationsMutex() 59 | { 60 | s_AllocationsMutex.MutexUnlock(); 61 | } 62 | #endif // LOGOG_LEAK_DETECTION 63 | 64 | Mutex &GetStringSearchMutex() 65 | { 66 | Statics *pStatic = &Static(); 67 | Mutex **ppStringSearchMutex = (Mutex **)&( pStatic->s_pStringSearchMutex ); 68 | 69 | #ifdef LOGOG_INTERNAL_DEBUGGING 70 | if ( pStatic == NULL ) 71 | LOGOG_INTERNAL_FAILURE; 72 | #endif 73 | if ( *ppStringSearchMutex == NULL ) 74 | *ppStringSearchMutex = new Mutex(); 75 | 76 | return *(( Mutex *)( *ppStringSearchMutex )); 77 | } 78 | 79 | void DestroyStringSearchMutex() 80 | { 81 | Statics *pStatic = &Static(); 82 | Mutex **ppStringSearchMutex = (Mutex **)&( pStatic->s_pStringSearchMutex ); 83 | 84 | if ( *ppStringSearchMutex != NULL ) 85 | { 86 | delete *ppStringSearchMutex; 87 | *ppStringSearchMutex = NULL; 88 | } 89 | } 90 | 91 | } 92 | 93 | -------------------------------------------------------------------------------- /src/message.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * \file message.cpp 3 | */ 4 | 5 | #include "logog.hpp" 6 | 7 | namespace logog 8 | { 9 | Message::Message( const LOGOG_LEVEL_TYPE level, 10 | const LOGOG_CHAR *sFileName , 11 | const int nLineNumber, 12 | const LOGOG_CHAR *sGroup, 13 | const LOGOG_CHAR *sCategory, 14 | const LOGOG_CHAR *sMessage, 15 | const double dTimestamp, 16 | bool *pbIsCreated ) : 17 | Checkpoint( level, sFileName, nLineNumber, sGroup, sCategory, sMessage, dTimestamp ) 18 | { 19 | m_pbIsCreated = pbIsCreated; 20 | 21 | if ( pbIsCreated != NULL ) 22 | *pbIsCreated = true; 23 | 24 | /* Messages are always sources, so there's no need to call Initialize() here */ 25 | // Initialize(); 26 | 27 | /* NOTE! The message is typically assigned to a checkpoint AFTER it's been published. 28 | * Ergo a message needs to be unpublished from, and published to, all filters 29 | * iff one of those filters is searching for a substring of that message. 30 | */ 31 | PublishToMultiple( AllFilters() ); 32 | } 33 | 34 | Message::~Message() 35 | { 36 | if ( m_pbIsCreated != NULL ) 37 | *m_pbIsCreated = false; 38 | } 39 | 40 | 41 | bool Message::Republish() 42 | { 43 | UnpublishToMultiple( AllFilters() ); 44 | return PublishToMultiple( AllFilters() ); 45 | } 46 | 47 | Mutex &GetMessageCreationMutex() 48 | { 49 | Statics *pStatic = &Static(); 50 | 51 | if ( pStatic->s_pMessageCreationMutex == NULL ) 52 | pStatic->s_pMessageCreationMutex = new Mutex(); 53 | 54 | return *( pStatic->s_pMessageCreationMutex ); 55 | } 56 | 57 | void DestroyMessageCreationMutex() 58 | { 59 | Statics *pStatic = &Static(); 60 | 61 | if ( pStatic->s_pMessageCreationMutex != NULL ) 62 | { 63 | delete pStatic->s_pMessageCreationMutex; 64 | pStatic->s_pMessageCreationMutex = NULL; 65 | } 66 | } 67 | } 68 | 69 | 70 | -------------------------------------------------------------------------------- /include/api.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file api.hpp Initialization and shutdown functions for logog. 3 | */ 4 | 5 | #ifndef __LOGOG_API_HPP__ 6 | #define __LOGOG_API_HPP__ 7 | 8 | namespace logog 9 | { 10 | //! [INIT_PARAMS] 11 | /** 12 | * To initialize the memory manager with non-default values, allocate a temporary INIT_PARAMS structure, fill it with 13 | * zeros, and then change the individual entries in the INIT_PARAMS struct before passing as a parameter to Initialize(). 14 | * \sa Initialize 15 | */ 16 | struct INIT_PARAMS 17 | { 18 | /** A pointer to a function that allocates memory. By default logog allocates using malloc(). Change this pointer 19 | ** before passing to Initialize() to use your own custom memory allocator. 20 | * \sa logog::Initialize() 21 | */ 22 | void *( *m_pfMalloc )( size_t ); 23 | 24 | /** A pointer to a function that frees memory. By default logog frees using free(). Change this pointer 25 | * before passing to Initialize() to use your own custom memory allocator. 26 | * \sa logog::Initialize() 27 | */ 28 | void ( *m_pfFree )( void * ); 29 | }; 30 | //! [INIT_PARAMS] 31 | 32 | /** Initializes the logog system. No logog calls or allocations may be made before calling this function; expect 33 | * crashes if you haven't called this at the top of your program. Furthermore, this function is not thread safe. 34 | * Only call it once, at the start of your program thread. 35 | * \param params The address of an INIT_PARAMS structure you have already allocated on the heap, or NULL to 36 | * use default values. 37 | * \sa INIT_PARAMS 38 | */ 39 | extern void Initialize( INIT_PARAMS *params = NULL ); 40 | 41 | /** Shuts down the logog system and frees all memory allocated by logog. Memory still allocated by the logog system after Shutdown() indicates 42 | ** a bug. This function is not thread safe. Call it once at the conclusion of your program. 43 | **/ 44 | extern void Shutdown( ); 45 | 46 | } 47 | 48 | #endif // __LOGOG_API_HPP 49 | -------------------------------------------------------------------------------- /include/statics.hpp: -------------------------------------------------------------------------------- 1 | /*! 2 | * \file statics.hpp Static variables simultaneously assigned for all instances of logog. 3 | */ 4 | 5 | #ifndef __LOGOG_STATICS_HPP__ 6 | #define __LOGOG_STATICS_HPP__ 7 | 8 | 9 | namespace logog 10 | { 11 | 12 | class Timer; 13 | class Formatter; 14 | class Target; 15 | class Mutex; 16 | 17 | extern void DestroyAllNodes(); 18 | extern void DestroyGlobalTimer(); 19 | extern void DestroyDefaultFormatter(); 20 | extern void DestroyStringSearchMutex(); 21 | extern void DestroyMessageCreationMutex(); 22 | 23 | /* Technically this information should be in node.hpp but statics is responsible for 24 | * this global list. 25 | */ 26 | class Statics 27 | { 28 | public: 29 | /** A pointer to the malloc() compatible function used by logog. See logog::Initialize() 30 | ** for more details. */ 31 | void *(*s_pfMalloc)( size_t ); 32 | /** A pointer to the free() compatible function used by logog. See logog::Initialize() 33 | ** for more details. */ 34 | void (*s_pfFree)( void * ); 35 | /** Pointers to all the currently existing nodes in the network. */ 36 | void *s_pAllNodes; 37 | /** Pointers to only those nodes that are capable of subscribing. */ 38 | void *s_pAllSubscriberNodes; 39 | /** Pointers to only those nodes that are capable of subscribing and publishing. */ 40 | void *s_pAllFilterNodes; 41 | /** Pointers to the group of all valid targets. */ 42 | void *s_pAllTargets; 43 | /** Pointer to the default filter, if any. */ 44 | void *s_pDefaultFilter; 45 | /** The default global shared timer. All events are generally in reference to this timer, though yoy may create your own timers. */ 46 | Timer *s_pTimer; 47 | /** A lock on the KMP search for all strings. Prevents mutex explosions. */ 48 | void *s_pStringSearchMutex; 49 | /** A lock for creating messages. Prevents dual message creation from multiple threads. */ 50 | Mutex *s_pMessageCreationMutex; 51 | /** The default Formatter for all targets. Targets may use their individual formatters as well if preferred. */ 52 | Formatter *s_pDefaultFormatter; 53 | /** The number of sockets created. */ 54 | int s_nSockets; 55 | /** A pointer to this object; used for final destruction. */ 56 | Statics *s_pSelf; 57 | 58 | Statics(); 59 | ~Statics(); 60 | 61 | /** Resets all statics to startup values. Releases memory allocated by statics. */ 62 | void Reset(); 63 | 64 | }; 65 | 66 | extern Statics &Static(); 67 | 68 | /** Destroys the Static() structure. Calls to Static() after calling DestroyStatic() will probably crash your 69 | ** program. 70 | */ 71 | extern void DestroyStatic(); 72 | 73 | } 74 | #endif // __LOGOG_STATICS_HPP 75 | -------------------------------------------------------------------------------- /include/mutex.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file mutex.hpp Defines a mutual exclusion object for the current platform. 3 | */ 4 | 5 | #ifndef __LOGOG_MUTEX_HPP__ 6 | #define __LOGOG_MUTEX_HPP__ 7 | 8 | #ifdef LOGOG_FLAVOR_POSIX 9 | #include 10 | #endif 11 | 12 | namespace logog 13 | { 14 | 15 | //! [Mutex] 16 | #ifndef LOGOG_MUTEX 17 | 18 | #ifdef LOGOG_FLAVOR_WINDOWS 19 | #define LOGOG_MUTEX(x) CRITICAL_SECTION (x); 20 | #define LOGOG_MUTEX_INIT(x) InitializeCriticalSection (x) 21 | #define LOGOG_MUTEX_DELETE(x) DeleteCriticalSection (x) 22 | #define LOGOG_MUTEX_LOCK(x) EnterCriticalSection (x) 23 | #define LOGOG_MUTEX_UNLOCK(x) LeaveCriticalSection (x) 24 | #define LOGOG_MUTEX_CTOR(x) 25 | #endif // LOGOG_FLAVOR_WINDOWS 26 | 27 | #ifdef LOGOG_FLAVOR_POSIX 28 | #define LOGOG_MUTEX(x) pthread_mutex_t (x); 29 | #define LOGOG_MUTEX_INIT(x) pthread_mutex_init(x, 0) 30 | #define LOGOG_MUTEX_DELETE(x) pthread_mutex_destroy (x) 31 | #define LOGOG_MUTEX_LOCK(x) pthread_mutex_lock (x) 32 | #define LOGOG_MUTEX_UNLOCK(x) pthread_mutex_unlock (x) 33 | #define LOGOG_MUTEX_CTOR(x) 34 | #endif // LOGOG_FLAVOR_POSIX 35 | #endif // LOGOG_MUTEX 36 | 37 | #ifndef LOGOG_MUTEX 38 | #error You need to define mutex macros for your platform; please see mutex.hpp 39 | #endif 40 | 41 | //! [Mutex] 42 | 43 | /** An object that can only be locked by one thread at a time. Implement the LOGOG_MUTEX_* functions for your platform 44 | * to support the Mutex object. 45 | * A mutex is intended to be used with the ScopedLock object to implement critical sections within logog. 46 | * \sa ScopedLock 47 | */ 48 | class Mutex : public Object 49 | { 50 | public: 51 | Mutex(); 52 | ~Mutex(); 53 | /** Acquires a lock on the mutex. Only one thread is permitted to lock the mutex at one time. */ 54 | void MutexLock(); 55 | /** Releases the lock on the mutex. */ 56 | void MutexUnlock(); 57 | 58 | protected: 59 | Mutex(const Mutex &); 60 | Mutex & operator = (const Mutex &); 61 | 62 | LOGOG_MUTEX( m_Mutex ) 63 | }; 64 | 65 | /** Asserts a lock while this object exists and is in scope. A ScopedLock should be 66 | * declared in "auto" format, typically on the stack. 67 | */ 68 | class ScopedLock : public Object 69 | { 70 | public : 71 | /** Instances and locks a ScopedLock. 72 | * \param mutex The mutex to attempt to lock. Program execution halts at this point until the lock can be obtained. 73 | */ 74 | ScopedLock( Mutex &mutex ); 75 | ~ScopedLock(); 76 | protected: 77 | /** A pointer to the lockable mutex. */ 78 | Mutex *m_pMutex; 79 | 80 | private: 81 | /* no default constructor */ 82 | ScopedLock(); 83 | /* no copy constructor */ 84 | ScopedLock( const ScopedLock &other ); 85 | }; 86 | 87 | #ifdef LOGOG_LEAK_DETECTION 88 | extern Mutex s_AllocationsMutex; 89 | extern void LockAllocationsMutex(); 90 | extern void UnlockAllocationsMutex(); 91 | #endif // LOGOG_LEAK_DETECTION 92 | 93 | extern Mutex &GetStringSearchMutex(); 94 | extern void DestroyStringSearchMutex(); 95 | 96 | } 97 | 98 | #endif // __LOGOG_MUTEX_HPP_ 99 | -------------------------------------------------------------------------------- /include/const.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file const.hpp Constants. 3 | */ 4 | 5 | #ifndef __LOGOG_CONST_HPP__ 6 | #define __LOGOG_CONST_HPP__ 7 | 8 | #ifndef LOGOG_FORMATTER_MAX_LENGTH 9 | /** The maximum length of a single line that a formatter may output, in LOGOG_CHAR units. */ 10 | #define LOGOG_FORMATTER_MAX_LENGTH ( 1024 * 16 ) 11 | #endif 12 | 13 | #ifndef LOGOG_DEFAULT_LOG_BUFFER_SIZE 14 | /** The default size of a RingBuffer object for buffering outputs. */ 15 | #define LOGOG_DEFAULT_LOG_BUFFER_SIZE ( 4 * 1024 * 1024 ) 16 | #endif 17 | 18 | #ifndef LOGOG_TIME_STRING_MAX 19 | /** The maximum length of a char string containing a text representation of time. \sa TimeStamp */ 20 | #define LOGOG_TIME_STRING_MAX 256 21 | #endif 22 | 23 | #ifndef LOGOG_TIME_FORMAT_MAX 24 | /** The maximum length of a char string representing a formatter for time. \sa TimeStamp */ 25 | #define LOGOG_TIME_FORMAT_MAX 128 26 | #endif 27 | 28 | #ifndef LOGOG_DEFAULT_TIME_FORMAT 29 | /** The default format for time output. \sa Formatter::SetTimeOfDayFormat */ 30 | #define LOGOG_DEFAULT_TIME_FORMAT "%c" 31 | #endif 32 | 33 | 34 | /** \addtogroup levelsettings Level Settings 35 | ** These are level settings for logog. These settings are valid for the LOGOG_LEVEL compilation flag. To enable 36 | ** all logog messages, use the compilation flag -DLOGOG_LEVEL=LOGOG_LEVEL_ALL. To disable all logog messages 37 | ** (and effectively remove logog code from your executable) use the compilation flag -DLOGOG_LEVEL=LOGOG_LEVEL_NONE. 38 | ** \sa LOGOG_LEVEL 39 | ** @{ 40 | **/ 41 | //! [Level Constants] 42 | #define LOGOG_LEVEL_NONE 0 43 | #define LOGOG_LEVEL_EMERGENCY 8 44 | #define LOGOG_LEVEL_ALERT 16 45 | #define LOGOG_LEVEL_CRITICAL 24 46 | #define LOGOG_LEVEL_ERROR 32 47 | #define LOGOG_LEVEL_WARN 40 48 | #define LOGOG_LEVEL_WARN1 48 49 | #define LOGOG_LEVEL_WARN2 56 50 | #define LOGOG_LEVEL_WARN3 64 51 | #define LOGOG_LEVEL_INFO 72 52 | #define LOGOG_LEVEL_DEBUG 80 53 | #define LOGOG_LEVEL_ALL 88 54 | //! [Level Constants] 55 | 56 | #define LOGOG_LEVEL_TYPE int 57 | 58 | #ifndef LOGOG_LEVEL 59 | #define LOGOG_LEVEL LOGOG_LEVEL_DEBUG 60 | #endif 61 | 62 | /** @} */ 63 | 64 | /** \addtogroup topicbitstype Topic Bits Type 65 | * Bit flags representing whether a topic cares about a specific field or not. 1 = care, 0 = don't care. 66 | * @{ 67 | */ 68 | //! [Topic Bits] 69 | typedef enum 70 | { 71 | TOPIC_LEVEL_FLAG = 0x01, 72 | TOPIC_LINE_NUMBER_FLAG = 0x02, 73 | TOPIC_FILE_NAME_FLAG = 0x04, 74 | TOPIC_GROUP_FLAG = 0x08, 75 | TOPIC_CATEGORY_FLAG = 0x10, 76 | TOPIC_MESSAGE_FLAG = 0x20, 77 | TOPIC_TIMESTAMP_FLAG = 0x40, 78 | /** Bits 0 through TOPIC_COUNT turned on */ 79 | TOPIC_ALL = 0x7f 80 | } TopicBitsType; 81 | //! [Topic Bits] 82 | 83 | /** @} */ 84 | 85 | typedef int TOPIC_FLAGS; 86 | 87 | //! [Topic Offsets] 88 | /** Offsets within the m_vIntProps and m_vStringProps arrays for this topic. */ 89 | typedef enum 90 | { 91 | TOPIC_LEVEL = 0, 92 | TOPIC_LINE_NUMBER = 1, 93 | /** This must be the number of integer fields. */ 94 | TOPIC_INT_COUNT = 2, 95 | 96 | TOPIC_FILE_NAME = 0, 97 | TOPIC_GROUP = 1, 98 | TOPIC_CATEGORY = 2, 99 | TOPIC_MESSAGE = 3, 100 | /** This must be the number of string fields for this topic. */ 101 | TOPIC_STRING_COUNT = 4 102 | } TopicOffsetType; 103 | //! [Topic Offsets] 104 | 105 | #endif // __LOGOG_CONST_HPP__ 106 | -------------------------------------------------------------------------------- /src/unittest.cpp: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * \file unittest.cpp 4 | */ 5 | 6 | #define LOGOG_UNIT_TESTING 1 7 | 8 | #include "logog.hpp" 9 | 10 | namespace logog 11 | { 12 | 13 | TestRegistryType &LogogTestRegistry() 14 | { 15 | static TestRegistryType *pRegistry = new TestRegistryType(); 16 | return *pRegistry; 17 | } 18 | 19 | TestSignup::TestSignup( UnitTest *pTest ) 20 | { 21 | m_pTest = pTest; 22 | LogogTestRegistry().push_back( pTest ); 23 | } 24 | 25 | UnitTest::UnitTest( const TestNameType &sTestName ) 26 | { 27 | m_sTestName = sTestName; 28 | m_pTestSignup = new TestSignup( this ); 29 | } 30 | 31 | UnitTest::~UnitTest() 32 | { 33 | FreeInternals(); 34 | } 35 | 36 | void UnitTest::FreeInternals() 37 | { 38 | if ( m_pTestSignup ) 39 | delete m_pTestSignup; 40 | 41 | m_pTestSignup = NULL; 42 | } 43 | 44 | /** Returns the name of this UnitTest provided at construction time. */ 45 | TestNameType &UnitTest::GetName() 46 | { 47 | return m_sTestName; 48 | } 49 | 50 | /** Executes all currently registered tests and prints a report of success or failure. */ 51 | int RunAllTests() 52 | { 53 | using namespace std; 54 | 55 | int nTests = 0, nTestsSucceeded = 0; 56 | int nTestResult; 57 | int nFailures = 0; 58 | 59 | #ifdef LOGOG_UNICODE 60 | wostream *pOut; 61 | #else // LOGOG_UNICODE 62 | ostream *pOut; 63 | #endif // LOGOG_UNICODE 64 | 65 | pOut = &(LOGOG_COUT); 66 | 67 | nTests = (int) LogogTestRegistry().size(); 68 | 69 | if ( nTests == 0 ) 70 | { 71 | *pOut << _LG("No tests currently defined.") << endl; 72 | return 1; 73 | } 74 | 75 | for ( TestRegistryType::iterator it = LogogTestRegistry().begin(); 76 | it != LogogTestRegistry().end(); 77 | ++it ) 78 | { 79 | (*pOut) << _LG("Test ") << (*it)->GetName() << _LG(" running... ") << endl; 80 | nTestResult = (*it)->RunTest(); 81 | 82 | (*pOut) << _LG("Test ") << (*it)->GetName(); 83 | 84 | if ( nTestResult == 0 ) 85 | { 86 | *pOut << _LG(" successful.") << endl; 87 | nTestsSucceeded++; 88 | } 89 | else 90 | { 91 | *pOut << _LG(" failed!") << endl; 92 | nFailures++; 93 | } 94 | 95 | /* Validate that no allocations are currently outstanding. Make sure to handle the case 96 | * where leak detection is disabled */ 97 | int nMemoryTestResult = ReportMemoryAllocations(); 98 | 99 | if ( nMemoryTestResult != -1 ) 100 | { 101 | (*pOut) << _LG("Test ") << (*it)->GetName() << _LG(" has ") << nMemoryTestResult << 102 | _LG(" memory allocations outstanding at end of test.") << endl; 103 | nFailures += nMemoryTestResult; 104 | } 105 | } 106 | 107 | *pOut << _LG("Testing complete, ") 108 | << nTests << _LG(" total tests, ") 109 | << nTestsSucceeded << _LG(" tests succeeded, ") 110 | << ( nTests - nTestsSucceeded ) << _LG(" failed") 111 | << endl; 112 | 113 | return nFailures; 114 | } 115 | 116 | /** Should remove all memory allocated during unit testing. */ 117 | void ShutdownTests() 118 | { 119 | TestRegistryType::iterator it; 120 | 121 | for ( it = LogogTestRegistry().begin(); it != LogogTestRegistry().end(); ++it ) 122 | (*it)->FreeInternals(); 123 | 124 | delete &LogogTestRegistry(); 125 | } 126 | 127 | } 128 | 129 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # cmakelists.txt for logog 2 | cmake_minimum_required (VERSION 2.8.4) 3 | 4 | # Compile-time constants 5 | set( LOGOG_UNICODE FALSE CACHE BOOL "Enable Unicode support for output strings.") 6 | set( LOGOG_USE_PREFIX FALSE CACHE BOOL "Apply a LOGOG_ prefix to all logging macros; helps avoid conflicts with existing code" ) 7 | set( LOGOG_LEAK_DETECTION FALSE CACHE BOOL "Enable detection of internal leaks in the library") 8 | set( LOGOG_LEAK_DETECTION_MICROSOFT CACHE BOOL "Enable detection of internal leaks using Microsoft's crt mechanism" ) 9 | set( LOGOG_UNIT_TESTING FALSE CACHE BOOL "Enable logog's internal unit testing framework.") 10 | set( LOGOG_USE_COTIRE FALSE CACHE BOOL "Use cotire to speed up builds.") 11 | 12 | if( LOGOG_USE_COTIRE ) 13 | set (CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/CMake") 14 | include(cotire OPTIONAL) 15 | endif() 16 | 17 | if ( LOGOG_LEAK_DETECTION AND LOGOG_LEAK_DETECTION_MICROSOFT ) 18 | message( FATAL_ERROR "Can't enable both LOGOG_LEAK_DETECTION and LOGOG_LEAK_DETECTION_MICROSOFT, choose one or the other") 19 | endif() 20 | 21 | if( LOGOG_UNICODE ) 22 | add_definitions( -DLOGOG_UNICODE ) 23 | endif() 24 | if( LOGOG_USE_PREFIX ) 25 | add_definitions( -DLOGOG_USE_PREFIX ) 26 | endif() 27 | if( LOGOG_LEAK_DETECTION ) 28 | add_definitions( -DLOGOG_LEAK_DETECTION ) 29 | endif() 30 | if( LOGOG_LEAK_DETECTION_MICROSOFT ) 31 | add_definitions( -DLOGOG_LEAK_DETECTION_MICROSOFT ) 32 | endif() 33 | if( LOGOG_UNIT_TESTING ) 34 | add_definitions( -DLOGOG_UNIT_TESTING ) 35 | endif() 36 | 37 | enable_testing() 38 | project (logog) 39 | include(CTest) 40 | 41 | # logog needs thread support on linux 42 | find_package( Threads ) 43 | 44 | set( RUNTIME_OUTPUT_DIRECTORY bin/ ) 45 | set( ARCHIVE_OUTPUT_DIRECTORY bin/ ) 46 | set( LIBRARY_OUTPUT_DIRECTORY bin/ ) 47 | set( CMAKE_LEGACY_CYGWIN_WIN32 0 ) 48 | 49 | if(MSVC) 50 | # Force to always compile with W4; all warnings are errors 51 | if(CMAKE_CXX_FLAGS MATCHES "/W[0-4]") 52 | string(REGEX REPLACE "/W[0-4]" "/W4 /WX" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") 53 | else() 54 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4 /WX") 55 | endif() 56 | elseif(CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX) 57 | # Warnings are errors; generate lots of warnings 58 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-variadic-macros -Wall -Werror -Wno-long-long") 59 | endif() 60 | 61 | include_directories( include ) 62 | add_library( logog 63 | src/api.cpp 64 | src/checkpoint.cpp 65 | src/formatter.cpp 66 | src/lobject.cpp 67 | src/lstring.cpp 68 | src/message.cpp 69 | src/mutex.cpp 70 | src/node.cpp 71 | src/platform.cpp 72 | src/socket.cpp 73 | src/statics.cpp 74 | src/target.cpp 75 | src/timer.cpp 76 | src/topic.cpp 77 | src/unittest.cpp 78 | ) 79 | 80 | set_target_properties(logog PROPERTIES DEBUG_POSTFIX "d") 81 | add_executable( test-logog test/test.cpp ) 82 | target_link_libraries( test-logog logog ${CMAKE_THREAD_LIBS_INIT}) 83 | if( LOGOG_USE_COTIRE ) 84 | if (COMMAND cotire) 85 | cotire( logog ) 86 | endif() 87 | endif() 88 | 89 | add_test( NAME test-harness COMMAND test-logog ) 90 | install(TARGETS logog ARCHIVE DESTINATION ${CMAKE_INSTALL_PREFIX}/lib) 91 | install(DIRECTORY include/ DESTINATION "${CMAKE_INSTALL_PREFIX}/include/logog" 92 | FILES_MATCHING PATTERN "*.hpp") 93 | 94 | # Docs generation with Doxygen 95 | find_package( Doxygen ) 96 | if( DOXYGEN_FOUND ) 97 | add_custom_target (doc ${DOXYGEN_EXECUTABLE} ${CMAKE_SOURCE_DIR}/doxyfile 98 | WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} 99 | COMMENT "Generating source code documentation with Doxygen." VERBATIM) 100 | endif() # DOXYGEN_FOUND -------------------------------------------------------------------------------- /include/formatter.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file formatter.hpp Formats a topic into human-readable, compiler lookalike format. 3 | */ 4 | 5 | #ifndef __LOGOG_FORMATTER_HPP__ 6 | #define __LOGOG_FORMATTER_HPP__ 7 | 8 | namespace logog 9 | { 10 | /** A helper object for generating a current timestamp as a string. */ 11 | class TimeStamp : public Object 12 | { 13 | public: 14 | /** Returns a pointer to a string representing the current time. Note! This pointer is only 15 | ** valid while this object is valid -- if you destroy this object, this pointer is no longer 16 | ** valid. 17 | */ 18 | const char *Get(const char *fmt=LOGOG_DEFAULT_TIME_FORMAT); 19 | 20 | protected: 21 | char cTimeString[ LOGOG_TIME_STRING_MAX ]; 22 | }; 23 | 24 | /** Converts a topic into a human-readable string for printing or otherwise rendering to a target. */ 25 | class Formatter : public Object 26 | { 27 | public: 28 | Formatter(); 29 | 30 | /** Causes this formatter to format a topic into its own m_sMessageBuffer field, and thence to 31 | ** return a reference to that string. This function must be written to be efficient; it will be called 32 | ** for every logging operation. It is strongly recommended not to allocate or free memory in this function. 33 | **/ 34 | virtual LOGOG_STRING &Format( const Topic &topic, const Target &target ) = 0; 35 | 36 | /** Causes the time of day to be rendered, if it needs to be rendered. This function is only supported on 37 | ** ANSI builds, not Unicode, as the underlying functions are ANSI only. 38 | */ 39 | virtual void RenderTimeOfDay(); 40 | 41 | /** In the base case, this function calls GetTopicFlags() on the provided 42 | ** topic in order to figure out which fields this formatter should render. 43 | ** However, subclasses of the Formatter class can override this function in order 44 | ** to change the default fields that the topic wants to be rendered. For example, 45 | ** you can turn off the TOPIC_LINE_NUMBER_FLAG and the TOPIC_FILE_NAME_FLAG 46 | ** in order to disable these fields from being rendered in your own Formatter 47 | ** subclass. 48 | ** \param topic The topic whose flags are to be determined 49 | ** \return The set of flags representing the topics that really need to be rendered. 50 | **/ 51 | virtual TOPIC_FLAGS GetTopicFlags( const Topic &topic ); 52 | 53 | /** Should this formatter render the current time of day? */ 54 | bool GetShowTimeOfDay() const; 55 | 56 | /** Sets whether this formatter renders the current time of day. Time of day is not rendered by default. 57 | ** This function is only supported on ANSI builds, not Unicode, as the underlying functions are ANSI only. 58 | **/ 59 | void SetShowTimeOfDay(bool val); 60 | 61 | #ifndef LOGOG_UNICODE 62 | /** Sets the format string used to render the current time of day. 63 | ** Uses the same format specifier as strftime, check your compiler's documentation for details. 64 | ** This function is only supported on ANSI builds, not Unicode, as the underlying functions are ANSI only. 65 | **/ 66 | void SetTimeOfDayFormat( const char *fmt ); 67 | #endif 68 | 69 | protected: 70 | const LOGOG_CHAR *ErrorDescription( const LOGOG_LEVEL_TYPE level ); 71 | 72 | LOGOG_STRING m_sMessageBuffer; 73 | LOGOG_STRING m_sIntBuffer; 74 | 75 | bool m_bShowTimeOfDay; 76 | 77 | #ifndef LOGOG_UNICODE 78 | char m_TimeOfDayFormat[LOGOG_TIME_FORMAT_MAX]; 79 | #endif 80 | 81 | }; 82 | 83 | class FormatterGCC : public Formatter 84 | { 85 | public: 86 | virtual LOGOG_STRING &Format( const Topic &topic, const Target &target ); 87 | 88 | }; 89 | 90 | class FormatterMSVC : public Formatter 91 | { 92 | public: 93 | virtual LOGOG_STRING &Format( const Topic &topic, const Target &target ); 94 | }; 95 | 96 | extern Formatter &GetDefaultFormatter(); 97 | extern void DestroyDefaultFormatter(); 98 | 99 | } 100 | 101 | #endif // __LOGOG_FORMATTER_HPP_ 102 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | doc/html/*.html 3 | doc/html/*.js 4 | doc/html/*.png 5 | doc/html/*.css 6 | doc/html/search 7 | *.cl.cfg 8 | *.cl.cmd 9 | *.PVS-Studio.cfg 10 | 11 | 12 | ################# 13 | ## Eclipse 14 | ################# 15 | 16 | *.pydevproject 17 | .project 18 | .metadata 19 | bin/ 20 | tmp/ 21 | *.tmp 22 | *.bak 23 | *.swp 24 | *~.nib 25 | local.properties 26 | .classpath 27 | .settings/ 28 | .loadpath 29 | 30 | # External tool builders 31 | .externalToolBuilders/ 32 | 33 | # Locally stored "Eclipse launch configurations" 34 | *.launch 35 | 36 | # CDT-specific 37 | .cproject 38 | 39 | # PDT-specific 40 | .buildpath 41 | 42 | 43 | ################# 44 | ## Visual Studio 45 | ################# 46 | 47 | ## Ignore Visual Studio temporary files, build results, and 48 | ## files generated by popular Visual Studio add-ons. 49 | 50 | # User-specific files 51 | *.suo 52 | *.user 53 | *.sln.docstates 54 | 55 | # Build results 56 | 57 | [Dd]ebug/ 58 | [Rr]elease/ 59 | x64/ 60 | build/ 61 | [Bb]in/ 62 | [Oo]bj/ 63 | 64 | # MSTest test Results 65 | [Tt]est[Rr]esult*/ 66 | [Bb]uild[Ll]og.* 67 | 68 | *_i.c 69 | *_p.c 70 | *.ilk 71 | *.meta 72 | *.obj 73 | *.pch 74 | *.pdb 75 | *.pgc 76 | *.pgd 77 | *.rsp 78 | *.sbr 79 | *.tlb 80 | *.tli 81 | *.tlh 82 | *.tmp 83 | *.tmp_proj 84 | *.log 85 | *.vspscc 86 | *.vssscc 87 | .builds 88 | *.pidb 89 | *.log 90 | *.scc 91 | 92 | # Visual C++ cache files 93 | ipch/ 94 | *.aps 95 | *.ncb 96 | *.opensdf 97 | *.sdf 98 | *.cachefile 99 | 100 | # Visual Studio profiler 101 | *.psess 102 | *.vsp 103 | *.vspx 104 | 105 | # Guidance Automation Toolkit 106 | *.gpState 107 | 108 | # ReSharper is a .NET coding add-in 109 | _ReSharper*/ 110 | *.[Rr]e[Ss]harper 111 | 112 | # TeamCity is a build add-in 113 | _TeamCity* 114 | 115 | # DotCover is a Code Coverage Tool 116 | *.dotCover 117 | 118 | # NCrunch 119 | *.ncrunch* 120 | .*crunch*.local.xml 121 | 122 | # Installshield output folder 123 | [Ee]xpress/ 124 | 125 | # DocProject is a documentation generator add-in 126 | DocProject/buildhelp/ 127 | DocProject/Help/*.HxT 128 | DocProject/Help/*.HxC 129 | DocProject/Help/*.hhc 130 | DocProject/Help/*.hhk 131 | DocProject/Help/*.hhp 132 | DocProject/Help/Html2 133 | DocProject/Help/html 134 | 135 | # Click-Once directory 136 | publish/ 137 | 138 | # Publish Web Output 139 | *.Publish.xml 140 | *.pubxml 141 | 142 | # NuGet Packages Directory 143 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line 144 | #packages/ 145 | 146 | # Windows Azure Build Output 147 | csx 148 | *.build.csdef 149 | 150 | # Windows Store app package directory 151 | AppPackages/ 152 | 153 | # Others 154 | sql/ 155 | *.Cache 156 | ClientBin/ 157 | [Ss]tyle[Cc]op.* 158 | ~$* 159 | *~ 160 | *.dbmdl 161 | *.[Pp]ublish.xml 162 | *.pfx 163 | *.publishsettings 164 | 165 | # RIA/Silverlight projects 166 | Generated_Code/ 167 | 168 | # Backup & report files from converting an old project file to a newer 169 | # Visual Studio version. Backup files are not needed, because we have git ;-) 170 | _UpgradeReport_Files/ 171 | Backup*/ 172 | UpgradeLog*.XML 173 | UpgradeLog*.htm 174 | 175 | # SQL Server files 176 | App_Data/*.mdf 177 | App_Data/*.ldf 178 | 179 | ############# 180 | ## Windows detritus 181 | ############# 182 | 183 | # Windows image file caches 184 | Thumbs.db 185 | ehthumbs.db 186 | 187 | # Folder config file 188 | Desktop.ini 189 | 190 | # Recycle Bin used on file shares 191 | $RECYCLE.BIN/ 192 | 193 | # Mac crap 194 | .DS_Store 195 | 196 | 197 | ############# 198 | ## Python 199 | ############# 200 | 201 | *.py[co] 202 | 203 | # Packages 204 | *.egg 205 | *.egg-info 206 | dist/ 207 | build/ 208 | eggs/ 209 | parts/ 210 | var/ 211 | sdist/ 212 | develop-eggs/ 213 | .installed.cfg 214 | 215 | # Installer logs 216 | pip-log.txt 217 | 218 | # Unit test / coverage reports 219 | .coverage 220 | .tox 221 | 222 | #Translations 223 | *.mo 224 | 225 | #Mr Developer 226 | .mr.developer.cfg 227 | -------------------------------------------------------------------------------- /include/socket.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file socket.hpp Cross-platform socket abstractions 3 | */ 4 | 5 | /** 6 | * \todo Socket support is unfinished and is not working yet. Please review socket.hpp if you'd like to implement 7 | * a cross-platform socket abstraction. 8 | */ 9 | #ifndef __LOGOG_SOCKET_HPP_ 10 | #define __LOGOG_SOCKET_HPP_ 11 | 12 | #ifdef LOGOG_FLAVOR_WINDOWS 13 | #pragma comment(lib, "wsock32.lib") 14 | #endif 15 | 16 | #include 17 | 18 | namespace logog 19 | { 20 | /** A sending or receiving socket to be used as a target. */ 21 | class Socket : public Target 22 | { 23 | public: 24 | static int Initialize() 25 | { 26 | static bool bInitialized = false; 27 | 28 | if ( !bInitialized ) 29 | { 30 | #ifdef LOGOG_FLAVOR_WINDOWS 31 | WORD wVersionRequested; 32 | WSADATA wsaData; 33 | int err; 34 | wVersionRequested = MAKEWORD( 2, 2 ); 35 | 36 | err = WSAStartup( wVersionRequested, &wsaData ); 37 | 38 | if ( err != 0 ) 39 | { 40 | #ifdef LOGOG_INTERNAL_DEBUGGING 41 | LOGOG_COUT << _LG("WSAStartup failed with error: ") << err << endl; 42 | #endif 43 | return 1; 44 | } 45 | 46 | #endif 47 | } 48 | 49 | bInitialized = true; 50 | 51 | return 0; 52 | } 53 | 54 | static void Shutdown() 55 | { 56 | #ifdef LOGOG_FLAVOR_WINDOWS 57 | WSACleanup(); 58 | #endif 59 | } 60 | 61 | static const int MAXHOSTNAME = 255; 62 | 63 | Socket( 64 | int type = SOCK_STREAM, 65 | int port = LOGOG_DEFAULT_PORT 66 | ) 67 | { 68 | m_Socket = -1; 69 | m_nType = type; 70 | m_nPort = port; 71 | } 72 | 73 | virtual void Close() 74 | { 75 | #ifdef LOGOG_FLAVOR_WINDOWS 76 | closesocket( m_Socket ); 77 | #endif 78 | #ifdef LOGOG_FLAVOR_POSIX 79 | close( m_Socket ); 80 | #endif 81 | } 82 | 83 | #ifdef NYI 84 | virtual int Create( int type, 85 | int port ) 86 | { 87 | char myname[MAXHOSTNAME+1]; 88 | int err; 89 | struct sockaddr_in sa; 90 | struct hostent *hp; 91 | 92 | memset( &sa, 0, sizeof( struct sockaddr_in ) ); /* clear our address */ 93 | gethostname( myname, MAXHOSTNAME ); /* who are we? */ 94 | hp= gethostbyname( myname ); /* get our address info */ 95 | 96 | if ( hp == NULL ) /* we don't exist !? */ 97 | return -1; 98 | 99 | sa.sin_family= hp->h_addrtype; /* this is our host address */ 100 | sa.sin_port= (u_short)htons( (u_short)port ); /* this is our port number */ 101 | 102 | if (( m_Socket = socket( AF_INET, type, 0 ) ) < 0 ) /* create socket */ 103 | return -1; 104 | 105 | if ( bind( m_Socket, (struct sockaddr*) &sa, sizeof( struct sockaddr_in ) ) < 0 ) 106 | { 107 | Close(); 108 | return -1; 109 | } 110 | 111 | if (( err = SetNonBlocking() ) != 0 ) 112 | return err; 113 | } 114 | 115 | #endif // NYI 116 | 117 | virtual int SetNonBlocking() 118 | { 119 | int err; 120 | 121 | #ifdef LOGOG_FLAVOR_POSIX 122 | int flags; 123 | flags = socket_fcntl( m_Socket, F_GETFL, 0 ); 124 | flags |= O_NONBLOCK; 125 | err = socket_fcntl( m_Socket, F_SETFL, flags ); 126 | #endif 127 | 128 | #ifdef LOGOG_FLAVOR_WINDOWS 129 | unsigned long parg; 130 | parg = 1; 131 | err = ioctlsocket( m_Socket, FIONBIO, &parg ); 132 | #endif 133 | return err; 134 | } 135 | 136 | virtual int Output( const LOGOG_STRING &output ) = 0; 137 | 138 | protected: 139 | int m_Socket; 140 | int m_nType; 141 | int m_nPort; 142 | }; 143 | 144 | class SocketServer : Socket 145 | { 146 | 147 | }; 148 | } 149 | 150 | #endif // __LOGOG_CHECKPOINT_HPP_ 151 | -------------------------------------------------------------------------------- /include/thread.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file thread.hpp Defines a thread object for the current platform. 3 | */ 4 | 5 | #ifndef __LOGOG_THREAD_HPP__ 6 | #define __LOGOG_THREAD_HPP__ 7 | 8 | //! [Thread] 9 | #ifndef LOGOG_THREAD 10 | #if defined(LOGOG_FLAVOR_WINDOWS) 11 | 12 | #include 13 | 14 | typedef unsigned int (WINAPI * __logog_pThreadFn)(void *); 15 | 16 | /* 64-bit versions of Windows use 32-bit handles for interoperability. When 17 | * sharing a handle between 32-bit and 64-bit applications, only the lower 18 | * 32 bits are significant, so it is safe to truncate the handle (when passing 19 | * it from 64-bit to 32-bit) or sign-extend the handle (when passing it from 20 | * 32-bit to 64-bit)." 21 | * source: https://msdn.microsoft.com/en-us/library/windows/desktop/aa384203%28v=vs.85%29.aspx 22 | */ 23 | 24 | #define LOGOG_THREAD HANDLE 25 | 26 | #define LOGOG_THREAD_CREATE(handle, attr, pStartFn, arg) \ 27 | (int)((*handle=(HANDLE) _beginthreadex (NULL, /* security */ \ 28 | 0, /* stack size */ \ 29 | (__logog_pThreadFn)pStartFn, /* start address */ \ 30 | arg, /* pv argument */ \ 31 | 0, /* init flag */ \ 32 | NULL /*thread addr */ ))==NULL) 33 | 34 | #define LOGOG_THREAD_JOIN( thread ) \ 35 | ( (WaitForSingleObject(( thread ),INFINITE)!=WAIT_OBJECT_0) \ 36 | || !CloseHandle(thread) \ 37 | ) 38 | #define LOGOG_THREAD_SELF (LOGOG_THREAD)((size_t)GetCurrentThreadId()) 39 | 40 | #endif // defined(LOGOG_FLAVOR_WINDOWS) 41 | 42 | #if defined(LOGOG_FLAVOR_POSIX) 43 | 44 | #define LOGOG_THREAD \ 45 | pthread_t 46 | 47 | #define LOGOG_THREAD_CREATE(handle, attr, pStartFn, arg) \ 48 | pthread_create(handle, attr, pStartFn, arg) 49 | 50 | #define LOGOG_THREAD_JOIN(thread) \ 51 | pthread_join(thread, NULL) 52 | 53 | #define LOGOG_THREAD_SELF \ 54 | pthread_self() 55 | 56 | #endif 57 | 58 | #endif // LOGOG_THREAD 59 | 60 | #ifndef LOGOG_THREAD 61 | #error You need to define mutex macros for your platform; please see mutex.hpp 62 | #endif 63 | 64 | //! [Thread] 65 | 66 | namespace logog 67 | { 68 | 69 | /** A thread abstraction. Requires definition of macros to describe how to create, start, and wait for threads to terminate. */ 70 | class Thread 71 | { 72 | public: 73 | 74 | /** A type describing the entry point of a function. */ 75 | typedef void* (*ThreadStartLocationType)(void *); 76 | 77 | /** Creating a new thread requires the starting location as well as a single void pointer to the argument to a function. */ 78 | Thread(ThreadStartLocationType fnThreadStart, void* pvParams) 79 | { 80 | m_pFnThreadStart = fnThreadStart; 81 | m_pvThreadParams = pvParams; 82 | m_Thread = nullptr; 83 | } 84 | 85 | /** Cause the created thread to commence execution asynchronously. */ 86 | int Start() 87 | { 88 | return LOGOG_THREAD_CREATE(&m_Thread, NULL, m_pFnThreadStart, m_pvThreadParams); 89 | } 90 | 91 | /** Causes the current thread to wait for completion of the provided thread. 92 | ** \param thread The thread object to wait for 93 | */ 94 | static int WaitFor(const Thread& thread) 95 | { 96 | return LOGOG_THREAD_JOIN(thread.m_Thread); 97 | } 98 | 99 | #ifdef LOGOG_THREAD_JOIN_SUPPORT 100 | static void Join(const std::vector& threads) 101 | { 102 | for(size_t i=0; i& threads) 107 | { 108 | for(size_t i=0; i b ) ? a : b ) 102 | 103 | size_t BM(LOGOG_CHAR *y, size_t n); 104 | 105 | LOGOG_CHAR *m_pBuffer; 106 | LOGOG_CHAR *m_pOffset; 107 | LOGOG_CHAR *m_pEndOfBuffer; 108 | size_t *m_pKMP; 109 | bool m_bIsConst; 110 | }; 111 | } 112 | 113 | #define LOGOG_STRING ::logog::String 114 | 115 | #endif // __LOGOG_STRING_HPP_ 116 | -------------------------------------------------------------------------------- /src/lobject.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * \file object.cpp 3 | */ 4 | 5 | #include "logog.hpp" 6 | #ifdef LOGOG_LEAK_DETECTION 7 | #include 8 | #endif // LOGOG_LEAK_DETECTION 9 | 10 | namespace logog { 11 | 12 | #ifdef LOGOG_LEAK_DETECTION 13 | AllocationsType s_Allocations; 14 | #endif 15 | 16 | Object::Object() {} 17 | 18 | Object::~Object() 19 | { 20 | 21 | } 22 | 23 | void *Object::operator new( size_t nSize ) 24 | { 25 | return Allocate( nSize ); 26 | } 27 | 28 | void *Object::operator new[]( size_t nSize ) 29 | { 30 | return Allocate( nSize ); 31 | } 32 | 33 | #ifdef _DEBUG 34 | #ifdef LOGOG_FLAVOR_WINDOWS 35 | 36 | void *Object::operator new(size_t nSize, LPCSTR lpszFileName, int nLine) 37 | { 38 | /* avoid unref'd parameter warnings */ 39 | lpszFileName; 40 | nLine; 41 | return Allocate( nSize ); 42 | } 43 | 44 | void *Object::operator new[](size_t nSize, LPCSTR lpszFileName, int nLine) 45 | { 46 | /* avoid unref'd parameter warnings */ 47 | lpszFileName; 48 | nLine; 49 | return Allocate( nSize ); 50 | } 51 | 52 | void Object::operator delete(void *ptr, LPCSTR lpszFileName, int nLine) 53 | { 54 | lpszFileName; 55 | nLine; 56 | Deallocate( ptr ); 57 | } 58 | 59 | void Object::operator delete[](void *ptr, LPCSTR lpszFileName, int nLine) 60 | { 61 | lpszFileName; 62 | nLine; 63 | Deallocate( ptr ); 64 | } 65 | 66 | #endif // LOGOG_FLAVOR_WINDOWS 67 | #endif // _DEBUG 68 | 69 | /** Deletes an object pointed to by ptr. */ 70 | void Object::operator delete( void *ptr ) 71 | { 72 | Deallocate( ptr ); 73 | } 74 | /** Deletes an object array pointed to by ptr. */ 75 | void Object::operator delete[]( void *ptr ) 76 | { 77 | Deallocate( ptr ); 78 | } 79 | 80 | /** Allocates nSize bytes of memory. You must call logog::Initialize() before calling this function. 81 | * \sa Initialize() 82 | */ 83 | void *Object::Allocate( size_t nSize ) 84 | { 85 | void *ptr = Static().s_pfMalloc( nSize ); 86 | #ifdef LOGOG_REPORT_ALLOCATIONS 87 | LOGOG_COUT << _LG("Allocated ") << nSize << _LG(" bytes of memory at ") << ptr << endl; 88 | #endif // LOGOG_REPORT_ALLOCATIONS 89 | #ifdef LOGOG_LEAK_DETECTION 90 | AllocationsType::iterator it; 91 | 92 | LockAllocationsMutex(); 93 | it = s_Allocations.find( ptr ); 94 | 95 | if ( it != s_Allocations.end() ) 96 | { 97 | LOGOG_COUT << _LG("Reallocation detected in memory manager! We seem to have allocated the same address twice ") 98 | << _LG("without freeing it! Address = ") << ptr << std::endl; 99 | UnlockAllocationsMutex(); 100 | LOGOG_INTERNAL_FAILURE; 101 | } 102 | 103 | s_Allocations.insert( LOGOG_PAIR< const PointerType, size_t >( ptr, nSize ) ); 104 | UnlockAllocationsMutex(); 105 | #endif // LOGOG_LEAK_DETECTION 106 | return ptr; 107 | } 108 | 109 | /** Deallocate a pointer previously acquired by Allocate(). */ 110 | void Object::Deallocate( void *ptr ) 111 | { 112 | #ifdef LOGOG_LEAK_DETECTION 113 | LockAllocationsMutex(); 114 | AllocationsType::iterator it; 115 | 116 | it = s_Allocations.find( ptr ); 117 | 118 | if ( it == s_Allocations.end() ) 119 | { 120 | LOGOG_COUT << _LG("Freeing memory not previously allocated! Address = ") << ptr << std::endl; 121 | UnlockAllocationsMutex(); 122 | LOGOG_INTERNAL_FAILURE; 123 | } 124 | 125 | #ifdef LOGOG_REPORT_ALLOCATIONS 126 | LOGOG_COUT << _LG("Freeing ") << it->second << _LG(" bytes of memory at ") << it->first << endl; 127 | #endif // LOGOG_REPORT_ALLOCATIONS 128 | s_Allocations.erase( ptr ); 129 | UnlockAllocationsMutex(); 130 | #endif // LOGOG_LEAK_DETECTION 131 | Static().s_pfFree( ptr ); 132 | } 133 | 134 | int MemoryAllocations() 135 | { 136 | #ifdef LOGOG_LEAK_DETECTION 137 | LockAllocationsMutex(); 138 | size_t nSize = s_Allocations.size(); 139 | 140 | if ( nSize != 0 ) 141 | LOGOG_COUT << _LG("Total active allocations: ") << nSize << std::endl; 142 | 143 | UnlockAllocationsMutex(); 144 | return (int)nSize; 145 | #else // LOGOG_LEAK_DETECTION 146 | return -1; 147 | #endif // LOGOG_LEAK_DETECTION 148 | } 149 | 150 | int ReportMemoryAllocations() 151 | { 152 | #ifdef LOGOG_LEAK_DETECTION 153 | LockAllocationsMutex(); 154 | 155 | if ( s_Allocations.size() == 0 ) 156 | { 157 | LOGOG_COUT << _LG("No memory allocations outstanding.") << std::endl; 158 | } 159 | else 160 | { 161 | for ( AllocationsType::iterator it = s_Allocations.begin(); 162 | it != s_Allocations.end(); 163 | it++ ) 164 | { 165 | LOGOG_COUT << _LG("Memory allocated at ") << it->first << 166 | _LG(" with size ") << it->second << 167 | _LG(" bytes ") << std::endl; 168 | } 169 | } 170 | 171 | UnlockAllocationsMutex(); 172 | #endif 173 | return MemoryAllocations(); 174 | } 175 | 176 | } 177 | 178 | -------------------------------------------------------------------------------- /include/unittest.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file unittest.hpp Unit testing interface; may be used for other programs as well. 3 | */ 4 | #ifndef __LOGOG_UNITTEST_HPP 5 | #define __LOGOG_UNITTEST_HPP 6 | 7 | #include 8 | #include 9 | 10 | namespace logog 11 | { 12 | 13 | /** 14 | * \page unittesting Unit test framework 15 | * 16 | * A unit test framework is included with logog. This framework was intended specifically to exercise logog functionality, but it may also 17 | * be used as a general purpose test framework. 18 | * 19 | * A typical test program will look like this: 20 | * \code 21 | * int main( int argc, char *argv[] ) 22 | * { 23 | * int nResult; 24 | * nResult = RunAllTests(); 25 | * ShutdownTests(); 26 | * return nResult; 27 | * } 28 | * \endcode 29 | * 30 | * To define a unit test, create a function of the following form: 31 | * 32 | * \code 33 | * UNITTEST( AdditionTest ) 34 | * { 35 | * logog::Initialize(); 36 | * 37 | * int nResult = 0; 38 | * 39 | * if ( 2 + 2 == 4 ) 40 | * { 41 | * LOGOG_COUT << _LG("Sane.") << endl; 42 | * } 43 | * else 44 | * { 45 | * LOGOG_COUT << _LG("Insane!") << endl; 46 | * nResult = 1; 47 | * } 48 | * 49 | * logog::Shutdown(); 50 | * 51 | * return nResult; 52 | * 53 | * }; 54 | * \endcode 55 | * 56 | * The UNITTEST() macro defines a unique UnitTest object that encompasses your function. 57 | * Your function should take no parameters and return an integer value. It should return 58 | * zero if the test was successful, and non-zero if the test was unsuccesful. If any 59 | * of your tests fail, the stub main() program will propagate that error to the operating 60 | * system, which in turn can be used to halt an automated build system. 61 | * 62 | * The unit testing framework is known to leak memory. However, the underlying logog macros are not known to leak memory (let us know 63 | * if you find any leaks). 64 | */ 65 | 66 | /** A standard string type, used for labelling a test. We don't use LOGOG_STRING here because that class is mutable 67 | ** and it allocates memory. 68 | **/ 69 | typedef const char * TestNameType; 70 | class UnitTest; 71 | 72 | /** A registry for all tests. All tests are instanced using the UNITTEST() macro and stored in the LogogTestRegistry. 73 | ** \ref UNITTEST 74 | **/ 75 | typedef LOGOG_LIST< UnitTest * > TestRegistryType; 76 | 77 | /** All unit tests are registered in here at program initialization time. */ 78 | extern TestRegistryType &LogogTestRegistry(); 79 | 80 | /** A TestSignup is responsible for recording each instanced UnitTest in the test registry. */ 81 | class TestSignup 82 | { 83 | public: 84 | /** Creates a new TestSignup. Only called from constructor for UnitTest. */ 85 | TestSignup( UnitTest *pTest ); 86 | 87 | protected: 88 | /** A pointer back to the UnitTest that created this TestSignup */ 89 | UnitTest *m_pTest; 90 | private: 91 | TestSignup(); 92 | }; 93 | 94 | /** The base class for unit testing. Children of UnitTest are instanced by the UNITTEST() macro. */ 95 | class UnitTest 96 | { 97 | public: 98 | /** Instances a test. An instanced test is automatically executed when the RunAllTests() function is called. 99 | * \param sTestName A string representing the name of this test. 100 | */ 101 | UnitTest( const TestNameType &sTestName ); 102 | 103 | virtual ~UnitTest(); 104 | /** Returns the name of this UnitTest provided at construction time. */ 105 | virtual TestNameType &GetName(); 106 | /** Child classes of UnitTest() must provide a RunTest() function. A RunTest() function must initialize logog, conduct its 107 | ** tests, and return 0 if the test was successful, a non-0 value otherwise. If any RunTest() function returns any value other than 108 | ** zero, then the main RunAllTests() function will return non zero as well. 109 | */ 110 | virtual int RunTest() = 0; 111 | 112 | /** This function is called during ShutdownTests(). Its purpose is to free 113 | ** the internal structures allocated by the UnitTest without freeing 114 | ** the UnitTest itself. Microsoft's leak detector will flag the internals 115 | ** of a UnitTest as a leak unless these internals are explicitly destroyed 116 | ** prior to exit(). 117 | **/ 118 | virtual void FreeInternals(); 119 | 120 | protected: 121 | /** The name of this particular test. */ 122 | TestNameType m_sTestName; 123 | /** A pointer to the TestSignup constructed by this UnitTest. */ 124 | TestSignup *m_pTestSignup; 125 | private: 126 | UnitTest(); 127 | }; 128 | 129 | /** Executes all currently registered tests and prints a report of success or failure. */ 130 | extern int RunAllTests(); 131 | 132 | /** Should remove all memory allocated during unit testing. */ 133 | extern void ShutdownTests(); 134 | 135 | /** This should be the function prefix for a unit test. It defines a new class for the test inherited from UnitTest. It instances 136 | ** a member of the class at run-time (before main() starts). Lastly it provides the function definition for the actual test class. 137 | */ 138 | #define UNITTEST( x ) class x : public UnitTest { \ 139 | public: \ 140 | x( TestNameType name ) : \ 141 | UnitTest( name ) \ 142 | {} \ 143 | virtual ~x() {}; \ 144 | virtual int RunTest(); \ 145 | }; \ 146 | x __LogogTestInstance_ ## x ( #x ); \ 147 | int x::RunTest() 148 | 149 | } 150 | 151 | #endif // __LOGOG_UNITTEST_HPP 152 | -------------------------------------------------------------------------------- /include/node.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file node.hpp Base class for higher-level logog objects. 3 | */ 4 | 5 | #ifndef __LOGOG_NODE_HPP__ 6 | #define __LOGOG_NODE_HPP__ 7 | 8 | namespace logog 9 | { 10 | /** The base class for most high-level logog objects. Represents the publisher-subscriber 11 | ** model within logog. */ 12 | class Node; 13 | 14 | /** An aggregation of nodes. Internally, we choose a set representation because we want to be able to traverse 15 | ** the aggregation quickly while still looking up entries quickly. 16 | **/ 17 | typedef LOGOG_SET< Node *, std::less< Node * >, Allocator< Node * > > NodesType; 18 | 19 | /** A type that double inherits from NodesType and Mutex. A lockable NodesType. Handles the copy 20 | ** case correctly. 21 | **/ 22 | class LockableNodesType : public NodesType, public Mutex 23 | { 24 | public: 25 | /** A LockableNodesType shouldn't copy the internal Mutex when it is copied, but it 26 | ** should copy all the internal nodes. 27 | **/ 28 | LockableNodesType & operator = (const LockableNodesType &other); 29 | }; 30 | 31 | extern LockableNodesType &GetStaticNodes( void ** pvLocation ); 32 | 33 | /** Returns a reference to the global nodes group. Allocates a new global node group if one does not already 34 | ** exist. 35 | */ 36 | extern LockableNodesType &AllNodes(); 37 | 38 | /** Returns a reference to the group of nodes that are capable of subscribing. Allocates a new global subscriber 39 | ** node group if one does not already exist. 40 | */ 41 | extern LockableNodesType &AllSubscriberNodes(); 42 | 43 | /** Returns a reference to the group of nodes that are capable of both subscribing as well as publishing. Allocates a new global subscriber 44 | ** node group if one does not already exist. 45 | */ 46 | extern LockableNodesType &AllFilters(); 47 | 48 | /** Returns a reference to the group of nodes that represent terminals in the graph, i.e. nodes that can't publish. */ 49 | extern LockableNodesType &AllTargets(); 50 | 51 | class Node : public Object 52 | { 53 | public: 54 | 55 | /** All nodes self-register as part of the all-nodes database. */ 56 | Node(); 57 | 58 | ~Node(); 59 | 60 | /** Call this function immediately after creating a node (or any of the children of the node class.) This function currently 61 | ** registers the node as part of the list of subscriber nodes, if this node may in fact subscribe. 62 | ** If this node is capable of subscribing at all, then this function registers this node as a possible subscriber. 63 | ** Doing this helps to keep down the number of nodes we search, when we are determining which nodes a new node 64 | ** might subscribe to. We have to do this registration as a second step, after the node is completely 65 | ** initialized, as subscriberness is determined late in initialization. 66 | **/ 67 | virtual void Initialize(); 68 | 69 | /** Can a node send notifications? By default they can; later subclasses may not be able to. */ 70 | virtual bool CanPublish() const; 71 | /** Can a node receive notifications? By default they can; later subclasses may not be able to. */ 72 | virtual bool CanSubscribe() const; 73 | /** Is this node interested in receiving notifications from another topic? */ 74 | virtual bool CanSubscribeTo( const Node & ); 75 | 76 | /** In order to avoid bringing in a bunch of RTTI stuff, we permit nodes to be asked whether they're topics or not */ 77 | virtual bool IsTopic() const; 78 | 79 | /** Causes this node to begin publishing events to the given subscriber. 80 | ** \param subscriber The node to receive published events 81 | ** \return true if the request was successful, false if the subscriber was already subscribed 82 | **/ 83 | virtual bool PublishTo( Node &subscriber ); 84 | 85 | /** Causes this node to attempt to publish to some other nodes. */ 86 | virtual bool PublishToMultiple( LockableNodesType &nodes ); 87 | 88 | /** Causes this node to stop publishing events to this subscriber. 89 | ** \param subscriber The node to stop receiving events 90 | ** \return true if successful, false if the subscriber was not being published to in the first place 91 | **/ 92 | virtual bool UnpublishTo( Node &subscriber ); 93 | 94 | /** Causes this node to attempt to unpublish to some other nodes. */ 95 | virtual bool UnpublishToMultiple( LockableNodesType &nodes ); 96 | 97 | /** Causes this node to start receiving events from the given publisher. 98 | ** \param publisher The node to start receiving events from 99 | ** \return true if successful, false if the publisher was already subscribed 100 | **/ 101 | virtual bool SubscribeTo( Node &publisher ); 102 | 103 | /** Causes this node to attempt to subscribe to some other nodes. */ 104 | virtual bool SubscribeToMultiple( LockableNodesType &nodes ); 105 | 106 | 107 | /** Causes this node to unsubscribe from the given publisher's events. 108 | ** \param publisher The publisher to unsubscribe from 109 | ** \return true if successful, false if the node was already unsubscribed 110 | **/ 111 | virtual bool UnsubscribeTo( Node &publisher ); 112 | 113 | /** Causes this node to attempt to unsubscribe to some other nodes. */ 114 | virtual bool UnsubscribeToMultiple( LockableNodesType &nodes ); 115 | 116 | void Clear(); 117 | 118 | 119 | /** A pointer to any custom data you need to store for a node. */ 120 | void *m_pUserData1; 121 | 122 | /** A pointer to any custom data you need to store for a node. */ 123 | void *m_pUserData2; 124 | 125 | protected: 126 | /** A bunch of nodes that are interested in what this node has to report. */ 127 | LockableNodesType m_Subscribers; 128 | 129 | /** A bunch of nodes that this node interested in hearing from. */ 130 | LockableNodesType m_Publishers; 131 | }; 132 | 133 | extern void DestroyNodesList( void **pvList ); 134 | 135 | /** Destroys all nodes currently recorded. This happens at shutdown time. NOTE! If you have allocated 136 | ** your own logog items and free them yourself AFTER this call, exciting crashes will occur. 137 | **/ 138 | extern void DestroyAllNodes(); 139 | 140 | } 141 | 142 | #endif // __LOGOG_NODE_HPP_ 143 | -------------------------------------------------------------------------------- /include/platform.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file platform.hpp Platform specific detection and defines, including STL overrides 3 | */ 4 | 5 | #ifndef __LOGOG_PLATFORM_HPP__ 6 | #define __LOGOG_PLATFORM_HPP__ 7 | 8 | #ifdef DOXYGEN 9 | /** Set this compilation flag to 1 to indicate that this is a Windows-like platform, e.g. Win32, Win64, Xbox 360, etc. */ 10 | #define LOGOG_FLAVOR_WINDOWS 1 11 | /** Set this compilation flag to 1 to indicate that this is a Posix-like platform, e.g. Linux, PS3, etc. */ 12 | #define LOGOG_FLAVOR_POSIX 1 13 | #endif 14 | 15 | #ifndef LOGOG_FLAVOR_WINDOWS 16 | #ifndef LOGOG_FLAVOR_POSIX 17 | 18 | /* The user hasn't told us which flavor we're running on, so we must make a guess as to which platform is valid. */ 19 | 20 | /* If this is MSVC, then it's Windows like */ 21 | #ifdef _MSC_VER 22 | #define LOGOG_FLAVOR_WINDOWS 1 23 | #endif // _MSC_VER 24 | 25 | /* gcc probably means Posix */ 26 | #ifdef __GNUC__ 27 | #define LOGOG_FLAVOR_POSIX 1 28 | #endif 29 | 30 | #ifdef __CYGWIN__ 31 | #ifdef LOGOG_UNICODE 32 | #error LOGOG_UNICODE not supported on Cygwin platform because Cygwin does not support vsnwprintf 33 | #endif 34 | /* Cygwin lacks vsnprintf support in headers but it's in the libraries. First we 35 | * need to define the constants size_t and va_list, then define vsnprintf */ 36 | #include 37 | #include 38 | #include 39 | extern int vsnprintf(char *str, size_t size, const char *format, va_list ap); 40 | #define LOGOG_USE_TR1 1 41 | #endif // __CYGWIN__ 42 | 43 | #ifdef __linux__ 44 | #define LOGOG_USE_TR1 1 45 | #endif 46 | 47 | #ifdef __APPLE__ 48 | #ifndef __clang__ 49 | #define LOGOG_USE_TR1 1 50 | #else 51 | #if !(__has_include()) 52 | #define LOGOG_USE_TR1 1 53 | #endif // !(__has_include()) 54 | #endif // __clang 55 | #endif // __APPLE__ 56 | 57 | /* Detect IBM's XL C++ */ 58 | #ifdef __IBMCPP__ 59 | // Enable use of TR1 unorderd_map etc. 60 | #define __IBMCPP_TR1__ 1 61 | #ifdef __PPC__ 62 | #define LOGOG_FLAVOR_POSIX 1 63 | #endif // __PPC__ 64 | #endif // __IBMCPP__ 65 | 66 | /* If we've recognized it already, it's a relatively modern compiler */ 67 | #if defined( LOGOG_FLAVOR_WINDOWS ) || defined( LOGOG_FLAVOR_POSIX ) 68 | #define LOGOG_HAS_UNORDERED_MAP 1 69 | #endif // defined(...) 70 | 71 | /* PS3 */ 72 | #ifdef SN_TARGET_PS3 73 | #include "proprietary/ps3.hpp" 74 | #endif 75 | 76 | #endif // LOGOG_FLAVOR_POSIX 77 | #endif // LOGOG_FLAVOR_WINDOWS 78 | 79 | #ifdef LOGOG_FLAVOR_WINDOWS 80 | /* Detect Xbox 360 */ 81 | #if _XBOX_VER >= 200 82 | #include "proprietary/xbox360.hpp" 83 | #else 84 | /* Windows has been detected. */ 85 | 86 | /** Microsoft's CRT library has its own leak detection mechanism. If you don't trust logog's 87 | ** LOGOG_LEAK_DETECTION, you can enable LOGOG_LEAK_DETECTION_WINDOWS to enable 88 | ** Microsoft's version. This really doesn't belong in platform.hpp -- feel 89 | ** free to refactor this into a Windows-specific header. 90 | **/ 91 | #ifdef LOGOG_LEAK_DETECTION_WINDOWS 92 | #define _CRTDBG_MAP_ALLOC 93 | #include 94 | #include 95 | 96 | #ifdef _DEBUG 97 | #ifndef DBG_NEW 98 | #define DBG_NEW new ( _NORMAL_BLOCK , __FILE__ , __LINE__ ) 99 | #define new DBG_NEW 100 | #endif 101 | #endif // _DEBUG 102 | #endif // LOGOG_LEAK_DETECTION_WINDOWS 103 | 104 | #include "windows.h" 105 | #endif // _XBOX_VER 106 | #endif // LOGOG_FLAVOR_WINDOWS 107 | 108 | #ifndef LOGOG_FLAVOR_WINDOWS 109 | #ifndef LOGOG_FLAVOR_POSIX 110 | #error Platform flavor not detected. Please see platform.hpp to configure this platform. 111 | #endif 112 | #endif 113 | 114 | // For the FILE type. 115 | #include 116 | // For POSIX file access. 117 | #include 118 | // For Ubuntu 11 and ptrdiff_t 119 | #include 120 | 121 | #include 122 | 123 | #if defined(LOGOG_LEAK_DETECTION) || defined(LOGOG_INTERNAL_DEBUGGING) 124 | #include 125 | #include 126 | #endif // LOGOG_LEAK_DETECTION || LOGOG_INTERNAL_DEBUGGING 127 | 128 | #ifdef LOGOG_FLAVOR_POSIX 129 | #include 130 | // for uint64_t 131 | #include 132 | #include 133 | // for strstr support 134 | #include 135 | #endif 136 | 137 | // For Unicode support 138 | #include 139 | 140 | // How this compiler prefers to represent a pragma when described within a macro 141 | #ifdef LOGOG_FLAVOR_WINDOWS 142 | #define LOGOG_MICROSOFT_PRAGMA_IN_MACRO( x ) __pragma( x ) 143 | #else // LOGOG_FLAVOR_WINDOWS 144 | #define LOGOG_MICROSOFT_PRAGMA_IN_MACRO( x ) 145 | #endif 146 | 147 | /* ----------------------------------------------------------- */ 148 | /* Here's the stuff your compiler may have a problem with... */ 149 | 150 | //! [STLTypes] 151 | 152 | /** The definition for a hash type in logog. You can replace it here with your own set compatible class if needed. */ 153 | #define LOGOG_HASH std::tr1::hash 154 | /** The definition for an unordered map type in logog. You can replace it here with your own unordered_map compatible class if needed. */ 155 | 156 | #ifdef LOGOG_HAS_UNORDERED_MAP 157 | #ifdef LOGOG_USE_TR1 158 | #include 159 | #else 160 | #include 161 | #endif // LOGOG_USE_TR1 162 | #ifdef _GLIBCXX_UNORDERED_MAP 163 | #define LOGOG_UNORDERED_MAP std::unordered_map 164 | #else // _GLIBCXX_UNORDERED_MAP 165 | #define LOGOG_UNORDERED_MAP std::tr1::unordered_map 166 | #endif // _GLIBCXX_UNORDERED_MAP 167 | #else // LOGOG_HAS_UNORDERED_MAP 168 | #include 169 | #define LOGOG_UNORDERED_MAP std::hash_map 170 | #endif // LOGOG_HAS_UNORDERED_MAP 171 | 172 | /** An internal consistency error has been detected in logog. */ 173 | #define LOGOG_INTERNAL_FAILURE abort(); 174 | 175 | /* ----------------------------------------------- */ 176 | /* Here's the stuff that's pretty standard in STL by now. */ 177 | 178 | /** The definition for a pair type in logog. You can replace it here with your own pair compatible class if needed. */ 179 | #define LOGOG_PAIR std::pair 180 | #include 181 | /** The definition for a list type in logog. You can replace it here with your own list compatible class if needed. */ 182 | #define LOGOG_LIST std::list 183 | #include 184 | /** The definition for a vector type in logog. You can replace it here with your own vector compatible class if needed. */ 185 | #define LOGOG_VECTOR std::vector 186 | #include 187 | /** The definition for a set type in logog. You can replace it here with your own set compatible class if needed. */ 188 | #define LOGOG_SET std::set 189 | /** The definition for STL "equal to" in logog. You can replace it here with your own set compatible class if needed. */ 190 | #define LOGOG_EQUAL_TO std::equal_to 191 | 192 | //! [STLTypes] 193 | 194 | /** The default port number that logog uses to communicate via TCP/UDP */ 195 | #define LOGOG_DEFAULT_PORT 9987 196 | 197 | /** We use va_list for formatting messages. */ 198 | #include 199 | 200 | 201 | 202 | #endif // __LOGOG_PLATFORM_HPP 203 | -------------------------------------------------------------------------------- /include/target.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file target.hpp Abstractions representing logging outputs. 3 | */ 4 | 5 | #ifndef __LOGOG_TARGET_HPP_ 6 | #define __LOGOG_TARGET_HPP_ 7 | 8 | namespace logog 9 | { 10 | /** A target is abstraction representing an output stream from logog. cerr, cout, and syslog, and other logging formats are supported. 11 | ** Targets do not validate their received data. Their only job is to render it on a call to Receive() to their supported target 12 | ** type. Targets should generally make sure to handle calls on multiple threads -- they should make sure to avoid overlapping 13 | ** outputs from multiple threads correctly. This base class handles this serialization in the Receive() function. 14 | ** Children of this class are expected to implement the Output() function to do the actual output. 15 | **/ 16 | class Target : public TopicSink 17 | { 18 | friend class LogBuffer; 19 | public : 20 | Target(); 21 | virtual ~Target(); 22 | 23 | /** Sets the current formatter for this target. */ 24 | void SetFormatter( Formatter &formatter ); 25 | 26 | /** Returns a reference to the current formatter for this target. */ 27 | Formatter &GetFormatter() const; 28 | 29 | /** All targets must implement the Output function. This function outputs the provided string to the 30 | * output that the target represents. 31 | * \return Zero if no error has occurred; an error code (generally propagated 32 | * from the operating system) if an error has occurred. 33 | */ 34 | virtual int Output( const LOGOG_STRING &data ) = 0; 35 | 36 | /** Receives a topic on behalf of this target. A mutex prevents race conditions from occurring when 37 | ** multiple threads attempt to write to this target at the same time. 38 | ** \return Zero if no error has occurred; an error code (generally propagated from the operating 39 | ** system) if an error has occurred. 40 | */ 41 | virtual int Receive( const Topic &topic ); 42 | 43 | /** Does this target want its formatter to null terminate its strings? */ 44 | bool GetNullTerminatesStrings() const { return m_bNullTerminatesStrings; } 45 | 46 | /** Tells this target whether to request its formatter to null terminate its strings. */ 47 | void SetNullTerminatesStrings(bool val) { m_bNullTerminatesStrings = val; } 48 | 49 | 50 | protected: 51 | /** A pointer to the formatter used for this output. */ 52 | Formatter *m_pFormatter; 53 | /** A mutex on the Receive() function. */ 54 | Mutex m_MutexReceive; 55 | /** Does this target cause its formatter to null-terminate its output strings? File and buffer outputs 56 | ** don't require null terminated strings, but line outputs do. 57 | **/ 58 | bool m_bNullTerminatesStrings; 59 | }; 60 | 61 | /** A target representing the cerr stream. */ 62 | class Cerr : public Target 63 | { 64 | virtual int Output( const LOGOG_STRING &data ); 65 | }; 66 | 67 | /** A target representing the cout stream. */ 68 | class Cout : public Target 69 | { 70 | virtual int Output( const LOGOG_STRING &data ); 71 | }; 72 | 73 | /** A target representing the debugger stream on Win32 targets. This only logs information 74 | * on Windows like platforms. 75 | */ 76 | class OutputDebug : public Target 77 | { 78 | virtual int Output( const LOGOG_STRING &data ); 79 | }; 80 | 81 | /** A LogFile renders received messages to a file. Provide the name of the file to be rendered to as 82 | * a parameter to the construction of the LogFile() object. Destroying a LogFile object will cause the 83 | * output file to be closed. LogFile objects always append to the output file; they do not delete the previous 84 | * log file. 85 | */ 86 | class LogFile : public Target 87 | { 88 | public: 89 | /** Creates a LogFile object. 90 | * \param sFileName The name of the file to be created. 91 | * Since file names do not support Unicode on most systems, there is no option to create 92 | * a filename with a LOGOG_CHAR. 93 | * \param bEnableOutputBuffering Whether to perform output buffering, or 94 | * flush after each write. 95 | */ 96 | LogFile(const char *sFileName, 97 | bool bEnableOutputBuffering = true); 98 | 99 | /** Closes the log file. */ 100 | virtual ~LogFile(); 101 | 102 | /** Opens the log file on first write. */ 103 | virtual int Open(); 104 | 105 | /** This function makes a guess as to the correct BOM for this file, and attempts 106 | ** to write it into the file. It does this by considering the size of LOGOG_CHAR 107 | ** as well as considering the current endianness of this system. This guess 108 | ** may be incorrect. 109 | **/ 110 | virtual void WriteUnicodeBOM(); 111 | 112 | /** Writes the message to the log file. */ 113 | virtual int Output( const LOGOG_STRING &data ); 114 | 115 | /** Should a Unicode BOM be written to the beginning of this log file, if the log file 116 | * was previously empty? By default a BOM is written to a log file if LOGOG_UNICODE 117 | * is enabled. */ 118 | bool m_bWriteUnicodeBOM; 119 | 120 | protected: 121 | char *m_pFileName; 122 | bool m_bFirstTime; 123 | bool m_bOpenFailed; 124 | FILE *m_pFile; 125 | bool m_bEnableOutputBuffering; 126 | 127 | /** Does the actual fwrite to the file. Call Output() instead to handle error conditions better. */ 128 | virtual int InternalOutput( size_t nSize, const LOGOG_CHAR *pData ); 129 | 130 | private: 131 | LogFile(); 132 | }; 133 | 134 | /** A buffering target. Stores up to a fixed buffer size of output and then renders that output to another 135 | * target. Can be used for buffering log output in memory and then storing it to a log file upon program completion. 136 | * To use, create another target (such as a LogFile) and then create a LogBuffer, providing the other target 137 | * as a parameter to the creation function. 138 | */ 139 | class LogBuffer : public Target 140 | { 141 | public: 142 | LogBuffer( Target *pTarget = NULL, 143 | size_t s = LOGOG_DEFAULT_LOG_BUFFER_SIZE ); 144 | virtual ~LogBuffer(); 145 | 146 | /** Changes the current rendering target. NOTE: This function does no locking on either the target or 147 | * this object. Program accordingly. 148 | */ 149 | virtual void SetTarget( Target &t ); 150 | 151 | /** Inserts a range of LOGOG_CHAR objects into this buffer. The characters should consist of a null-terminated 152 | * string of length size. Providing anything else as input creates undefined behavior. 153 | */ 154 | virtual int Insert( const LOGOG_CHAR *pChars, size_t size ); 155 | 156 | /** Dumps the current contents of the buffer to the output target. */ 157 | virtual int Dump(); 158 | 159 | virtual int Output( const LOGOG_STRING &data ); 160 | 161 | protected: 162 | virtual void Allocate( size_t size ); 163 | 164 | virtual void Deallocate(); 165 | 166 | /** The non-changing pointer to the basic buffer. */ 167 | LOGOG_CHAR *m_pStart; 168 | /** The current write offset into the buffer. */ 169 | LOGOG_CHAR *m_pCurrent; 170 | /** The position in the buffer after which no data may be written. */ 171 | LOGOG_CHAR *m_pEnd; 172 | /** The size of the buffer in LOGOG_CHAR primitives. */ 173 | size_t m_nSize; 174 | /** A pointer to the target to which the buffer will be rendered upon calling Dump(). */ 175 | Target *m_pOutputTarget; 176 | }; 177 | 178 | } 179 | 180 | #endif // __LOGOG_TARGET_HPP_ 181 | -------------------------------------------------------------------------------- /src/node.cpp: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * \file node.cpp 4 | */ 5 | 6 | #include "logog.hpp" 7 | 8 | namespace logog { 9 | 10 | LockableNodesType & LockableNodesType::operator = (const LockableNodesType &other) 11 | { 12 | /* This function is only used at shutdown. */ 13 | LockableNodesType::const_iterator it; 14 | 15 | it = other.begin(); 16 | while ( it != other.end()) 17 | { 18 | this->insert( *it ); 19 | ++it; 20 | } 21 | 22 | return *this; 23 | } 24 | 25 | LockableNodesType &GetStaticNodes( void ** pvLocation ) 26 | { 27 | if ( *pvLocation == NULL ) 28 | *pvLocation = new LockableNodesType(); 29 | 30 | return *(( LockableNodesType *)( *pvLocation )); 31 | 32 | } 33 | 34 | LockableNodesType &AllNodes() 35 | { 36 | return GetStaticNodes( &(Static().s_pAllNodes) ); 37 | } 38 | 39 | LockableNodesType &AllSubscriberNodes() 40 | { 41 | return GetStaticNodes( &(Static().s_pAllSubscriberNodes ) ); 42 | } 43 | 44 | LockableNodesType &AllFilters() 45 | { 46 | return GetStaticNodes( &(Static().s_pAllFilterNodes ) ); 47 | } 48 | 49 | LockableNodesType &AllTargets() 50 | { 51 | return GetStaticNodes( &(Static().s_pAllTargets ) ); 52 | } 53 | 54 | Node::Node() 55 | { 56 | AllNodes().insert( this ); 57 | } 58 | 59 | Node::~Node() 60 | { 61 | Clear(); 62 | AllNodes().erase( this ); 63 | } 64 | 65 | void Node::Initialize() 66 | { 67 | if ( CanSubscribe() ) 68 | { 69 | LockableNodesType *pSubscriberNodes = &AllSubscriberNodes(); 70 | 71 | { 72 | ScopedLock sl( *pSubscriberNodes ); 73 | pSubscriberNodes->insert( this ); 74 | } 75 | 76 | /* This branch is taken iff this node can both subscribe and publish */ 77 | if ( CanPublish() ) 78 | { 79 | LockableNodesType *pFilterNodes = &AllFilters(); 80 | { 81 | ScopedLock sl( *pFilterNodes ); 82 | pFilterNodes->insert( this ); 83 | } 84 | } 85 | } 86 | } 87 | 88 | bool Node::CanPublish() const 89 | { 90 | return true; 91 | } 92 | 93 | bool Node::CanSubscribe() const 94 | { 95 | return true; 96 | } 97 | 98 | bool Node::CanSubscribeTo( const Node & ) 99 | { 100 | return CanSubscribe(); 101 | } 102 | 103 | bool Node::IsTopic() const 104 | { 105 | return false; 106 | } 107 | 108 | bool Node::PublishTo( Node &subscriber ) 109 | { 110 | #ifdef LOGOG_INTERNAL_DEBUGGING 111 | if ( &subscriber == this ) 112 | LOGOG_INTERNAL_FAILURE; 113 | #endif 114 | bool bWasInserted; 115 | 116 | { 117 | ScopedLock sl( m_Subscribers ); 118 | bWasInserted = ( m_Subscribers.insert( &subscriber ) ).second; 119 | } 120 | 121 | if ( bWasInserted ) 122 | subscriber.SubscribeTo( *this ); 123 | 124 | return bWasInserted; 125 | } 126 | 127 | bool Node::PublishToMultiple( LockableNodesType &nodes ) 128 | { 129 | LockableNodesType::iterator it; 130 | 131 | bool bWasPublished = false; 132 | 133 | nodes.MutexLock(); 134 | it = nodes.begin(); 135 | 136 | while ( it != nodes.end() ) 137 | { 138 | if ( PublishTo( **it ) == true ) 139 | bWasPublished = true; 140 | 141 | ++it; 142 | } 143 | 144 | nodes.MutexUnlock(); 145 | 146 | return bWasPublished; 147 | } 148 | 149 | bool Node::UnpublishTo( Node &subscriber ) 150 | { 151 | #ifdef LOGOG_INTERNAL_DEBUGGING 152 | if ( &subscriber == this ) 153 | LOGOG_INTERNAL_FAILURE; 154 | #endif 155 | bool bWasRemoved = false; 156 | 157 | { 158 | ScopedLock sl( m_Subscribers ); 159 | NodesType::iterator it; 160 | 161 | if ( ( it = m_Subscribers.find( &subscriber) ) != m_Subscribers.end() ) 162 | { 163 | bWasRemoved = true; 164 | m_Subscribers.erase( it ); 165 | } 166 | } 167 | 168 | if ( bWasRemoved ) 169 | subscriber.UnsubscribeTo( *this ); 170 | 171 | return bWasRemoved; 172 | } 173 | 174 | bool Node::UnpublishToMultiple( LockableNodesType &nodes ) 175 | { 176 | LockableNodesType::iterator it; 177 | 178 | bool bWasUnpublished = false; 179 | 180 | nodes.MutexLock(); 181 | it = nodes.begin(); 182 | 183 | while ( it != nodes.end() ) 184 | { 185 | if ( UnpublishTo( **it ) == true ) 186 | bWasUnpublished = true; 187 | 188 | ++it; 189 | } 190 | 191 | nodes.MutexUnlock(); 192 | 193 | return bWasUnpublished; 194 | } 195 | 196 | bool Node::SubscribeTo( Node &publisher ) 197 | { 198 | #ifdef LOGOG_INTERNAL_DEBUGGING 199 | if ( &publisher == this ) 200 | LOGOG_INTERNAL_FAILURE; 201 | #endif 202 | bool bWasInserted; 203 | 204 | { 205 | ScopedLock sl( m_Publishers ); 206 | bWasInserted = ( m_Publishers.insert( &publisher ) ).second; 207 | } 208 | 209 | if ( bWasInserted ) 210 | publisher.PublishTo( *this ); 211 | 212 | return bWasInserted; 213 | } 214 | 215 | bool Node::SubscribeToMultiple( LockableNodesType &nodes ) 216 | { 217 | LockableNodesType::iterator it; 218 | 219 | bool bWasSubscribed = false; 220 | 221 | nodes.MutexLock(); 222 | it = nodes.begin(); 223 | 224 | while ( it != nodes.end() ) 225 | { 226 | if ( SubscribeTo( **it ) == true ) 227 | bWasSubscribed = true; 228 | 229 | ++it; 230 | } 231 | 232 | nodes.MutexUnlock(); 233 | 234 | return bWasSubscribed; 235 | } 236 | 237 | bool Node::UnsubscribeTo( Node &publisher ) 238 | { 239 | #ifdef LOGOG_INTERNAL_DEBUGGING 240 | if ( &publisher == this ) 241 | LOGOG_INTERNAL_FAILURE; 242 | #endif 243 | bool bWasRemoved = false; 244 | 245 | { 246 | ScopedLock sl( m_Publishers ); 247 | NodesType::iterator it; 248 | 249 | if ( ( it = m_Publishers.find( &publisher ) ) != m_Publishers.end() ) 250 | { 251 | bWasRemoved = true; 252 | m_Publishers.erase( it ); 253 | } 254 | } 255 | 256 | if ( bWasRemoved ) 257 | publisher.UnpublishTo( *this ); 258 | 259 | return bWasRemoved; 260 | } 261 | 262 | bool Node::UnsubscribeToMultiple( LockableNodesType &nodes ) 263 | { 264 | LockableNodesType::iterator it; 265 | 266 | bool bWasUnsubscribed = false; 267 | 268 | nodes.MutexLock(); 269 | it = nodes.begin(); 270 | 271 | while ( it != nodes.end() ) 272 | { 273 | if ( UnsubscribeTo( **it ) == true ) 274 | bWasUnsubscribed = true; 275 | 276 | ++it; 277 | } 278 | 279 | nodes.MutexUnlock(); 280 | 281 | return bWasUnsubscribed; 282 | } 283 | 284 | void Node::Clear() 285 | { 286 | { 287 | ScopedLock sl( m_Publishers ); 288 | m_Publishers.clear(); 289 | } 290 | { 291 | ScopedLock sl( m_Subscribers ); 292 | m_Publishers.clear(); 293 | } 294 | } 295 | 296 | 297 | void DestroyNodesList( void **pvList ) 298 | { 299 | LockableNodesType **ppNodesList = (LockableNodesType **)pvList; 300 | 301 | if ( *ppNodesList == NULL ) 302 | return; 303 | 304 | (*ppNodesList)->clear(); 305 | delete *ppNodesList; 306 | *ppNodesList = NULL; 307 | } 308 | 309 | void DestroyAllNodes() 310 | { 311 | Statics *pStatics = &Static(); 312 | 313 | LockableNodesType *pAllNodes = ( LockableNodesType *)pStatics->s_pAllNodes; 314 | 315 | if ( pAllNodes == NULL ) 316 | return; 317 | 318 | /** Destroy all the node groups, but don't destroy their contents -- we'll do that as the next step. */ 319 | DestroyNodesList( &(pStatics->s_pAllSubscriberNodes )); 320 | DestroyNodesList( &(pStatics->s_pAllFilterNodes )); 321 | DestroyNodesList( &(pStatics->s_pAllTargets )); 322 | 323 | /* We have to copy the AllNodes because destroying each node will remove it from AllNodes. Fortunately 324 | * this only happens at shutdown, so we don't have to worry about efficiency. 325 | */ 326 | LockableNodesType nodes = *pAllNodes; 327 | 328 | LockableNodesType::iterator it; 329 | 330 | it = nodes.begin(); 331 | 332 | while ( it != nodes.end() ) 333 | { 334 | delete *it; 335 | ++it; 336 | } 337 | 338 | nodes.clear(); 339 | 340 | #ifdef LOGOG_INTERNAL_DEBUGGING 341 | if ( pAllNodes->size() != 0 ) 342 | cout << "Not all nodes were deleted at shutdown -- memory leak may have occurred" << endl; 343 | #endif 344 | 345 | pAllNodes->clear(); // just in case 346 | delete pAllNodes; 347 | pStatics->s_pAllNodes = NULL; 348 | } 349 | 350 | } 351 | 352 | -------------------------------------------------------------------------------- /src/formatter.cpp: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * \file formatter.cpp 4 | */ 5 | 6 | #include "logog.hpp" 7 | 8 | namespace logog { 9 | 10 | Formatter::Formatter() : 11 | m_bShowTimeOfDay( false ) 12 | { 13 | m_sMessageBuffer.reserve( LOGOG_FORMATTER_MAX_LENGTH ); 14 | m_sIntBuffer.reserve_for_int(); 15 | #ifndef LOGOG_UNICODE 16 | #ifdef LOGOG_FLAVOR_WINDOWS 17 | #pragma warning( push ) 18 | #pragma warning( disable : 4996 ) 19 | #endif // LOGOG_FLAVOR_WINDOWS 20 | /* Microsoft prefers strcpy_s, but gcc does not have it*/ 21 | strcpy( m_TimeOfDayFormat, LOGOG_DEFAULT_TIME_FORMAT ); 22 | #ifdef LOGOG_FLAVOR_WINDOWS 23 | #pragma warning( pop ) 24 | #endif // LOGOG_FLAVOR_WINDOWS 25 | #endif // LOGOG_UNICODE 26 | } 27 | 28 | void Formatter::RenderTimeOfDay() 29 | { 30 | if ( m_bShowTimeOfDay ) 31 | { 32 | #ifndef LOGOG_UNICODE 33 | //Time stamps are always ascii, even if LOGOG_UNICODE is 34 | //defined. 35 | TimeStamp stamp; 36 | m_sMessageBuffer.append( stamp.Get(m_TimeOfDayFormat) ); 37 | m_sMessageBuffer.append(": "); 38 | #endif 39 | } 40 | } 41 | 42 | const LOGOG_CHAR * Formatter::ErrorDescription( const LOGOG_LEVEL_TYPE level ) 43 | { 44 | if ( level <= LOGOG_LEVEL_NONE ) 45 | return LOGOG_CONST_STRING("none"); 46 | 47 | if ( level <= LOGOG_LEVEL_EMERGENCY ) 48 | return LOGOG_CONST_STRING("emergency"); 49 | 50 | if ( level <= LOGOG_LEVEL_ALERT ) 51 | return LOGOG_CONST_STRING("alert"); 52 | 53 | if ( level <= LOGOG_LEVEL_CRITICAL ) 54 | return LOGOG_CONST_STRING("critical"); 55 | 56 | if ( level <= LOGOG_LEVEL_ERROR ) 57 | return LOGOG_CONST_STRING("error"); 58 | 59 | if ( level <= LOGOG_LEVEL_WARN ) 60 | return LOGOG_CONST_STRING("warning"); 61 | 62 | if ( level <= LOGOG_LEVEL_WARN1 ) 63 | return LOGOG_CONST_STRING("warning1"); 64 | 65 | if ( level <= LOGOG_LEVEL_WARN2 ) 66 | return LOGOG_CONST_STRING("warning2"); 67 | 68 | if ( level <= LOGOG_LEVEL_WARN3 ) 69 | return LOGOG_CONST_STRING("warning3"); 70 | 71 | if ( level <= LOGOG_LEVEL_INFO ) 72 | return LOGOG_CONST_STRING("info"); 73 | 74 | if ( level <= LOGOG_LEVEL_DEBUG ) 75 | return LOGOG_CONST_STRING("debug"); 76 | 77 | return LOGOG_CONST_STRING("unknown"); 78 | } 79 | 80 | bool Formatter::GetShowTimeOfDay() const 81 | { 82 | return m_bShowTimeOfDay; 83 | } 84 | 85 | void Formatter::SetShowTimeOfDay( bool val ) 86 | { 87 | m_bShowTimeOfDay = val; 88 | } 89 | 90 | #ifndef LOGOG_UNICODE 91 | void Formatter::SetTimeOfDayFormat( const char *fmt ) 92 | { 93 | #ifdef LOGOG_FLAVOR_WINDOWS 94 | #pragma warning( push ) 95 | #pragma warning( disable : 4996 ) 96 | #endif // LOGOG_FLAVOR_WINDOWS 97 | /* Microsoft prefers strcpy_s, but gcc does not have it*/ 98 | strcpy( m_TimeOfDayFormat, fmt ); 99 | #ifdef LOGOG_FLAVOR_WINDOWS 100 | #pragma warning( pop ) 101 | #endif // LOGOG_FLAVOR_WINDOWS 102 | } 103 | #endif 104 | 105 | TOPIC_FLAGS Formatter::GetTopicFlags( const Topic &topic ) 106 | { 107 | return topic.GetTopicFlags(); 108 | } 109 | 110 | LOGOG_STRING &FormatterGCC::Format( const Topic &topic, const Target &target ) 111 | { 112 | TOPIC_FLAGS flags; 113 | flags = GetTopicFlags( topic ); 114 | 115 | m_sMessageBuffer.clear(); 116 | 117 | if ( flags & TOPIC_FILE_NAME_FLAG ) 118 | { 119 | m_sMessageBuffer.append( topic.FileName() ); 120 | m_sMessageBuffer.append( ':' ); 121 | } 122 | 123 | if ( flags & TOPIC_LINE_NUMBER_FLAG ) 124 | { 125 | m_sIntBuffer.assign( topic.LineNumber() ); 126 | m_sMessageBuffer.append( m_sIntBuffer ); 127 | 128 | m_sMessageBuffer.append( LOGOG_CONST_STRING(": ")); 129 | } 130 | 131 | RenderTimeOfDay(); 132 | 133 | if ( flags & TOPIC_LEVEL_FLAG ) 134 | { 135 | m_sMessageBuffer.append( ErrorDescription( topic.Level())); 136 | m_sMessageBuffer.append( LOGOG_CONST_STRING(": ")); 137 | } 138 | 139 | if ( flags & TOPIC_GROUP_FLAG ) 140 | { 141 | m_sMessageBuffer.append( LOGOG_CONST_STRING("{") ); 142 | m_sMessageBuffer.append( topic.Group() ); 143 | m_sMessageBuffer.append( LOGOG_CONST_STRING("} ") ); 144 | } 145 | 146 | if ( flags & TOPIC_CATEGORY_FLAG ) 147 | { 148 | m_sMessageBuffer.append( LOGOG_CONST_STRING("[")); 149 | m_sMessageBuffer.append( topic.Category() ); 150 | m_sMessageBuffer.append( LOGOG_CONST_STRING("] ")); 151 | } 152 | 153 | if ( flags & TOPIC_MESSAGE_FLAG ) 154 | { 155 | m_sMessageBuffer.append( topic.Message() ); 156 | m_sMessageBuffer.append( (LOGOG_CHAR)'\n' ); 157 | } 158 | 159 | if ( target.GetNullTerminatesStrings() ) 160 | m_sMessageBuffer.append( (LOGOG_CHAR)NULL ); 161 | 162 | return m_sMessageBuffer; 163 | } 164 | 165 | 166 | LOGOG_STRING &FormatterMSVC::Format( const Topic &topic, const Target &target ) 167 | { 168 | m_sMessageBuffer.clear(); 169 | 170 | TOPIC_FLAGS flags; 171 | flags = GetTopicFlags( topic ); 172 | 173 | if ( flags & TOPIC_FILE_NAME_FLAG ) 174 | { 175 | m_sMessageBuffer.append( topic.FileName() ); 176 | m_sMessageBuffer.append( '(' ); 177 | } 178 | 179 | if ( flags & TOPIC_LINE_NUMBER_FLAG ) 180 | { 181 | m_sIntBuffer.assign( topic.LineNumber() ); 182 | m_sMessageBuffer.append( m_sIntBuffer ); 183 | 184 | m_sMessageBuffer.append( LOGOG_CONST_STRING(") : ") ); 185 | } 186 | 187 | RenderTimeOfDay(); 188 | 189 | if ( flags & TOPIC_LEVEL_FLAG ) 190 | { 191 | m_sMessageBuffer.append( ErrorDescription( topic.Level() ) ); 192 | m_sMessageBuffer.append( LOGOG_CONST_STRING(": ")); 193 | } 194 | 195 | if ( flags & TOPIC_GROUP_FLAG ) 196 | { 197 | m_sMessageBuffer.append( LOGOG_CONST_STRING("{")); 198 | m_sMessageBuffer.append( topic.Group() ); 199 | m_sMessageBuffer.append( LOGOG_CONST_STRING("} ")); 200 | } 201 | 202 | if ( flags & TOPIC_CATEGORY_FLAG ) 203 | { 204 | m_sMessageBuffer.append( LOGOG_CONST_STRING("[")); 205 | m_sMessageBuffer.append( topic.Category() ); 206 | m_sMessageBuffer.append( LOGOG_CONST_STRING("] ")); 207 | } 208 | 209 | if ( flags & TOPIC_MESSAGE_FLAG ) 210 | { 211 | m_sMessageBuffer.append( topic.Message() ); 212 | #ifdef LOGOG_FLAVOR_WINDOWS 213 | m_sMessageBuffer.append( LOGOG_CONST_STRING("\r\n") ); 214 | #else // LOGOG_FLAVOR_WINDOWS 215 | m_sMessageBuffer.append( LOGOG_CONST_STRING("\n") ); 216 | #endif // LOGOG_FLAVOR_WINDOWS 217 | } 218 | 219 | if ( target.GetNullTerminatesStrings() ) 220 | m_sMessageBuffer.append( LOGOG_CHAR( NULL ) ); 221 | 222 | return m_sMessageBuffer; 223 | } 224 | 225 | Formatter &GetDefaultFormatter() 226 | { 227 | Statics *pStatic = &Static(); 228 | 229 | if ( pStatic->s_pDefaultFormatter == NULL ) 230 | { 231 | #ifdef LOGOG_FLAVOR_WINDOWS 232 | pStatic->s_pDefaultFormatter = new FormatterMSVC(); 233 | #else 234 | pStatic->s_pDefaultFormatter = new FormatterGCC(); 235 | #endif 236 | } 237 | 238 | return *( pStatic->s_pDefaultFormatter ); 239 | } 240 | 241 | void DestroyDefaultFormatter() 242 | { 243 | Statics *pStatic = &Static(); 244 | Formatter *pDefaultFormatter = pStatic->s_pDefaultFormatter; 245 | 246 | if ( pDefaultFormatter != NULL ) 247 | delete pDefaultFormatter; 248 | 249 | pStatic->s_pDefaultFormatter = NULL; 250 | } 251 | 252 | const char * TimeStamp::Get(const char* fmt) 253 | { 254 | time_t tRawTime; 255 | struct tm * tmInfo; 256 | 257 | time ( &tRawTime ); 258 | 259 | #ifdef LOGOG_FLAVOR_WINDOWS 260 | #pragma warning( push ) 261 | #pragma warning( disable : 4996 ) 262 | #endif // LOGOG_FLAVOR_WINDOWS 263 | /* Microsoft is afraid of this function; I'm not sure this warning is sensible */ 264 | tmInfo = localtime ( &tRawTime ); 265 | #ifdef LOGOG_FLAVOR_WINDOWS 266 | #pragma warning( pop ) 267 | #endif // LOGOG_FLAVOR_WINDOWS 268 | 269 | cTimeString[ 0 ] = '\0'; 270 | if ( tmInfo != NULL ) 271 | strftime (cTimeString, LOGOG_TIME_STRING_MAX, fmt, tmInfo); 272 | 273 | return cTimeString; 274 | } 275 | 276 | } 277 | 278 | -------------------------------------------------------------------------------- /include/object.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file object.hpp Base class for all allocated logog objects. 3 | */ 4 | 5 | #ifndef __LOGOG_OBJECT_HPP__ 6 | #define __LOGOG_OBJECT_HPP__ 7 | 8 | namespace logog 9 | { 10 | 11 | #ifdef LOGOG_LEAK_DETECTION 12 | /** Iteration over an unordered map is slow, but we only do it at shutdown time. Access to a lookup or insert 13 | ** is very fast. Additionally, we do not allocate leak detection records from our own heap; it saves us from 14 | ** doing a recursive allocation. 15 | **/ 16 | typedef void *PointerType; 17 | 18 | /** A type describing the currently outstanding memory allocations. Note that this type does not inherit from 19 | ** Object, because to do so would create an infinite recursion when memory is allocated. 20 | **/ 21 | typedef LOGOG_UNORDERED_MAP< PointerType, size_t > AllocationsType; 22 | 23 | /** All currently outstanding memory allocations, including their size. */ 24 | extern AllocationsType s_Allocations; 25 | 26 | /** A global function to lock the global allocations mutex. We must do this as a static 27 | ** void because Mutexes depend on Objects. This should probably belong in a class, but then 28 | ** we get recursive class definitions. 29 | ** */ 30 | extern void LockAllocationsMutex(); 31 | 32 | /** A global function to unlock the global allocations mutex. We must do this as a static 33 | ** void because Mutexes depend on Objects. */ 34 | extern void UnlockAllocationsMutex(); 35 | #endif // LOGOG_LEAK_DETECTION 36 | 37 | #ifdef new 38 | #define LOGOG_PREVIOUS_DEFINITION_OF_NEW new 39 | #undef new 40 | #endif 41 | #ifdef delete 42 | #define LOGOG_PREVIOUS_DEFINITION_OF_DELETE delete 43 | #undef delete 44 | #endif //delete 45 | 46 | /** Base class for all objects allocated with logog. */ 47 | class Object 48 | { 49 | public: 50 | Object(); 51 | /* Some builds complain about ~Object() being virtual... sorry lint :( */ 52 | virtual ~Object(); 53 | /** Initializes an object of size new. */ 54 | void *operator new( size_t nSize ); 55 | /** Initializes an array of size new. */ 56 | void *operator new[](size_t nSize); 57 | 58 | /* There's a wonderful behavior in Windows MFC in debug builds that causes it 59 | * to attempt to redefine NEW with a macro if we're compiling in debug mode 60 | * and we're using Gdiplus as well. In that case, Windows attempts to redefine 61 | * new with a macro for everything that has a new. This wonderful behavior 62 | * is worked around here. See http://support.microsoft.com/kb/317799/EN-US/ 63 | * and http://social.msdn.microsoft.com/Forums/en/vcgeneral/thread/0df13145-670e-4070-b0a1-61794b20dff7 64 | * for more exciting information. 65 | */ 66 | #ifdef _DEBUG 67 | #ifdef LOGOG_FLAVOR_WINDOWS 68 | void* operator new(size_t nSize, LPCSTR lpszFileName, int nLine); 69 | void* operator new[](size_t nSize, LPCSTR lpszFileName, int nLine); 70 | void operator delete(void* ptr, LPCSTR lpszFileName, int nLine); 71 | void operator delete[](void* ptr, LPCSTR lpszFileName, int nLine); 72 | #endif // LOGOG_FLAVOR_WINDOWS 73 | #endif // _DEBUG 74 | 75 | /** Deletes an object pointed to by ptr. */ 76 | void operator delete( void *ptr ); 77 | /** Deletes an object array pointed to by ptr. */ 78 | void operator delete[]( void *ptr ); 79 | 80 | /** Allocates nSize bytes of memory. You must call logog::Initialize() before calling this function. 81 | * \sa Initialize() 82 | */ 83 | static void *Allocate( size_t nSize ); 84 | 85 | /** Deallocate a pointer previously acquired by Allocate(). */ 86 | static void Deallocate( void *ptr ); 87 | }; 88 | 89 | #ifdef LOGOG_PREVIOUS_DEFINITION_OF_NEW 90 | #define new LOGOG_PREVIOUS_DEFINITION_OF_NEW 91 | #endif 92 | 93 | #ifdef LOGOG_PREVIOUS_DEFINITION_OF_DELETE 94 | #define delete GA_PREVIOUS_DEFINITION_OF_DELETE 95 | #endif 96 | 97 | /** An STL-compatible allocator which redirects all memory requests to the logog allocator. Used for all STL-like classes within logog. */ 98 | template 99 | class Allocator 100 | { 101 | public: 102 | /** Memory allocation size type. */ 103 | typedef size_t size_type; 104 | /** Memory allocation comparison type. */ 105 | typedef ptrdiff_t difference_type; 106 | /** A pointer to T type. */ 107 | typedef T *pointer; 108 | /** A const pointer to T type. */ 109 | typedef const T *const_pointer; 110 | /** A reference to T type. */ 111 | typedef T &reference; 112 | /** A const reference to T type. */ 113 | typedef const T &const_reference; 114 | /** A value type (T itself). */ 115 | typedef T value_type; 116 | 117 | Allocator() {} 118 | /** Not implemented here -- required by the STL standard though. */ 119 | Allocator( const Allocator & ) {} 120 | 121 | /** Allocate and return n value_types of memory through this allocator. Requires that logog::Initialize() has been called. */ 122 | pointer allocate( size_type n, const void * = 0 ) 123 | { 124 | T *t = ( T * ) Object::Allocate( n * sizeof( value_type ) ); 125 | return t; 126 | } 127 | 128 | /** Frees memory previously allocated by allocate(). */ 129 | void deallocate( void *p, size_type ) 130 | { 131 | if ( p ) 132 | { 133 | Object::Deallocate( p ); 134 | } 135 | } 136 | 137 | /** Returns the address of a reference to T. */ 138 | pointer address( reference x ) const 139 | { 140 | return &x; 141 | } 142 | /** Returns the address of a const reference to T. */ 143 | const_pointer address( const_reference x ) const 144 | { 145 | return &x; 146 | } 147 | /** STL required override for = operator. */ 148 | Allocator& operator=( const Allocator & ) 149 | { 150 | return *this; 151 | } 152 | /** Constructs a new T at location p with value val. */ 153 | void construct( pointer p, const T &val ) 154 | { 155 | new(( T * ) p ) T( val ); 156 | } 157 | /** Destroys a T at location p. */ 158 | void destroy( pointer p ) 159 | { 160 | #ifdef LOGOG_FLAVOR_WINDOWS 161 | // MSVC tends to complain unless we reference this pointer here. 162 | p; 163 | #endif // LOGOG_FLAVOR_WINDOWS 164 | p->~T(); 165 | } 166 | 167 | /** The largest size of an object that can be allocated with this allocator. */ 168 | size_type max_size() const 169 | { 170 | return size_t( -1 ); 171 | } 172 | 173 | /** Rebinding to permit allocations of unknown types. Part of std::allocator definition. 174 | * \param other The other "unknown" type to be permitted access to this allocator */ 175 | template 176 | struct rebind 177 | { 178 | /** The "other" class that will use this allocator for its allocation. */ 179 | typedef Allocator other; 180 | }; 181 | 182 | /** Required by STL -- unused here. */ 183 | template 184 | Allocator( const Allocator& ) {} 185 | 186 | /** Permit this allocator to be used for assignment in other classes */ 187 | template 188 | Allocator &operator=( const Allocator& ) 189 | { 190 | return *this; 191 | } 192 | }; 193 | 194 | /* All specializations of this allocator are interchangeable. */ 195 | template 196 | bool operator== ( const Allocator&, 197 | const Allocator& ) 198 | { 199 | return true; 200 | } 201 | template 202 | bool operator!= ( const Allocator &, 203 | const Allocator& ) 204 | { 205 | return false; 206 | } 207 | 208 | // 209 | 210 | /** Returns the current number of outstanding memory allocations in logog. Returns -1 iff LOGOG_LEAK_DETECTION 211 | ** has not been defined at compile time. 212 | **/ 213 | extern int MemoryAllocations(); 214 | 215 | /** Sends a report to cout describing the current memory allocations that exist. Returns the outstanding number of 216 | ** memory allocations, or -1 iff LOGOG_LEAK_DETECTION is defined. 217 | **/ 218 | extern int ReportMemoryAllocations(); 219 | 220 | } 221 | #endif // __LOGOG_OBJECT_HPP 222 | -------------------------------------------------------------------------------- /src/target.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * \file target.cpp 3 | */ 4 | 5 | #include "logog.hpp" 6 | 7 | #include 8 | 9 | namespace logog { 10 | 11 | Target::Target() : 12 | m_bNullTerminatesStrings( true ) 13 | { 14 | SetFormatter( GetDefaultFormatter() ); 15 | LockableNodesType *pAllTargets = &AllTargets(); 16 | 17 | { 18 | ScopedLock sl( *pAllTargets ); 19 | pAllTargets->insert( this ); 20 | } 21 | 22 | SubscribeToMultiple( AllFilters() ); 23 | } 24 | 25 | 26 | Target::~Target() 27 | { 28 | LockableNodesType *pAllTargets = &AllTargets(); 29 | 30 | UnsubscribeToMultiple( AllFilters() ); 31 | 32 | { 33 | ScopedLock sl( *pAllTargets ); 34 | pAllTargets->erase( this ); 35 | } 36 | } 37 | 38 | void Target::SetFormatter( Formatter &formatter ) 39 | { 40 | m_pFormatter = &formatter; 41 | } 42 | 43 | Formatter & Target::GetFormatter() const 44 | { 45 | return *m_pFormatter; 46 | } 47 | 48 | int Target::Receive( const Topic &topic ) 49 | { 50 | ScopedLock sl( m_MutexReceive ); 51 | return Output( m_pFormatter->Format( topic, *this ) ); 52 | } 53 | 54 | int Cerr::Output( const LOGOG_STRING &data ) 55 | { 56 | LOGOG_CERR << (const LOGOG_CHAR *)data; 57 | 58 | return 0; 59 | } 60 | //! [Cout] 61 | int Cout::Output( const LOGOG_STRING &data ) 62 | { 63 | LOGOG_COUT << (const LOGOG_CHAR *)data; 64 | 65 | return 0; 66 | } 67 | //! [Cout] 68 | 69 | int OutputDebug::Output( const LOGOG_STRING &data ) 70 | { 71 | #ifdef LOGOG_FLAVOR_WINDOWS 72 | #ifdef LOGOG_UNICODE 73 | OutputDebugStringW( (const LOGOG_CHAR *)data ); 74 | #else 75 | OutputDebugStringA( (const LOGOG_CHAR *)data ); 76 | #endif // LOGOG_UNICODE 77 | #endif // LOGOG_FLAVOR_WINDOWS 78 | return 0; 79 | } 80 | 81 | LogFile::LogFile(const char *sFileName, bool bEnableOutputBuffering) : 82 | m_bFirstTime( true ), 83 | m_bOpenFailed( false ), 84 | m_pFile( NULL ), 85 | m_bEnableOutputBuffering( bEnableOutputBuffering ) 86 | { 87 | m_bNullTerminatesStrings = false; 88 | 89 | #ifdef LOGOG_UNICODE 90 | m_bWriteUnicodeBOM = true; 91 | #else // LOGOG_UNICODE 92 | m_bWriteUnicodeBOM = false; 93 | #endif // LOGOG_UNICODE 94 | 95 | size_t nNameLength = 0; 96 | 97 | const char *sNameCount = sFileName; 98 | while ( *sNameCount++ != '\0' ) 99 | nNameLength++; 100 | 101 | // add one for trailing null 102 | nNameLength++; 103 | 104 | m_pFileName = (char *)Object::Allocate( nNameLength ); 105 | 106 | char *m_pOut = m_pFileName; 107 | while ( ( *m_pOut++ = *sFileName++) != '\0' ) 108 | ; 109 | } 110 | 111 | LogFile::~LogFile() 112 | { 113 | if ( m_pFile ) 114 | fclose( m_pFile ); 115 | 116 | Object::Deallocate( m_pFileName ); 117 | } 118 | 119 | int LogFile::Open() 120 | { 121 | 122 | #ifdef LOGOG_UNICODE 123 | bool bFileAlreadyExists = false; 124 | #endif // LOGOG_UNICODE 125 | 126 | FILE *fpTest; 127 | 128 | #ifdef LOGOG_FLAVOR_WINDOWS 129 | int nError; // only exists in Windows build 130 | nError = fopen_s( &fpTest, m_pFileName, "r"); // ignore the return code for now 131 | #else // LOGOG_FLAVOR_WINDOWS 132 | fpTest = fopen( m_pFileName, "r"); 133 | #endif // LOGOG_FLAVOR_WINDOWS 134 | 135 | if ( fpTest != NULL ) 136 | { 137 | fclose( fpTest ); 138 | 139 | #ifdef LOGOG_UNICODE 140 | bFileAlreadyExists = true; 141 | #endif // LOGOG_UNICODE 142 | } 143 | 144 | /** Windows tries to be clever and help us with converting line feeds 145 | ** to carriage returns when writing a text file. This causes problems 146 | ** when writing a Unicode file as Windows helpfully inserts a single-byte 147 | ** 0x0D between the return and line feed on write. So we open and operate 148 | ** the output in binary mode only. 149 | **/ 150 | #ifdef LOGOG_FLAVOR_WINDOWS 151 | #ifdef LOGOG_UNICODE 152 | const char *openMode = "ab, ccs=UNICODE"; 153 | #else // LOGOG_UNICODE 154 | const char *openMode = "ab"; 155 | #endif // LOGOG_UNICODE 156 | m_pFile = _fsopen( m_pFileName, openMode, _SH_DENYWR ); 157 | nError = (m_pFile != NULL) ? (0) : (errno); 158 | if ( nError != 0 ) 159 | return nError; 160 | #else // LOGOG_FLAVOR_WINDOWS 161 | m_pFile = fopen( m_pFileName, "ab+" ); 162 | #endif // LOGOG_FLAVOR_WINDOWS 163 | 164 | if ( m_pFile == NULL ) 165 | m_bOpenFailed = true; // and no further outputs will work 166 | else 167 | { 168 | #ifdef LOGOG_UNICODE 169 | if ( !bFileAlreadyExists ) 170 | { 171 | WriteUnicodeBOM(); 172 | } 173 | #endif 174 | } 175 | 176 | // Disable output buffering if requested. 177 | // Buffering is performed by default. 178 | if (!m_bEnableOutputBuffering && m_pFile) 179 | { 180 | setvbuf(m_pFile, NULL, _IONBF, 0); 181 | } 182 | 183 | return ( m_pFile ? 0 : -1 ); 184 | } 185 | 186 | int LogFile::Output( const LOGOG_STRING &data ) 187 | { 188 | if ( m_bOpenFailed ) 189 | return -1; 190 | 191 | int result = 0; 192 | if ( m_bFirstTime ) 193 | { 194 | result = Open(); 195 | if ( result != 0 ) 196 | return result; 197 | 198 | m_bFirstTime = false; 199 | } 200 | 201 | return InternalOutput( data.size(), data.c_str()); 202 | } 203 | 204 | int LogFile::InternalOutput( size_t nSize, const LOGOG_CHAR *pData ) 205 | { 206 | size_t result; 207 | 208 | result = fwrite( pData, sizeof( LOGOG_CHAR ), nSize, m_pFile ); 209 | 210 | if ( (size_t)result != nSize ) 211 | return -1; 212 | 213 | return 0; 214 | } 215 | 216 | void LogFile::WriteUnicodeBOM() 217 | { 218 | static union { 219 | int i; 220 | char c[sizeof(int)]; 221 | } bDetectEndian = {0x01020304}; 222 | 223 | bool bIsLittleEndian = ( bDetectEndian.c[0] != 1 ); 224 | 225 | switch ( sizeof( LOGOG_CHAR )) 226 | { 227 | case 1: 228 | // This could be a UTF-8 BOM but technically very few systems support 229 | // sizeof( wchar_t ) == sizeof( char ). So for now we're not going 230 | // to write a BOM in these cases. 231 | break; 232 | 233 | case 2: 234 | if ( bIsLittleEndian ) 235 | InternalOutput( 1, (const LOGOG_CHAR *)"\xFF\xFE" ); // little endian UTF-16LE 236 | else 237 | InternalOutput( 1, (const LOGOG_CHAR *)"\xFE\xFF" ); // big endian UTF-16BE 238 | 239 | break; 240 | 241 | case 4: 242 | if ( bIsLittleEndian ) 243 | InternalOutput( 1, (const LOGOG_CHAR *)"\xFF\xFE\x00\x00" ); // little endian UTF-32LE 244 | else 245 | InternalOutput( 1, (const LOGOG_CHAR *)"\x00\x00\xFE\xFF" ); // big endian UTF-32BE 246 | 247 | break; 248 | 249 | default: 250 | // No idea what that character size is; do nothing 251 | break; 252 | } 253 | } 254 | 255 | LogBuffer::LogBuffer( Target *pTarget , 256 | size_t s ) : 257 | m_pStart( NULL ), 258 | m_nSize( 0 ) 259 | { 260 | m_pOutputTarget = pTarget; 261 | Allocate( s ); 262 | } 263 | 264 | LogBuffer::~LogBuffer() 265 | { 266 | Dump(); 267 | Deallocate(); 268 | } 269 | 270 | void LogBuffer::SetTarget( Target &t ) 271 | { 272 | m_pOutputTarget = &t; 273 | } 274 | 275 | int LogBuffer::Insert( const LOGOG_CHAR *pChars, size_t size ) 276 | { 277 | if (( m_pCurrent + size ) >= m_pEnd ) 278 | Dump(); 279 | 280 | if ( size > (size_t)( m_pEnd - m_pStart )) 281 | { 282 | #ifdef LOGOG_INTERNAL_DEBUGGING 283 | cerr << "Cannot insert string into buffer; string is larger than buffer. Allocate a larger size for the LogBuffer." << endl; 284 | #endif 285 | return -1; // can't fit this into buffer; punt it 286 | } 287 | 288 | // Store the size of this string in the buffer 289 | size_t *pSize; 290 | pSize = ( size_t *)m_pCurrent; 291 | *pSize = size; 292 | m_pCurrent = (LOGOG_CHAR *)++pSize; 293 | 294 | while ( size-- ) 295 | *m_pCurrent++ = *pChars++; 296 | 297 | return 0; 298 | } 299 | 300 | int LogBuffer::Dump() 301 | { 302 | LOGOG_CHAR *pCurrent = m_pStart; 303 | size_t *pSize; 304 | int nError; 305 | 306 | if ( m_pOutputTarget == NULL ) 307 | return -1; 308 | 309 | // We have to lock the output target here, as we do an end run around its Receive() function */ 310 | ScopedLock sl( m_pOutputTarget->m_MutexReceive ); 311 | 312 | while ( pCurrent < m_pCurrent ) 313 | { 314 | String sOut; 315 | // Get the size of this entry 316 | pSize = ( size_t * )pCurrent; 317 | // Move past that entry into the data area 318 | pCurrent = ( LOGOG_CHAR * )( pSize + 1 ); 319 | 320 | sOut.assign( pCurrent, pCurrent + *pSize - 1 ); 321 | 322 | if ( m_pOutputTarget ) 323 | { 324 | nError = m_pOutputTarget->Output( sOut ); 325 | if ( nError != 0 ) 326 | return nError; 327 | } 328 | 329 | pCurrent += *pSize; 330 | } 331 | 332 | // reset buffer 333 | m_pCurrent = m_pStart; 334 | 335 | return 0; 336 | } 337 | 338 | int LogBuffer::Output( const LOGOG_STRING &data ) 339 | { 340 | return Insert( &(*data), data.size() ); 341 | } 342 | 343 | void LogBuffer::Allocate( size_t size ) 344 | { 345 | m_nSize = size; 346 | m_pCurrent = m_pStart = (LOGOG_CHAR *)Object::Allocate( size * sizeof( LOGOG_CHAR )); 347 | m_pEnd = m_pStart + size; 348 | } 349 | 350 | void LogBuffer::Deallocate() 351 | { 352 | if ( m_pStart ) 353 | Object::Deallocate( m_pStart ); 354 | 355 | m_nSize = 0; 356 | } 357 | } 358 | 359 | -------------------------------------------------------------------------------- /include/topic.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file topic.hpp Topics -- subjects of interests for publishers and 3 | * subscribers to communicate about 4 | */ 5 | 6 | #ifndef __LOGOG_TOPIC_HPP__ 7 | #define __LOGOG_TOPIC_HPP__ 8 | 9 | namespace logog 10 | { 11 | 12 | /** A subject that nodes can choose to discuss with one another. 13 | ** Subscribers generally have very general topics, while publishers generally have very specific topics. 14 | **/ 15 | class Topic : public Node 16 | { 17 | friend class TopicLevel; 18 | friend class TopicGroup; 19 | friend class FilterDefault; 20 | 21 | public: 22 | /** Creates a topic. Note the defaults for creating a topic -- these defaults are equivalent to "no setting" 23 | ** for those fields. 24 | **/ 25 | Topic( const LOGOG_LEVEL_TYPE level = LOGOG_LEVEL_ALL, 26 | const LOGOG_CHAR *sFileName = NULL, 27 | const int nLineNumber = 0, 28 | const LOGOG_CHAR *sGroup = NULL, 29 | const LOGOG_CHAR *sCategory = NULL, 30 | const LOGOG_CHAR *sMessage = NULL, 31 | const double dTimestamp = 0.0f ); 32 | 33 | /** Topics are always topics. We use this to avoid any RTTI dependence. */ 34 | virtual bool IsTopic() const; 35 | 36 | /** Causes this topic to publish another topic to all its subscribers. 37 | ** \return 0 if successful, non-zero if this topic failed to send the publication to all subscribers */ 38 | virtual int Send( const Topic &node ); 39 | 40 | /** Causes this topic to publish itself to all its subscribers. */ 41 | virtual int Transmit(); 42 | 43 | /** Permits this node to receive a publication from another node, and act upon it. 44 | ** \param node The node constituting the publication 45 | ** \return 0 if successful, non-zero if this node failed to process the publication 46 | **/ 47 | virtual int Receive( const Topic &node ); 48 | 49 | /** Is this topic interested in receiving notifications from another topic? This function implements 50 | ** a generic (slow) test that should work for all topic types. This function only checks fields 51 | ** that have previously been set on this topic -- fields that have not been set will not limit this 52 | ** topic's ability to subscribe. If any of the previously set fields does not "match" the other topic, 53 | ** this function will return false. The matching function behaves slightly differently from field to 54 | ** field. 55 | ** - In the topic level case, this function rejects a publisher with a lower topic level than our 56 | ** own. 57 | ** - In the group, category, file name and message case, this function rejects a publisher if our 58 | ** own group/category/file name or message cannot be found as a substring in the possible publisher. 59 | ** This functionality permits very simple pattern matching functionality (i.e. show me all the message 60 | ** lines that have the word "upload" in them, regardless of their log level.) 61 | ** - In the line number case, this function rejects a publisher unless the line number matches exactly. 62 | ** - In the timestamp case, this function rejects a publisher if their timestamp is before our own. 63 | ** \param otherNode The topic which we are considering subscribing to 64 | **/ 65 | virtual bool CanSubscribeTo( const Node &otherNode ); 66 | 67 | virtual bool CanSubscribeCheckTopic( const Topic &other ); 68 | 69 | /** Causes this topic to begin publishing events to the given subscriber. 70 | ** \param subscriber The node to receive published events 71 | ** \return true if the request was successful, false if the subscriber was already subscribed 72 | **/ 73 | virtual bool PublishTo( Node &subscriber ); 74 | 75 | 76 | 77 | /** Formats the message in this topic given a sprintf-style set of arguments. 78 | ** This function can be used to set the current message in this topic to a string with a variable number of parameters. 79 | **/ 80 | virtual void Format( const LOGOG_CHAR *cFormatMessage, ... ); 81 | 82 | const LOGOG_STRING &FileName() const; 83 | void FileName( const LOGOG_STRING &s ); 84 | 85 | const LOGOG_STRING &Message() const; 86 | void Message( const LOGOG_STRING &s ); 87 | 88 | const LOGOG_STRING &Category() const; 89 | void Category( const LOGOG_STRING &s ); 90 | 91 | const LOGOG_STRING &Group() const; 92 | void Group( const LOGOG_STRING &s ); 93 | 94 | int LineNumber() const; 95 | void LineNumber( const int num ); 96 | 97 | LOGOG_LEVEL_TYPE Level() const; 98 | void Level( LOGOG_LEVEL_TYPE level ); 99 | 100 | LOGOG_TIME Timestamp() const; 101 | void Timestamp( const LOGOG_TIME t ); 102 | 103 | TOPIC_FLAGS GetTopicFlags() const; 104 | 105 | protected: 106 | /** An array (not an STL vector) of string properties for this topic. */ 107 | LOGOG_STRING m_vStringProps[ TOPIC_STRING_COUNT ]; 108 | /** An array (not an STL vector) of integer properties for this topic. */ 109 | int m_vIntProps[ TOPIC_INT_COUNT ]; 110 | /** The time associated with this topic. Usually this field is updated when a topic is triggered. Times need not be associated 111 | ** with a particular topic, in which case this value is zero. 112 | ** */ 113 | LOGOG_TIME m_tTime; 114 | /** A bitfield representing the "important" fields in this topic. Not all fields are considered to contain important information 115 | ** all the time. A logical OR of the TOPIC_*_FLAG fields. 116 | ** \sa TopicBitsType 117 | **/ 118 | TOPIC_FLAGS m_TopicFlags; 119 | }; 120 | 121 | /** A topic that permits both publishing as well as subscribing. This class is functionally same as a Topic; we've added it 122 | ** as a class for clarity when referring to different topic types. Filters should be instantiated only after outputs are 123 | ** instantiated, as they automatically search for and publish to targets. If you instantiate a filter before you 124 | ** instantiate a target, you will need to call PublishTo( theTarget ) yourself before using the target. 125 | **/ 126 | class Filter : public Topic 127 | { 128 | public: 129 | Filter( const LOGOG_LEVEL_TYPE level = LOGOG_LEVEL_ALL, 130 | const LOGOG_CHAR *sFileName = NULL, 131 | const int nLineNumber = 0, 132 | const LOGOG_CHAR *sGroup = NULL, 133 | const LOGOG_CHAR *sCategory = NULL, 134 | const LOGOG_CHAR *sMessage = NULL, 135 | const double dTimestamp = 0.0f ); 136 | }; 137 | 138 | /** A FilterDefault represents a singleton filter whose level may be changed dynamically 139 | * at run time. We only determine message routing once, when a message is invoked the 140 | * first time. So therefore a FilterDefault subscribes to all normal messages but 141 | * disregards the logging level at routing negotiation time. Instead it forwards 142 | * messages only if the dynamic level check succeeds. 143 | */ 144 | class FilterDefault : public Filter 145 | { 146 | typedef Filter super; 147 | public: 148 | FilterDefault(const LOGOG_LEVEL_TYPE level); 149 | int Receive(const Topic &node); 150 | void Level(LOGOG_LEVEL_TYPE level); 151 | }; 152 | 153 | /** Returns a reference to the unique default filter instantiated with logog. */ 154 | extern FilterDefault &GetFilterDefault(); 155 | 156 | /** Sets the current reporting level for the default filter. All messages 157 | * connected to the filter after this point should obey this default 158 | * level setting. 159 | */ 160 | void SetDefaultLevel( LOGOG_LEVEL_TYPE level ); 161 | 162 | /** A topic with the group name being the only field of significance. */ 163 | class TopicGroup : public Topic 164 | { 165 | public: 166 | TopicGroup( const LOGOG_CHAR *sGroup = NULL ); 167 | 168 | virtual bool CanSubscribeCheckTopic( const Topic &other ); 169 | }; 170 | 171 | /** A topic with the level being the only field of significance. */ 172 | class TopicLevel : public Topic 173 | { 174 | public: 175 | TopicLevel( const LOGOG_LEVEL_TYPE level ); 176 | virtual bool CanSubscribeCheckTopic( const Topic &other ); 177 | }; 178 | 179 | /** A topic that is also a source. */ 180 | class TopicSource : public Topic 181 | { 182 | public: 183 | TopicSource( const LOGOG_LEVEL_TYPE level = LOGOG_LEVEL_ALL, 184 | const LOGOG_CHAR *sFileName = NULL, 185 | const int nLineNumber = 0, 186 | const LOGOG_CHAR *sGroup = NULL, 187 | const LOGOG_CHAR *sCategory = NULL, 188 | const LOGOG_CHAR *sMessage = NULL, 189 | const double dTimestamp = 0.0f ); 190 | 191 | /** Returns false. Sources do not subscribe. */ 192 | virtual bool SubscribeTo( Node & ); 193 | 194 | /** Returns false. Sources do not unsubscribe. */ 195 | virtual bool UnsubscribeTo( Node & ); 196 | virtual bool CanSubscribe() const; 197 | }; 198 | 199 | /** A topic that is also a sink. */ 200 | class TopicSink : public Topic 201 | { 202 | public: 203 | virtual bool IsTopic() const; 204 | 205 | /** Sinks do not add themselves to the list of interested subscribers. That's up to intermediate topics to decide. */ 206 | virtual void Initialize(); 207 | 208 | /** Returns false. Sinks do not publish. */ 209 | virtual bool PublishTo( Node & ); 210 | 211 | /** Returns false. Sinks do not unpublish. */ 212 | virtual bool UnpublishTo( Node & ); 213 | 214 | /** Returns false. Sinks do not publish. */ 215 | virtual bool CanPublish() const; 216 | }; 217 | } 218 | 219 | #endif // __LOGOG_TOPIC_HPP_ 220 | -------------------------------------------------------------------------------- /include/macro.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file macro.hpp Macros for instantiation of a message. 3 | */ 4 | 5 | #ifndef __LOGOG_MACRO_HPP__ 6 | #define __LOGOG_MACRO_HPP__ 7 | 8 | namespace logog 9 | { 10 | #ifdef LOGOG_USE_PREFIX 11 | #define LOGOG_PREFIX LOGOG_ 12 | #endif // LOGOG_USE_PREFIX 13 | 14 | #ifndef LOGOG_GROUP 15 | /** This is the current group for created messages. Set this to NULL if you 16 | * want messages to not be part of any specific group. 17 | */ 18 | #define LOGOG_GROUP NULL 19 | #endif 20 | 21 | #ifndef LOGOG_CATEGORY 22 | /** This is the current category for created messages. Set this to NULL if you 23 | * want messages to not be part of any specific group. 24 | */ 25 | #define LOGOG_CATEGORY NULL 26 | #endif 27 | 28 | /** When you have a macro replacement, the preprocessor will only expand the macros recursively 29 | * if neither the stringizing operator # nor the token-pasting operator ## are applied to it. 30 | * So, you have to use some extra layers of indirection, you can use the token-pasting operator 31 | * with a recursively expanded argument. 32 | */ 33 | #define TOKENPASTE2(x, y) x ## y 34 | /** \sa TOKENPASTE2(x, y) */ 35 | #define TOKENPASTE(x, y) TOKENPASTE2(x, y) 36 | 37 | /** This macro is used when a message is instantiated without any varargs 38 | * provided by the user. It locks a global mutex, creates the message, 39 | * locks it, transmits it, and releases all locks. 40 | */ 41 | #define LOGOG_LEVEL_GROUP_CATEGORY_MESSAGE_NO_VA( level, group, cat, msg ) \ 42 | { \ 43 | Mutex *___pMCM = &GetMessageCreationMutex(); \ 44 | ___pMCM->MutexLock(); \ 45 | static logog::Message *TOKENPASTE(_logog_,__LINE__) = new logog::Message( level, \ 46 | LOGOG_CONST_STRING( __FILE__ ), \ 47 | __LINE__ , \ 48 | LOGOG_CONST_STRING( group ), \ 49 | LOGOG_CONST_STRING( cat ), \ 50 | msg; \ 51 | ___pMCM->MutexUnlock(); \ 52 | TOKENPASTE(_logog_,__LINE__)->m_Transmitting.MutexLock(); \ 53 | TOKENPASTE(_logog_,__LINE__)->Transmit(); \ 54 | TOKENPASTE(_logog_,__LINE__)->m_Transmitting.MutexUnlock(); \ 55 | } 56 | 57 | /** This macro is used when a message is instantiated with varargs provided 58 | * by the user. It locks a global mutex, creates the message, locks it, 59 | * formats the message string inside the message, transmits it, 60 | * and releases all locks. 61 | * When logog is shut down, it may be started back up again later. Therefore, 62 | * logog needs a way to flag all static Message pointers that they need 63 | * to be recreated. We manually simulate a static Message pointer by 64 | * implementing it via a static bool. The bool is turned on the first time 65 | * this code is run. 66 | * NOTE! A subtle race condition exists in the following code, that will ONLY occur 67 | * if logog is shut down at the same moment that a log message is processed from 68 | * another thread than the one calling the shutdown. The Message object could 69 | * theoretically be destroyed from another thread just before it's locked 70 | * in a thread that calls the Format() and Transmit() calls on it. I'm not 71 | * sure if this is really a bug -- technically, this race condition will 72 | * occur only if you are calling log messages right on top of the SHUTDOWN 73 | * call from the main thread. 74 | */ 75 | 76 | #define LOGOG_LEVEL_GROUP_CATEGORY_MESSAGE( level, group, cat, formatstring, ... ) \ 77 | LOGOG_MICROSOFT_PRAGMA_IN_MACRO(warning(push)) \ 78 | LOGOG_MICROSOFT_PRAGMA_IN_MACRO(warning(disable : 4127 )) \ 79 | do \ 80 | { \ 81 | ::logog::Mutex *___pMCM = &::logog::GetMessageCreationMutex(); \ 82 | ___pMCM->MutexLock(); \ 83 | static bool TOKENPASTE(_logog_static_bool_,__LINE__) = false; \ 84 | static logog::Message * TOKENPASTE(_logog_,__LINE__); \ 85 | if ( TOKENPASTE(_logog_static_bool_,__LINE__) == false ) \ 86 | { \ 87 | TOKENPASTE(_logog_,__LINE__) = \ 88 | new logog::Message( level, \ 89 | LOGOG_CONST_STRING( __FILE__ ), \ 90 | __LINE__ , \ 91 | LOGOG_CONST_STRING( group ), \ 92 | LOGOG_CONST_STRING( cat ), \ 93 | LOGOG_CONST_STRING( "" ), \ 94 | 0.0f, \ 95 | & (TOKENPASTE(_logog_static_bool_,__LINE__)) ); \ 96 | } \ 97 | ___pMCM->MutexUnlock(); \ 98 | /* A race condition could theoretically occur here if you are shutting down at the same instant as sending log messages. */ \ 99 | TOKENPASTE(_logog_,__LINE__)->m_Transmitting.MutexLock(); \ 100 | TOKENPASTE(_logog_,__LINE__)->Format( formatstring, ##__VA_ARGS__ ); \ 101 | TOKENPASTE(_logog_,__LINE__)->Transmit(); \ 102 | TOKENPASTE(_logog_,__LINE__)->m_Transmitting.MutexUnlock(); \ 103 | } while (false) \ 104 | LOGOG_MICROSOFT_PRAGMA_IN_MACRO(warning(pop)) 105 | 106 | /** Calls LOGOG_LEVEL_GROUP_CATEGORY_MESSAGE with the current LOGOG_GROUP and 107 | * LOGOG_CATEGORY setting. 108 | */ 109 | #define LOGOG_LEVEL_MESSAGE( level, formatstring, ... ) \ 110 | LOGOG_LEVEL_GROUP_CATEGORY_MESSAGE( level, LOGOG_GROUP, LOGOG_CATEGORY, formatstring, ##__VA_ARGS__ ) 111 | 112 | /** Calls LOGOG_LEVEL_MESSAGE with the current LOGOG_LEVEL setting. */ 113 | #define LOGOG_MESSAGE( formatstring, ... ) \ 114 | LOGOG_LEVEL_MESSAGE( LOGOG_LEVEL, formatstring, ##__VA_ARGS__ ) 115 | 116 | 117 | #if LOGOG_LEVEL >= LOGOG_LEVEL_DEBUG 118 | /** Logs a message at the DEBUG reporting level. */ 119 | #define LOGOG_DEBUG( formatstring, ... ) \ 120 | LOGOG_LEVEL_MESSAGE( LOGOG_LEVEL_DEBUG, formatstring, ##__VA_ARGS__ ) 121 | #else 122 | #define LOGOG_DEBUG( formatstring, ... ) do {} while (false) 123 | #endif 124 | 125 | #if LOGOG_LEVEL >= LOGOG_LEVEL_INFO 126 | /** Logs a message at the INFO reporting level. */ 127 | #define LOGOG_INFO( formatstring, ... ) \ 128 | LOGOG_LEVEL_MESSAGE( LOGOG_LEVEL_INFO, formatstring, ##__VA_ARGS__ ) 129 | #else 130 | #define LOGOG_INFO( formatstring, ... ) do {} while (false) 131 | #endif 132 | 133 | #if LOGOG_LEVEL >= LOGOG_LEVEL_WARN3 134 | /** Logs a message at the WARN3 reporting level. */ 135 | #define LOGOG_WARN3( formatstring, ... ) \ 136 | LOGOG_LEVEL_MESSAGE( LOGOG_LEVEL_WARN3, formatstring, ##__VA_ARGS__ ) 137 | #else 138 | #define LOGOG_WARN3( formatstring, ... ) do {} while (false) 139 | #endif 140 | 141 | #if LOGOG_LEVEL >= LOGOG_LEVEL_WARN2 142 | /** Logs a message at the WARN2 reporting level. */ 143 | #define LOGOG_WARN2( formatstring, ... ) \ 144 | LOGOG_LEVEL_MESSAGE( LOGOG_LEVEL_WARN2, formatstring, ##__VA_ARGS__ ) 145 | #else 146 | #define LOGOG_WARN2( formatstring, ... ) do {} while (false) 147 | #endif 148 | 149 | #if LOGOG_LEVEL >= LOGOG_LEVEL_WARN1 150 | /** Logs a message at the WARN1 reporting level. */ 151 | #define LOGOG_WARN1( formatstring, ... ) \ 152 | LOGOG_LEVEL_MESSAGE( LOGOG_LEVEL_WARN1, formatstring, ##__VA_ARGS__ ) 153 | #else 154 | #define LOGOG_WARN1( formatstring, ... ) do {} while (false) 155 | #endif 156 | 157 | #if LOGOG_LEVEL >= LOGOG_LEVEL_WARN 158 | /** Logs a message at the WARN reporting level. */ 159 | #define LOGOG_WARN( formatstring, ... ) \ 160 | LOGOG_LEVEL_MESSAGE( LOGOG_LEVEL_WARN, formatstring, ##__VA_ARGS__ ) 161 | #else 162 | #define LOGOG_WARN( formatstring, ... ) do {} while (false) 163 | #endif 164 | 165 | #if LOGOG_LEVEL >= LOGOG_LEVEL_ERROR 166 | /** Logs a message at the ERROR reporting level. */ 167 | #define LOGOG_ERROR( formatstring, ... ) \ 168 | LOGOG_LEVEL_MESSAGE( LOGOG_LEVEL_ERROR, formatstring, ##__VA_ARGS__ ) 169 | #else 170 | #define LOGOG_ERROR( formatstring, ... ) do {} while (false) 171 | #endif 172 | 173 | #if LOGOG_LEVEL >= LOGOG_LEVEL_CRITICAL 174 | /** Logs a message at the CRITICAL reporting level. */ 175 | #define LOGOG_CRITICAL( formatstring, ... ) \ 176 | LOGOG_LEVEL_MESSAGE( LOGOG_LEVEL_CRITICAL, formatstring, ##__VA_ARGS__ ) 177 | #else 178 | #define LOGOG_CRITICAL( formatstring, ... ) do {} while (false) 179 | #endif 180 | 181 | #if LOGOG_LEVEL >= LOGOG_LEVEL_ALERT 182 | /** Logs a message at the ALERT reporting level. */ 183 | #define LOGOG_ALERT( formatstring, ... ) \ 184 | LOGOG_LEVEL_MESSAGE( LOGOG_LEVEL_ALERT, formatstring, ##__VA_ARGS__ ) 185 | #else 186 | #define LOGOG_ALERT( formatstring, ... ) do {} while (false) 187 | #endif 188 | 189 | #if LOGOG_LEVEL >= LOGOG_LEVEL_EMERGENCY 190 | /** Logs a message at the EMERGENCY reporting level. */ 191 | #define LOGOG_EMERGENCY( formatstring, ... ) \ 192 | LOGOG_LEVEL_MESSAGE( LOGOG_LEVEL_EMERGENCY, formatstring, ##__VA_ARGS__ ) 193 | #else 194 | #define LOGOG_EMERGENCY( formatstring, ... ) do {} while (false) 195 | #endif 196 | 197 | #define LOGOG_SET_LEVEL( level ) \ 198 | ::logog::SetDefaultLevel( level ); 199 | 200 | /** Define this compilation flag if your compilation environment conflicts with 201 | * any of the shorthand logging macros in macro.hpp. 202 | */ 203 | #ifndef LOGOG_USE_PREFIX 204 | /* If you get compilation errors in this section, then define the flag LOGOG_USE_PREFIX during compilation, and these 205 | * shorthand logging macros won't exist -- you'll need to use the LOGOG_* equivalents above. 206 | */ 207 | /* We can't use DEBUG in Win32 unfortunately, so we use DBUG for shorthand here. */ 208 | //! [Shorthand] 209 | /** \sa LOGOG_DEBUG */ 210 | #define DBUG(...) LOGOG_DEBUG( __VA_ARGS__ ) 211 | /** \sa LOGOG_INFO */ 212 | #define INFO(...) LOGOG_INFO( __VA_ARGS__ ) 213 | /** \sa LOGOG_WARN3 */ 214 | #define WARN3(...) LOGOG_WARN3( __VA_ARGS__ ) 215 | /** \sa LOGOG_WARN2 */ 216 | #define WARN2(...) LOGOG_WARN2( __VA_ARGS__ ) 217 | /** \sa LOGOG_WARN1 */ 218 | #define WARN1(...) LOGOG_WARN1( __VA_ARGS__ ) 219 | /** \sa LOGOG_WARN */ 220 | #define WARN(...) LOGOG_WARN( __VA_ARGS__ ) 221 | /** \sa LOGOG_ERROR */ 222 | #define ERR(...) LOGOG_ERROR( __VA_ARGS__ ) 223 | /** \sa LOGOG_ALERT */ 224 | #define ALERT(...) LOGOG_ALERT( __VA_ARGS__ ) 225 | /** \sa LOGOG_CRITICAL */ 226 | #define CRITICAL(...) LOGOG_CRITICAL( __VA_ARGS__ ) 227 | /** \sa LOGOG_EMERGENCY */ 228 | #define EMERGENCY(...) LOGOG_EMERGENCY( __VA_ARGS__ ) 229 | //! [Shorthand] 230 | #endif 231 | 232 | /** Call this function to initialize logog and prepare for logging. 233 | * \sa logog::Initialize() 234 | */ 235 | #define LOGOG_INITIALIZE(...) logog::Initialize( __VA_ARGS__ ) 236 | 237 | /** Call this function to shut down logog and release all memory allocated. 238 | * \sa logog::Shutdown() 239 | */ 240 | #define LOGOG_SHUTDOWN() logog::Shutdown() 241 | 242 | } // namespace logog 243 | 244 | #endif // __LOGOG_MACRO_HPP_ 245 | -------------------------------------------------------------------------------- /src/topic.cpp: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * \file topic.cpp 4 | */ 5 | 6 | #include "logog.hpp" 7 | 8 | namespace logog { 9 | 10 | void SetDefaultLevel( LOGOG_LEVEL_TYPE level ) 11 | { 12 | FilterDefault *pDefaultFilter = &GetFilterDefault(); 13 | 14 | pDefaultFilter->Level( level ); 15 | } 16 | 17 | Topic::Topic( const LOGOG_LEVEL_TYPE level , 18 | const LOGOG_CHAR *sFileName , 19 | const int nLineNumber , 20 | const LOGOG_CHAR *sGroup , 21 | const LOGOG_CHAR *sCategory , 22 | const LOGOG_CHAR *sMessage , 23 | const double dTimestamp ) 24 | { 25 | m_TopicFlags = 0; 26 | 27 | if ( sFileName != NULL ) 28 | { 29 | m_vStringProps[ TOPIC_FILE_NAME ] = sFileName; 30 | m_TopicFlags |= TOPIC_FILE_NAME_FLAG; 31 | } 32 | 33 | if ( sGroup != NULL ) 34 | { 35 | m_vStringProps[ TOPIC_GROUP ] = sGroup; 36 | m_TopicFlags |= TOPIC_GROUP_FLAG; 37 | } 38 | 39 | if ( sCategory != NULL ) 40 | { 41 | m_vStringProps[ TOPIC_CATEGORY ] = sCategory; 42 | m_TopicFlags |= TOPIC_CATEGORY_FLAG; 43 | } 44 | 45 | if ( sMessage != NULL ) 46 | { 47 | m_vStringProps[ TOPIC_MESSAGE ] = sMessage; 48 | m_TopicFlags |= TOPIC_MESSAGE_FLAG; 49 | } 50 | 51 | m_vIntProps[ TOPIC_LEVEL ] = level; 52 | 53 | if ( level != LOGOG_LEVEL_ALL ) 54 | { 55 | m_TopicFlags |= TOPIC_LEVEL_FLAG; 56 | } 57 | 58 | m_vIntProps[ TOPIC_LINE_NUMBER ] = nLineNumber; 59 | 60 | if ( nLineNumber != 0 ) 61 | { 62 | m_TopicFlags |= TOPIC_LINE_NUMBER_FLAG; 63 | } 64 | 65 | m_tTime = dTimestamp; 66 | 67 | if ( dTimestamp != 0.0f ) //-V550 68 | m_TopicFlags |= TOPIC_TIMESTAMP_FLAG; 69 | } 70 | 71 | bool Topic::IsTopic() const 72 | { 73 | return true; 74 | } 75 | 76 | int Topic::Send( const Topic &node ) 77 | { 78 | LockableNodesType::iterator it; 79 | 80 | { 81 | ScopedLock sl( m_Subscribers ); 82 | it = m_Subscribers.begin(); 83 | } 84 | 85 | /* Iterate over the subscribers, but only addressing the subscribers group while locking it */ 86 | Topic *pCurrentTopic; 87 | Node *pCurrentNode; 88 | m_Subscribers.MutexLock(); 89 | int nError = 0; 90 | 91 | while ( it != m_Subscribers.end() ) 92 | { 93 | pCurrentNode = *it; 94 | 95 | if ( pCurrentNode->IsTopic() == false ) 96 | continue; 97 | 98 | pCurrentTopic = ( Topic * )pCurrentNode; 99 | 100 | if ( pCurrentTopic ) 101 | nError += pCurrentTopic->Receive( node ); 102 | 103 | ++it; 104 | } 105 | 106 | m_Subscribers.MutexUnlock(); 107 | 108 | return nError; 109 | } 110 | 111 | int Topic::Transmit() 112 | { 113 | return Send( *this ); 114 | } 115 | 116 | int Topic::Receive( const Topic &node ) 117 | { 118 | /* Default implementation -- send it on to all children */ 119 | return Send( node ); 120 | } 121 | 122 | bool Topic::CanSubscribeTo( const Node &otherNode ) 123 | { 124 | if ( CanSubscribe() == false ) 125 | return false; 126 | 127 | if ( otherNode.IsTopic() == false ) 128 | return false; 129 | 130 | Topic *pTopic = ( Topic * )&otherNode; 131 | 132 | /* This function will change from topic class to topic class. */ 133 | return CanSubscribeCheckTopic( *pTopic ); 134 | } 135 | 136 | bool Topic::CanSubscribeCheckTopic( const Topic &other ) 137 | { 138 | /* This is the generic comparison case. We'll want to optimize this function for other types 139 | * of topics. 140 | */ 141 | 142 | /* Check topics in likely order of disinterest */ 143 | if ( m_TopicFlags & TOPIC_LEVEL_FLAG ) 144 | { 145 | /* Topic levels are less interesting the larger the numbers are. */ 146 | if ( other.m_vIntProps[ TOPIC_LEVEL ] > m_vIntProps[ TOPIC_LEVEL ] ) 147 | return false; 148 | } 149 | 150 | if ( m_TopicFlags & TOPIC_GROUP_FLAG ) 151 | { 152 | /* If our topic is not a substring of the publisher's topic, ignore this */ 153 | if (( other.m_vStringProps[ TOPIC_GROUP ] ).find( m_vStringProps[ TOPIC_GROUP ] ) == LOGOG_STRING::npos ) 154 | return false; 155 | } 156 | 157 | if ( m_TopicFlags & TOPIC_CATEGORY_FLAG ) 158 | { 159 | /* If our topic is not a substring of the publisher's topic, ignore this */ 160 | if (( other.m_vStringProps[ TOPIC_CATEGORY ] ).find( m_vStringProps[ TOPIC_CATEGORY ] ) == LOGOG_STRING::npos ) 161 | return false; 162 | } 163 | 164 | if ( m_TopicFlags & TOPIC_FILE_NAME_FLAG ) 165 | { 166 | /* If our topic is not a substring of the publisher's file name, ignore this. */ 167 | if (( other.m_vStringProps[ TOPIC_FILE_NAME ] ).find( m_vStringProps[ TOPIC_FILE_NAME ] ) == LOGOG_STRING::npos ) 168 | return false; 169 | } 170 | 171 | if ( m_TopicFlags & TOPIC_LINE_NUMBER_FLAG ) 172 | { 173 | /* If our line number doesn't equal theirs, ignore this */ 174 | if ( other.m_vIntProps[ TOPIC_LINE_NUMBER ] != m_vIntProps[ TOPIC_LINE_NUMBER ] ) 175 | return false; 176 | } 177 | 178 | if ( m_TopicFlags & TOPIC_MESSAGE_FLAG ) 179 | { 180 | /* If our topic is not a substring of the publisher's file name, ignore this. */ 181 | if (( other.m_vStringProps[ TOPIC_MESSAGE ] ).find( m_vStringProps[ TOPIC_MESSAGE ] ) == LOGOG_STRING::npos ) 182 | return false; 183 | } 184 | 185 | if ( m_TopicFlags & TOPIC_TIMESTAMP_FLAG ) 186 | { 187 | /* Timestamps are only interesting if they're greater than or equal to ours. */ 188 | if ( other.m_tTime < m_tTime ) 189 | return false; 190 | } 191 | 192 | /* all tests passed */ 193 | return true; 194 | } 195 | 196 | bool Topic::PublishTo( Node &subscriber ) 197 | { 198 | #ifdef LOGOG_INTERNAL_DEBUGGING 199 | if ( &subscriber == this ) 200 | LOGOG_INTERNAL_FAILURE; 201 | #endif 202 | bool bWasInserted; 203 | 204 | /** Additional checking may be required first -- can the subscriber handle this publishing? */ 205 | if ( subscriber.IsTopic() ) 206 | { 207 | Topic *pSubscriber = (Topic *)&subscriber; 208 | 209 | if ( pSubscriber->CanSubscribeTo( *this ) == false ) 210 | return false; 211 | } 212 | 213 | { 214 | ScopedLock sl( m_Subscribers ); 215 | bWasInserted = ( m_Subscribers.insert( &subscriber ) ).second; 216 | } 217 | 218 | if ( bWasInserted ) 219 | subscriber.SubscribeTo( *this ); 220 | 221 | return bWasInserted; 222 | } 223 | 224 | void Topic::Format( const LOGOG_CHAR *cFormatMessage, ... ) 225 | { 226 | va_list args; 227 | 228 | va_start( args, cFormatMessage ); 229 | m_vStringProps[ TOPIC_MESSAGE ].format_va( cFormatMessage, args ); 230 | va_end( args ); 231 | 232 | m_TopicFlags |= TOPIC_MESSAGE_FLAG; 233 | } 234 | 235 | const LOGOG_STRING & Topic::FileName() const 236 | { 237 | return m_vStringProps[ TOPIC_FILE_NAME ]; 238 | } 239 | 240 | void Topic::FileName( const LOGOG_STRING &s ) 241 | { 242 | m_vStringProps[ TOPIC_FILE_NAME ] = s; 243 | m_TopicFlags |= TOPIC_FILE_NAME_FLAG; 244 | } 245 | 246 | const LOGOG_STRING & Topic::Message() const 247 | { 248 | return m_vStringProps[ TOPIC_MESSAGE ]; 249 | } 250 | 251 | void Topic::Message( const LOGOG_STRING &s ) 252 | { 253 | m_vStringProps[ TOPIC_MESSAGE ] = s; 254 | m_TopicFlags |= TOPIC_MESSAGE_FLAG; 255 | } 256 | 257 | const LOGOG_STRING & Topic::Category() const 258 | { 259 | return m_vStringProps[ TOPIC_CATEGORY ]; 260 | } 261 | 262 | void Topic::Category( const LOGOG_STRING &s ) 263 | { 264 | m_vStringProps[ TOPIC_CATEGORY ] = s; 265 | m_TopicFlags |= TOPIC_CATEGORY_FLAG; 266 | } 267 | 268 | const LOGOG_STRING & Topic::Group() const 269 | { 270 | return m_vStringProps[ TOPIC_GROUP ]; 271 | } 272 | 273 | void Topic::Group( const LOGOG_STRING &s ) 274 | { 275 | m_vStringProps[ TOPIC_GROUP ] = s; 276 | m_TopicFlags |= TOPIC_GROUP_FLAG; 277 | } 278 | 279 | int Topic::LineNumber() const 280 | { 281 | return m_vIntProps[ TOPIC_LINE_NUMBER ]; 282 | } 283 | 284 | void Topic::LineNumber( const int num ) 285 | { 286 | m_vIntProps[ TOPIC_LINE_NUMBER ] = num; 287 | m_TopicFlags |= TOPIC_LINE_NUMBER_FLAG; 288 | } 289 | 290 | LOGOG_LEVEL_TYPE Topic::Level() const 291 | { 292 | return ( LOGOG_LEVEL_TYPE )m_vIntProps[ TOPIC_LEVEL ]; 293 | } 294 | 295 | void Topic::Level( LOGOG_LEVEL_TYPE level ) 296 | { 297 | m_vIntProps[ TOPIC_LEVEL ] = level; 298 | m_TopicFlags |= TOPIC_LEVEL_FLAG; 299 | } 300 | 301 | logog::LOGOG_TIME Topic::Timestamp() const 302 | { 303 | return m_tTime; 304 | } 305 | 306 | void Topic::Timestamp( const LOGOG_TIME t ) 307 | { 308 | m_tTime = t; 309 | m_TopicFlags |= TOPIC_TIMESTAMP_FLAG; 310 | } 311 | 312 | TOPIC_FLAGS Topic::GetTopicFlags() const 313 | { 314 | return m_TopicFlags; 315 | } 316 | 317 | 318 | /********************************************************/ 319 | 320 | Filter::Filter( const LOGOG_LEVEL_TYPE level , 321 | const LOGOG_CHAR *sFileName , 322 | const int nLineNumber , 323 | const LOGOG_CHAR *sGroup , 324 | const LOGOG_CHAR *sCategory , 325 | const LOGOG_CHAR *sMessage , 326 | const double dTimestamp ) : 327 | Topic( level, sFileName, nLineNumber, sGroup, sCategory, sMessage, dTimestamp ) 328 | { 329 | #ifdef LOGOG_INTERNAL_DEBUGGING 330 | if ( pStatic == NULL ) 331 | LOGOG_INTERNAL_FAILURE; 332 | #endif 333 | 334 | PublishToMultiple( AllTargets() ); 335 | 336 | LockableNodesType *pFilterNodes = &AllFilters(); 337 | 338 | { 339 | ScopedLock sl( *pFilterNodes ); 340 | pFilterNodes->insert( this ); 341 | } 342 | } 343 | 344 | /** A FilterDefault represents a singleton filter whose level may be changed dynamically 345 | * at run time. We only determine message routing once, when a message is invoked the 346 | * first time. So therefore a FilterDefault subscribes to all normal messages but 347 | * disregards the logging level at routing negotiation time. Instead it forwards 348 | * messages only if the dynamic level check succeeds. 349 | */ 350 | FilterDefault::FilterDefault(const LOGOG_LEVEL_TYPE level) : 351 | Filter( level ) 352 | { 353 | /* We store the level but don't check it until message passing time */ 354 | m_TopicFlags &= ~TOPIC_LEVEL_FLAG; 355 | } 356 | 357 | void FilterDefault::Level(LOGOG_LEVEL_TYPE level) 358 | { 359 | m_vIntProps[TOPIC_LEVEL] = level; 360 | m_TopicFlags &= ~TOPIC_LEVEL_FLAG; 361 | } 362 | 363 | /** The FilterDefault silently discards messages that don't pass the level check. */ 364 | int FilterDefault::Receive(const Topic &node) 365 | { 366 | /* Topic levels are less interesting the larger the numbers are. */ 367 | if (node.m_vIntProps[TOPIC_LEVEL] > m_vIntProps[TOPIC_LEVEL]) 368 | return 0; 369 | 370 | return super::Receive(node); 371 | } 372 | 373 | extern FilterDefault & GetFilterDefault() 374 | { 375 | Statics *pStatic = &Static(); 376 | 377 | #ifdef LOGOG_INTERNAL_DEBUGGING 378 | if ( pStatic == NULL ) 379 | LOGOG_INTERNAL_FAILURE; 380 | #endif 381 | 382 | if ( pStatic->s_pDefaultFilter == NULL ) 383 | { 384 | pStatic->s_pDefaultFilter = new FilterDefault( LOGOG_LEVEL ); 385 | } 386 | 387 | return *((FilterDefault *)pStatic->s_pDefaultFilter); 388 | } 389 | 390 | TopicGroup::TopicGroup( const LOGOG_CHAR *sGroup ) : 391 | Topic( LOGOG_LEVEL_ALL, NULL, 0, sGroup ) 392 | { 393 | } 394 | 395 | bool TopicGroup::CanSubscribeCheckTopic( const Topic &other ) 396 | { 397 | if ( m_TopicFlags & TOPIC_LEVEL_FLAG ) 398 | { 399 | /* Topic levels are less interesting the larger the numbers are. */ 400 | if ( other.m_vIntProps[ TOPIC_LEVEL ] > m_vIntProps[ TOPIC_LEVEL ] ) 401 | return false; 402 | } 403 | 404 | return true; 405 | } 406 | 407 | TopicLevel::TopicLevel( const LOGOG_LEVEL_TYPE level ) : 408 | Topic( level ) 409 | { 410 | } 411 | 412 | 413 | bool TopicLevel::CanSubscribeCheckTopic( const Topic &other ) 414 | { 415 | /* Check topics in likely order of disinterest */ 416 | if ( m_TopicFlags & TOPIC_LEVEL_FLAG ) 417 | { 418 | /* Topic levels are less interesting the larger the numbers are. */ 419 | if ( other.m_vIntProps[ TOPIC_LEVEL ] > m_vIntProps[ TOPIC_LEVEL ] ) 420 | return false; 421 | } 422 | 423 | /* all tests passed */ 424 | return true; 425 | } 426 | 427 | TopicSource::TopicSource( const LOGOG_LEVEL_TYPE level , 428 | const LOGOG_CHAR *sFileName, 429 | const int nLineNumber, 430 | const LOGOG_CHAR *sGroup, 431 | const LOGOG_CHAR *sCategory, 432 | const LOGOG_CHAR *sMessage, 433 | const double dTimestamp ) : 434 | Topic( level, sFileName, nLineNumber, sGroup, sCategory, sMessage, dTimestamp ) 435 | { 436 | } 437 | 438 | bool TopicSource::SubscribeTo( Node & ) 439 | { 440 | return false; 441 | } 442 | 443 | bool TopicSource::UnsubscribeTo( Node & ) 444 | { 445 | return false; 446 | } 447 | 448 | bool TopicSource::CanSubscribe() const 449 | { 450 | return false; 451 | } 452 | 453 | bool TopicSink::IsTopic() const 454 | { 455 | return true; 456 | } 457 | 458 | void TopicSink::Initialize() 459 | { 460 | 461 | } 462 | 463 | bool TopicSink::PublishTo( Node & ) 464 | { 465 | return false; 466 | } 467 | 468 | bool TopicSink::UnpublishTo( Node & ) 469 | { 470 | return false; 471 | } 472 | 473 | bool TopicSink::CanPublish() const 474 | { 475 | return false; 476 | } 477 | } 478 | 479 | -------------------------------------------------------------------------------- /src/lstring.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * \file lstring.cpp 3 | */ 4 | 5 | #include "logog.hpp" 6 | 7 | namespace logog { 8 | 9 | String::String() 10 | { 11 | Initialize(); 12 | } 13 | 14 | String::~String() 15 | { 16 | Free(); 17 | } 18 | 19 | void String::Free() 20 | { 21 | if ( m_pBuffer && ( m_bIsConst == false )) 22 | { 23 | Deallocate( (void *)m_pBuffer ); 24 | m_pBuffer = m_pEndOfBuffer = m_pOffset = NULL; 25 | } 26 | 27 | if ( m_pKMP ) 28 | { 29 | Deallocate( (void *)m_pKMP ); 30 | m_pKMP = NULL; 31 | } 32 | } 33 | 34 | size_t String::Length( const LOGOG_CHAR *chars ) 35 | { 36 | size_t len = 0; 37 | 38 | while ( *chars++ ) 39 | len++; 40 | 41 | return len; 42 | } 43 | 44 | String & String::operator=( const String & other ) 45 | { 46 | Free(); 47 | Initialize(); 48 | assign( other ); 49 | return *this; 50 | } 51 | 52 | String & String::operator=( const LOGOG_CHAR *pstr ) 53 | { 54 | Free(); 55 | Initialize(); 56 | assign( pstr ); 57 | return *this; 58 | } 59 | 60 | String::String( const String &other ) 61 | { 62 | Initialize(); 63 | assign( other ); 64 | } 65 | 66 | String::String( const LOGOG_CHAR *pstr ) 67 | { 68 | Initialize(); 69 | assign( pstr ); 70 | } 71 | 72 | size_t String::size() const 73 | { 74 | return ( m_pOffset - m_pBuffer ); 75 | } 76 | 77 | void String::clear() 78 | { 79 | m_pOffset = m_pBuffer; 80 | } 81 | 82 | size_t String::reserve( size_t nSize ) 83 | { 84 | if ( nSize == (size_t)( m_pOffset - m_pBuffer )) 85 | return nSize; 86 | 87 | if ( nSize == 0 ) 88 | { 89 | if ( m_pBuffer && ( *m_pBuffer != (LOGOG_CHAR)NULL )) 90 | Deallocate( (void *)m_pBuffer ); 91 | 92 | Initialize(); 93 | return 0; 94 | } 95 | 96 | LOGOG_CHAR *pNewBuffer = (LOGOG_CHAR *)Allocate( sizeof( LOGOG_CHAR ) * nSize ); 97 | LOGOG_CHAR *pNewOffset = pNewBuffer; 98 | LOGOG_CHAR *pNewEnd = pNewBuffer + nSize; 99 | 100 | LOGOG_CHAR *pOldOffset = m_pOffset; 101 | 102 | if ( pOldOffset != NULL ) 103 | { 104 | while (( pNewOffset < pNewEnd ) && ( *pOldOffset != (LOGOG_CHAR)NULL )) 105 | *pNewOffset++ = *pOldOffset++; 106 | } 107 | 108 | if (( m_pBuffer != NULL ) && ( m_bIsConst == false )) 109 | Deallocate( m_pBuffer ); 110 | 111 | m_pBuffer = pNewBuffer; 112 | m_pOffset = pNewBuffer; 113 | m_pEndOfBuffer = pNewEnd; 114 | 115 | return ( m_pOffset - m_pBuffer ); 116 | } 117 | 118 | size_t String::reserve_for_int() 119 | { 120 | const int MAXIMUM_INT_SIZE = 64; 121 | reserve( MAXIMUM_INT_SIZE ); 122 | return MAXIMUM_INT_SIZE; 123 | } 124 | 125 | String::operator const LOGOG_CHAR *() const 126 | { 127 | return m_pBuffer; 128 | } 129 | 130 | const LOGOG_CHAR* String::c_str() const 131 | { 132 | return m_pBuffer; 133 | } 134 | 135 | size_t String::assign( const String &other ) 136 | { 137 | #ifdef LOGOG_INTERNAL_DEBUGGING 138 | if ( m_bIsConst ) 139 | cout << "Can't reassign const string!" << endl; 140 | #endif 141 | LOGOG_CHAR *pOther = other.m_pBuffer; 142 | 143 | if ( pOther == NULL ) 144 | return 0; 145 | 146 | size_t othersize = other.size(); 147 | 148 | reserve( othersize + 1 ); 149 | m_pOffset = m_pBuffer; 150 | 151 | for ( size_t t = 0; t <= othersize ; t++ ) 152 | *m_pOffset++ = *pOther++; 153 | 154 | return this->size(); 155 | } 156 | 157 | size_t String::assign( const int value ) 158 | { 159 | #ifdef LOGOG_INTERNAL_DEBUGGING 160 | if ( m_bIsConst ) 161 | cout << "Can't reassign const string!" << endl; 162 | #endif 163 | 164 | int number = value; 165 | m_pOffset = m_pBuffer; 166 | 167 | int bSign = value; 168 | 169 | if ( bSign < 0) 170 | number = -number; 171 | 172 | do 173 | { 174 | *m_pOffset++ = _LG("0123456789")[ number % 10 ]; 175 | } 176 | while( number /= 10 ); 177 | 178 | if (bSign < 0) 179 | *m_pOffset++ = '-'; 180 | 181 | *m_pOffset = (LOGOG_CHAR)'\0'; 182 | 183 | reverse( m_pBuffer, m_pOffset - 1 ); 184 | 185 | return ( m_pOffset - m_pBuffer ); 186 | } 187 | 188 | size_t String::assign( const LOGOG_CHAR *other, const LOGOG_CHAR *pEnd /*= NULL */ ) 189 | { 190 | size_t len; 191 | 192 | if ( pEnd == NULL ) 193 | len = Length( other ); 194 | else 195 | len = ( pEnd - other ); 196 | /** This constant decides whether assigning a LOGOG_CHAR * to a String will cause the String to use the previous buffer 197 | * in place, or create a new buffer and copy the results. 198 | */ 199 | #ifdef LOGOG_COPY_CONST_CHAR_ARRAY_ON_ASSIGNMENT 200 | reserve( len + 1 ); 201 | 202 | for (unsigned int t = 0; t <= len; t++ ) 203 | *m_pOffset++ = *other++; 204 | #else // LOGOG_COPY_CONST_CHAR_ARRAY_ON_ASSIGNMENT 205 | 206 | #ifdef LOGOG_INTERNAL_DEBUGGING 207 | if ( m_bIsConst ) 208 | cout << "Can't reassign const string!" << endl; 209 | #endif 210 | /* In this case we don't copy the buffer, just reuse it */ 211 | m_pBuffer = const_cast< LOGOG_CHAR *>( other ); 212 | m_pOffset = m_pBuffer + len + 1; 213 | m_pEndOfBuffer = m_pOffset; 214 | m_bIsConst = true; 215 | 216 | #endif // LOGOG_COPY_CONST_CHAR_ARRAY_ON_ASSIGNMENT 217 | 218 | return len; 219 | } 220 | size_t String::append( const String &other ) 221 | { 222 | #ifdef LOGOG_INTERNAL_DEBUGGING 223 | if ( m_bIsConst ) 224 | cout << "Can't reassign const string!" << endl; 225 | #endif 226 | 227 | return append( other.m_pBuffer ); 228 | } 229 | 230 | size_t String::append( const LOGOG_CHAR *other ) 231 | { 232 | #ifdef LOGOG_INTERNAL_DEBUGGING 233 | if ( m_bIsConst ) 234 | cout << "Can't reassign const string!" << endl; 235 | #endif 236 | if ( other == NULL ) 237 | return 0; 238 | 239 | while (( m_pOffset < m_pEndOfBuffer ) && ( *other != (LOGOG_CHAR)NULL )) 240 | *m_pOffset++ = *other++; 241 | 242 | return ( m_pOffset - m_pBuffer ); 243 | } 244 | 245 | size_t String::append( const LOGOG_CHAR c ) 246 | { 247 | if ( m_pOffset < m_pEndOfBuffer ) 248 | *m_pOffset++ = c; 249 | 250 | return ( m_pOffset - m_pBuffer ); 251 | } 252 | void String::reverse( LOGOG_CHAR* pStart, LOGOG_CHAR* pEnd ) 253 | { 254 | LOGOG_CHAR temp; 255 | 256 | while( pEnd > pStart) 257 | { 258 | temp=*pEnd, *pEnd-- =*pStart, *pStart++=temp; 259 | } 260 | } 261 | 262 | bool String::is_valid() const 263 | { 264 | return ( m_pBuffer != NULL ); 265 | } 266 | 267 | size_t String::find( String &other ) const 268 | { 269 | if ( is_valid() && other.is_valid()) 270 | { 271 | // KMP solution 272 | // String *pThis = const_cast< String *>(this); 273 | // return pThis->KMP( other.m_pBuffer, other.size()); 274 | LOGOG_CHAR *pFound; 275 | 276 | #ifdef LOGOG_UNICODE 277 | pFound = wcsstr( m_pBuffer, other.m_pBuffer ); 278 | #else // LOGOG_UNICODE 279 | pFound = strstr( m_pBuffer, other.m_pBuffer ); 280 | #endif 281 | 282 | if ( pFound != NULL ) 283 | { 284 | return ( pFound - m_pBuffer ); 285 | } 286 | 287 | return npos; 288 | } 289 | 290 | return npos; 291 | } 292 | 293 | void String::format( const LOGOG_CHAR *cFormatString, ... ) 294 | { 295 | va_list args; 296 | 297 | va_start(args, cFormatString); 298 | format_va( cFormatString, args ); 299 | va_end( args ); 300 | } 301 | 302 | void String::format_va( const LOGOG_CHAR *cFormatString, va_list args ) 303 | { 304 | ptrdiff_t nActualSize = -1, nAttemptedSize; 305 | LOGOG_CHAR *pszFormatted = NULL; 306 | 307 | Free(); 308 | 309 | /* Estimate length of output; don't pull in strlen() if we can help it */ 310 | size_t nEstLength = 0; 311 | const LOGOG_CHAR *pCurChar = cFormatString; 312 | while ( *pCurChar++ ) 313 | nEstLength++; 314 | 315 | if ( nEstLength == 0 ) 316 | { 317 | clear(); 318 | return; 319 | } 320 | 321 | /** nAttemptedSize is now a guess at an appropriate size, which is about 322 | ** two times the number of LOGOG_CHARs in the incoming format string. 323 | **/ 324 | nAttemptedSize = nEstLength * 2 * sizeof( LOGOG_CHAR ); 325 | 326 | /* Some *printf implementations, such as msvc's, return -1 on failure. 327 | * Others, such as gcc, return the number 328 | * of characters actually formatted on failure. Deal with either case here. 329 | */ 330 | for ( ; ; ) 331 | { 332 | /** We'll allocate that number of bytes. NOTE that this has less of a chance 333 | ** of working on a Unicode build. 334 | **/ 335 | pszFormatted = (LOGOG_CHAR *)Allocate( nAttemptedSize ); 336 | if ( !pszFormatted ) 337 | { 338 | LOGOG_INTERNAL_FAILURE; 339 | } 340 | 341 | *pszFormatted = (LOGOG_CHAR)'\0'; 342 | 343 | va_list argsCopy; 344 | 345 | /** The va_list structure is not standardized across all platforms; in particular 346 | ** Microsoft seems to have problem with the concept. 347 | **/ 348 | #if defined( va_copy ) 349 | va_copy( argsCopy, args ); 350 | #elif defined( __va_copy ) 351 | __va_copy( argsCopy, args ); 352 | #else 353 | memcpy( &argsCopy, &args, sizeof(va_list) ); 354 | #endif 355 | 356 | #ifdef LOGOG_UNICODE 357 | /** At this point, nSizeInWords will contain the number of words permitted in the 358 | ** output buffer. It takes into account space for appending a null character in the output 359 | ** buffer as well. 360 | **/ 361 | size_t nSizeInWords = (nAttemptedSize / sizeof( LOGOG_CHAR )); 362 | #endif 363 | /** The nActualSize value receives different things on different platforms. 364 | ** On some platforms it receives -1 on failure; on other platforms 365 | ** it receives the number of LOGOG_CHARs actually formatted (excluding 366 | ** the trailing NULL). 367 | **/ 368 | 369 | #ifdef LOGOG_FLAVOR_WINDOWS 370 | #ifdef LOGOG_UNICODE 371 | nActualSize = _vsnwprintf_s( pszFormatted, nSizeInWords, _TRUNCATE, cFormatString, argsCopy ); 372 | #else // LOGOG_UNICODE 373 | nActualSize = vsnprintf_s( pszFormatted, nAttemptedSize, _TRUNCATE, cFormatString, argsCopy ); 374 | #endif // LOGOG_UNICODE 375 | #else // LOGOG_FLAVOR_WINDOWS 376 | #ifdef LOGOG_UNICODE 377 | nActualSize = vswprintf( pszFormatted, nSizeInWords, cFormatString, argsCopy ); 378 | #else // LOGOG_UNICODE 379 | nActualSize = vsnprintf( pszFormatted, nAttemptedSize, cFormatString, argsCopy ); 380 | #endif // LOGOG_UNICODE 381 | #endif // LOGOG_FLAVOR_WINDOWS 382 | 383 | va_end( argsCopy ); 384 | 385 | /** Convert the number of LOGOG_CHARs actually formatted into bytes. This 386 | ** does NOT include the trailing NULL. 387 | **/ 388 | if ( nActualSize > 0 ) 389 | nActualSize *= sizeof( LOGOG_CHAR ); 390 | 391 | /** When we're checking the result, we have to keep in mind that the nActualSize 392 | ** does not include a null. We need to verify that the nAttemptedSize can hold all 393 | ** of nActualSize PLUS the size of one null on this platform. A LOGOG_CHAR could 394 | ** be 1, 2, or 4 bytes long. So nAttemptedSize must be greater or equal to nActualSize 395 | ** less the size of one (null) LOGOG_CHAR in bytes. Also, the last 396 | ** allocation may have failed altogether. Verify that all these conditions 397 | ** are clean before accepting the output and jumping out of this loop. 398 | **/ 399 | if (( nAttemptedSize >= (nActualSize - (ptrdiff_t) sizeof(LOGOG_CHAR))) && 400 | ( nActualSize > 0 ) ) 401 | break; 402 | 403 | /** That attempted allocation failed, we need to try again */ 404 | if ( pszFormatted != NULL ) 405 | Deallocate( pszFormatted ); 406 | 407 | /** If nActualSize has a positive value, it includes the number of bytes needed to hold 408 | ** the formatted string; we'll add a LOGOG_CHAR size to the end for the next 409 | ** allocation. If nActualSize has no meaningful value, we'll double the previous 410 | ** size and try again. 411 | **/ 412 | if (nActualSize > 0) 413 | { 414 | nAttemptedSize = nActualSize + sizeof( LOGOG_CHAR ); 415 | } 416 | else 417 | { 418 | nAttemptedSize *= 2; 419 | } 420 | 421 | } 422 | 423 | m_bIsConst = false; 424 | assign( pszFormatted ); 425 | /* We just allocated this string, which means it needs to be deallocated 426 | * at shutdown time. The previous function may have changed the const 427 | * setting for this string, which means we may need to change it back here... 428 | * */ 429 | m_bIsConst = false; 430 | } 431 | 432 | void String::Initialize() 433 | { 434 | m_pBuffer = NULL; 435 | m_pOffset = NULL; 436 | m_pEndOfBuffer = NULL; 437 | m_pKMP = NULL; 438 | m_bIsConst = false; 439 | } 440 | 441 | void String::preKmp( size_t m ) 442 | { 443 | ScopedLock sl( GetStringSearchMutex() ); 444 | 445 | size_t i, j; 446 | 447 | if ( m_pBuffer == NULL ) 448 | return; 449 | 450 | if ( m_pKMP == NULL ) 451 | { 452 | m_pKMP = (size_t *)Allocate( sizeof( size_t ) * ( m + 1) ); 453 | } 454 | 455 | i = 0; 456 | j = *m_pKMP = (size_t)-1; 457 | 458 | while (i < m) 459 | { 460 | while (j > (size_t)-1 && m_pBuffer[i] != m_pBuffer[j]) 461 | j = m_pKMP[j]; 462 | i++; 463 | j++; 464 | if (m_pBuffer[i] == m_pBuffer[j]) 465 | m_pKMP[i] = m_pKMP[j]; 466 | else 467 | m_pKMP[i] = j; 468 | } 469 | } 470 | 471 | size_t String::KMP( const LOGOG_CHAR *y, size_t n ) 472 | { 473 | size_t i, j; 474 | 475 | size_t m = size() - 1; // ignore NULL char 476 | 477 | /* Preprocessing */ 478 | if ( m_pKMP == NULL ) 479 | preKmp( m ); 480 | 481 | /* Searching */ 482 | i = j = 0; 483 | while (j < n) 484 | { 485 | while (i > (size_t)-1 && m_pBuffer[i] != y[j]) 486 | i = m_pKMP[i]; 487 | i++; 488 | j++; 489 | if (i >= m) 490 | { 491 | return (j - i); 492 | // We would do this if we cared about multiple substrings 493 | // i = m_pKMP[i]; 494 | } 495 | } 496 | 497 | return npos; 498 | } 499 | } 500 | 501 | -------------------------------------------------------------------------------- /test/test.cpp: -------------------------------------------------------------------------------- 1 | /** Only define this macro in source files that create unit tests. This setting brings in the std namespace, so its 2 | ** use is not recommended outside of unit tests. */ 3 | #define LOGOG_UNIT_TESTING 1 4 | 5 | /** Change this to higher constants to exponentially increase test difficulty. */ 6 | const int TEST_STRESS_LEVEL = 1; 7 | 8 | #include "logog.hpp" 9 | 10 | #include 11 | 12 | #include 13 | 14 | #ifdef LOGOG_FLAVOR_WINDOWS 15 | #include 16 | #endif 17 | 18 | using namespace logog; 19 | using namespace std; 20 | 21 | UNITTEST( SimpleLocking ) 22 | { 23 | //! [SimpleLocking] 24 | LOGOG_INITIALIZE(); 25 | 26 | { 27 | logog::Mutex m; 28 | 29 | { 30 | logog::ScopedLock s1( m ); 31 | LOGOG_COUT << _LG("Lock is on") << endl; 32 | } 33 | 34 | logog::Mutex *pM = new logog::Mutex(); 35 | 36 | { 37 | logog::ScopedLock s1( *pM ); 38 | LOGOG_COUT << _LG("Lock is on") << endl; 39 | } 40 | 41 | delete pM; 42 | 43 | LOGOG_COUT << _LG("Lock unlocked") << endl; 44 | 45 | logog::Message m1; 46 | 47 | } 48 | 49 | LOGOG_SHUTDOWN(); 50 | 51 | return 0; 52 | //! [SimpleLocking] 53 | } 54 | 55 | int _s_ThreadLockingTest = 0; 56 | 57 | void LockingThread( void *pvMutex ) 58 | { 59 | const int NUM_TRIALS = 10 * TEST_STRESS_LEVEL; 60 | Mutex *pMutex = (Mutex *)pvMutex; 61 | 62 | if ( pMutex == NULL ) 63 | { 64 | _s_ThreadLockingTest = 1; 65 | LOGOG_COUT << _LG("LockingThread received a NULL argument!") << endl; 66 | return; 67 | } 68 | 69 | for ( int t = 0; t < NUM_TRIALS; t++ ) 70 | { 71 | { 72 | ScopedLock sl( *pMutex ); 73 | INFO( _LG("Thread acquired lock on trial %d"), t); 74 | } 75 | 76 | INFO( _LG("Thread released lock %d"), t ); 77 | } 78 | } 79 | 80 | 81 | UNITTEST( Subscription ) 82 | { 83 | int nResult = 0; 84 | 85 | //! [Subscription] 86 | LOGOG_INITIALIZE(); 87 | 88 | { 89 | Topic n1, n2; 90 | 91 | // should succeed 92 | if ( n1.SubscribeTo( n2 ) != true ) 93 | nResult++; 94 | 95 | // should indicate no insertion took place 96 | if ( n2.PublishTo( n1 ) != false ) 97 | nResult++; 98 | 99 | n2.Transmit(); 100 | 101 | if ( n2.UnpublishTo( n1 ) != true ) 102 | nResult++; 103 | 104 | if ( n1.UnsubscribeTo( n2 ) != false ) 105 | nResult++; 106 | } 107 | 108 | LOGOG_SHUTDOWN(); 109 | 110 | //! [Subscription] 111 | 112 | return nResult; 113 | } 114 | 115 | UNITTEST( GlobalNodelist ) 116 | { 117 | LOGOG_INITIALIZE(); 118 | 119 | const int MAX_NODES = 10 * TEST_STRESS_LEVEL; 120 | 121 | LOGOG_VECTOR< Topic *> vTopics; 122 | 123 | for (int t = 0; t < MAX_NODES; t++ ) 124 | vTopics.push_back( new Topic ); 125 | 126 | for ( int i = 0; i < MAX_NODES; i++ ) 127 | for ( int j = i + 1; j < MAX_NODES; j++ ) 128 | vTopics.at( i )->SubscribeTo( *vTopics.at( j ) ); 129 | 130 | LockableNodesType *pNodes = ( LockableNodesType * )Static().s_pAllNodes; 131 | 132 | // For this test we assume that the default Filter has always been created. 133 | if ( pNodes->size() != ( MAX_NODES + 1)) 134 | { 135 | LOGOG_COUT << _LG("Incorrect number of nodes!") << endl; 136 | return 1; 137 | } 138 | 139 | // Try having that master node send a message to all other nodes! 140 | vTopics.at( MAX_NODES - 1)->Transmit(); 141 | 142 | // Let's try leaving all the nodes allocated and let logog clean them all up. 143 | LOGOG_SHUTDOWN(); 144 | 145 | if ( Static().s_pAllNodes != NULL ) 146 | { 147 | LOGOG_COUT << _LG("Could not delete all nodes!") << endl; 148 | return 1; 149 | } 150 | 151 | return 0; 152 | } 153 | 154 | 155 | UNITTEST( TimerTest ) 156 | { 157 | const int NUM_TRIALS = 10 * TEST_STRESS_LEVEL; 158 | const int WAIT_TIME = 1000000 * TEST_STRESS_LEVEL; 159 | 160 | Timer time; 161 | LOGOG_TIME fCurrent = time.Get(); 162 | 163 | for ( int t = 0; t < NUM_TRIALS; t++ ) 164 | { 165 | /* do busy work */ 166 | int k = 0; 167 | for ( int i = 0; i < WAIT_TIME; i++ ) 168 | k = k + 1; // fixes a lint warning 169 | 170 | if ( time.Get() < fCurrent ) 171 | { 172 | LOGOG_COUT << _LG("Timer error! Non monotonic timer behavior") << endl; 173 | return 1; 174 | } 175 | fCurrent = time.Get(); 176 | LOGOG_COUT << _LG("Current reported time: ") << fCurrent << endl; 177 | } 178 | 179 | return 0; 180 | } 181 | 182 | UNITTEST( TopicTest1 ) 183 | { 184 | LOGOG_INITIALIZE(); 185 | 186 | { 187 | Topic t1( LOGOG_LEVEL_WARN, 188 | _LG( "file1.cpp" ), 50 ); 189 | Topic t2( LOGOG_LEVEL_WARN ); 190 | Topic t3( LOGOG_LEVEL_ERROR, 191 | _LG( "file2.cpp" ), 100 ); 192 | Topic t4( LOGOG_LEVEL_WARN, NULL, 0, 193 | _LG( "Group" ), 194 | _LG( "Category" ), 195 | _LG( "Message" ), 196 | 30.0f); 197 | Topic t5( LOGOG_LEVEL_CRITICAL, NULL, 0, 198 | _LG( "GroupGROUP" ), 199 | _LG( "Important Category" ), 200 | _LG( "Your Message Here"), 201 | 150.0f); 202 | 203 | if (t1.CanSubscribeTo( t2 ) == true) 204 | { 205 | LOGOG_COUT << _LG("Subscription test failed; t1 can subscribe to t2") << endl; 206 | return 1; 207 | } 208 | 209 | if (t2.CanSubscribeTo( t1 ) == false ) 210 | { 211 | LOGOG_COUT << _LG("Subscription test failed; t2 can't subscribe to t1") << endl; 212 | return 1; 213 | } 214 | 215 | if ( t1.CanSubscribeTo( t3 ) == true ) 216 | { 217 | LOGOG_COUT << _LG("Subscription test failed; t1 can subscribe to t3") << endl; 218 | return 1; 219 | } 220 | 221 | if (t2.CanSubscribeTo( t3 ) == false ) 222 | { 223 | LOGOG_COUT << _LG("Subscription test failed; t2 can't subscribe to t3") << endl; 224 | return 1; 225 | } 226 | 227 | if (t4.CanSubscribeTo( t5 ) == false ) 228 | { 229 | LOGOG_COUT << _LG("Subscription test failed; t4 can't subscribe to t5") << endl; 230 | return 1; 231 | } 232 | 233 | if ( t5.CanSubscribeTo( t4 ) == true ) 234 | { 235 | LOGOG_COUT << _LG("Subscription test failed; t5 can subscribe to t4") << endl; 236 | return 1; 237 | } 238 | } 239 | 240 | LOGOG_SHUTDOWN(); 241 | 242 | return 0; 243 | } 244 | 245 | UNITTEST( Checkpoint1 ) 246 | { 247 | LOGOG_INITIALIZE(); 248 | 249 | { 250 | Checkpoint check( LOGOG_LEVEL_ALL, _LG( __FILE__ ), __LINE__, 251 | _LG("Group"), _LG("Category"), _LG("Message"), 1.0f ); 252 | 253 | Topic t; 254 | 255 | Cerr cerrobj; 256 | Cerr cerrgnu; 257 | OutputDebug outdebug; 258 | 259 | FormatterGCC f; 260 | cerrgnu.SetFormatter( f ); 261 | 262 | check.PublishTo( t ); 263 | t.PublishToMultiple( AllTargets() ); 264 | 265 | LOGOG_COUT << _LG("Setup complete; ready to transmit ") << endl; 266 | check.Transmit(); 267 | } 268 | 269 | LOGOG_SHUTDOWN(); 270 | 271 | return 0; 272 | } 273 | 274 | UNITTEST( FormatString1 ) 275 | { 276 | LOGOG_INITIALIZE(); 277 | { 278 | String s; 279 | 280 | s.format( _LG("This is a test message.\n")); 281 | LOGOG_COUT << s.c_str(); 282 | 283 | s.format( _LG("This is a test message: %d %x %f\n"), 1234, 0xf00d, 1.234f ); 284 | LOGOG_COUT << s.c_str(); 285 | 286 | const LOGOG_CHAR *p = _LG("Here is a string"); 287 | 288 | #ifdef LOGOG_UNICODE 289 | s.format( _LG("Here are six strings: %ls %ls %ls %ls %ls %ls \n"), p,p,p,p,p,p ); 290 | #else // LOGOG_UNICODE 291 | s.format( _LG("Here are six strings: %s %s %s %s %s %s \n"), p,p,p,p,p,p ); 292 | #endif 293 | 294 | LOGOG_COUT << s.c_str(); 295 | } 296 | 297 | LOGOG_SHUTDOWN(); 298 | 299 | return 0; 300 | } 301 | 302 | UNITTEST( FormatTopic1 ) 303 | { 304 | LOGOG_INITIALIZE(); 305 | { 306 | Cout out; 307 | LogFile f( "log.txt"); 308 | Message m; 309 | 310 | m.PublishToMultiple( AllTargets() ); 311 | 312 | m.Format( _LG("This is a test message: %d %x %f"), 1234, 0xf00d, 1.234f ); 313 | m.Transmit(); 314 | 315 | const char *p = "Here is a string"; 316 | 317 | m.Format( _LG("Here are six strings: %s %s %s %s %s %s"), p,p,p,p,p,p ); 318 | m.Transmit(); 319 | } 320 | 321 | LOGOG_SHUTDOWN(); 322 | 323 | return 0; 324 | } 325 | 326 | UNITTEST( ThreadLocking ) 327 | { 328 | LOGOG_INITIALIZE(); 329 | { 330 | Cout out; // only one of these please 331 | 332 | const int NUM_THREADS = 10 * TEST_STRESS_LEVEL; 333 | 334 | LOGOG_VECTOR< Thread *> vpThreads; 335 | Mutex mSharedMutex; 336 | 337 | for ( int t = 0; t < NUM_THREADS; t++ ) 338 | vpThreads.push_back( new Thread( (Thread::ThreadStartLocationType) LockingThread,&mSharedMutex )); 339 | 340 | for ( int t = 0; t < NUM_THREADS; t++ ) 341 | vpThreads[ t ]->Start(); 342 | 343 | for ( int t = 0; t < NUM_THREADS; t++ ) 344 | Thread::WaitFor( *vpThreads[ t ]); 345 | 346 | for ( int t = 0; t < NUM_THREADS; t++ ) 347 | delete vpThreads[t]; 348 | 349 | } 350 | 351 | LOGOG_SHUTDOWN(); 352 | 353 | return _s_ThreadLockingTest; 354 | } 355 | 356 | //! [FormatterCustom1] 357 | class FormatterCustom : public FormatterMSVC 358 | { 359 | virtual TOPIC_FLAGS GetTopicFlags( const Topic &topic ) 360 | { 361 | return ( Formatter::GetTopicFlags( topic ) & 362 | ~( TOPIC_FILE_NAME_FLAG | TOPIC_LINE_NUMBER_FLAG )); 363 | } 364 | }; 365 | 366 | UNITTEST( CustomFormatter ) 367 | { 368 | LOGOG_INITIALIZE(); 369 | { 370 | Cout out; 371 | FormatterCustom customFormat; 372 | 373 | out.SetFormatter( customFormat ); 374 | 375 | INFO( _LG( "No file and line number info is provided with this output.") ); 376 | 377 | /* The following output is produced: 378 | * info: No file and line number info is provided with this output. 379 | */ 380 | } 381 | LOGOG_SHUTDOWN(); 382 | 383 | return 0; 384 | } 385 | //! [FormatterCustom1] 386 | 387 | //! [FormatterModified] 388 | 389 | class FormatterModified : public FormatterMSVC 390 | { 391 | /* In this example custom Formatter, we've basically rewritten the parent 392 | * class to include !!!'s before any topic of LOGOG_LEVEL_WARN 393 | * intensity or more important, and we append ... to the message 394 | * in all cases. Large quantities of code are simply copied 395 | * and modified from the parent class. 396 | */ 397 | virtual LOGOG_STRING &Format( const Topic &topic, const Target &target ) 398 | { 399 | TOPIC_FLAGS flags = GetTopicFlags( topic ); 400 | 401 | m_sMessageBuffer.clear(); 402 | 403 | if ( topic.Level() <= LOGOG_LEVEL_WARN ) 404 | m_sMessageBuffer.append( _LG("!!! ") ); 405 | 406 | if ( flags & TOPIC_FILE_NAME_FLAG ) 407 | { 408 | m_sMessageBuffer.append( topic.FileName() ); 409 | m_sMessageBuffer.append( ':' ); 410 | } 411 | 412 | if ( flags & TOPIC_LINE_NUMBER_FLAG ) 413 | { 414 | m_sIntBuffer.assign( topic.LineNumber() ); 415 | m_sMessageBuffer.append( m_sIntBuffer ); 416 | m_sMessageBuffer.append( LOGOG_CONST_STRING(": ")); 417 | } 418 | 419 | RenderTimeOfDay(); 420 | 421 | if ( flags & TOPIC_LEVEL_FLAG ) 422 | { 423 | m_sMessageBuffer.append( ErrorDescription( topic.Level())); 424 | m_sMessageBuffer.append( LOGOG_CONST_STRING(": ")); 425 | } 426 | 427 | if ( flags & TOPIC_GROUP_FLAG ) 428 | { 429 | m_sMessageBuffer.append( LOGOG_CONST_STRING("{") ); 430 | m_sMessageBuffer.append( topic.Group() ); 431 | m_sMessageBuffer.append( LOGOG_CONST_STRING("} ") ); 432 | } 433 | 434 | if ( flags & TOPIC_CATEGORY_FLAG ) 435 | { 436 | m_sMessageBuffer.append( LOGOG_CONST_STRING("[")); 437 | m_sMessageBuffer.append( topic.Category() ); 438 | m_sMessageBuffer.append( LOGOG_CONST_STRING("] ")); 439 | } 440 | 441 | if ( flags & TOPIC_MESSAGE_FLAG ) 442 | { 443 | m_sMessageBuffer.append( topic.Message() ); 444 | } 445 | 446 | /* At this point we use the String's format() function to reformat the 447 | * old string into the new format. 448 | */ 449 | if ( topic.Level() <= LOGOG_LEVEL_WARN ) 450 | { 451 | /* If this message is more important than LOGOG_LEVEL_WARN, 452 | * add exclamation marks 453 | */ 454 | m_sMessageBuffer.append( _LG(" !!!") ); 455 | } 456 | else 457 | { 458 | /* Otherwise add periods */ 459 | m_sMessageBuffer.append( _LG(" ...") ); 460 | } 461 | 462 | if ( flags & TOPIC_MESSAGE_FLAG ) 463 | { 464 | m_sMessageBuffer.append( (LOGOG_CHAR)'\n' ); 465 | } 466 | 467 | if ( target.GetNullTerminatesStrings() ) 468 | m_sMessageBuffer.append( (LOGOG_CHAR)NULL ); 469 | 470 | /* Return m_sMessageBuffer like the parent class does */ 471 | return m_sMessageBuffer; 472 | } 473 | }; 474 | 475 | UNITTEST( CustomFormatterModified ) 476 | { 477 | LOGOG_INITIALIZE(); 478 | { 479 | /* This test produces output resembling: 480 | 481 | C:\logog\test\test.cpp:484: info: This info message output has trailing periods ... 482 | !!! C:\logog\test\test.cpp:485: warning: This warning message is surrounded by exclamation marks !!! 483 | 484 | */ 485 | 486 | Cout out; 487 | FormatterModified customFormat; 488 | 489 | out.SetFormatter( customFormat ); 490 | 491 | INFO( _LG( "This info message output has trailing periods") ); 492 | WARN( _LG( "This warning message is surrounded by exclamation marks") ); 493 | } 494 | 495 | LOGOG_SHUTDOWN(); 496 | 497 | return 0; 498 | } 499 | //! [FormatterModified] 500 | 501 | UNITTEST( HelloLogog ) 502 | { 503 | //! [HelloLogog] 504 | 505 | /* The LOGOG_INITIALIZE() function must be called before we call 506 | * any other logog functions. 507 | */ 508 | LOGOG_INITIALIZE(); 509 | 510 | { 511 | /* In order to see any output, we have to instance a Target object, 512 | * such as a Cerr or a Cout. Additionally, we have to destroy 513 | * this object before calling LOGOG_SHUTDOWN(). This 514 | * is why we have this object within these enclosing brackets. 515 | */ 516 | Cout out; 517 | 518 | /* Send some debugging information to any targets that have 519 | * been instanced. 520 | */ 521 | /* If you're just getting started, and you haven't defined 522 | * LOGOG_UNICODE, then ASCII logging is easy and works 523 | * out of the box. 524 | */ 525 | #ifndef LOGOG_UNICODE 526 | INFO("Hello, logog!"); 527 | WARN("This is a warning"); 528 | #endif // !LOGOG_UNICODE 529 | 530 | /* The _LG() macro around static text permits the given text to 531 | * display correctly in both Unicode and ASCII builds of the 532 | * logog library. 533 | */ 534 | ERR( _LG( "This is an error") ); 535 | DBUG( _LG( "This is debugging info") ); 536 | 537 | /* The Cout object is destroyed here because it falls out of 538 | * scope. */ 539 | } 540 | 541 | /* Call LOGOG_SHUTDOWN() at the termination of your program to free 542 | * all memory allocated by logog. Make sure no logog objects exist 543 | * when you call LOGOG_SHUTDOWN(). 544 | */ 545 | LOGOG_SHUTDOWN(); 546 | 547 | /* Depending on your compiler, the output of the preceding code is 548 | * something like: 549 | * 550 | * test.cpp(373) : info: Hello, logog! 551 | * test.cpp(374) : warning: This is a warning 552 | * test.cpp(375) : error: This is an error 553 | * test.cpp(376) : debug: This is debugging info 554 | */ 555 | //! [HelloLogog] 556 | return 0; 557 | } 558 | 559 | 560 | 561 | UNITTEST( GroupCategory1 ) 562 | { 563 | //! [GroupCategory1] 564 | /* 565 | The following example produces something like: 566 | .\test.cpp(364) : emergency: {Graphics} [Unrecoverable] The graphics card has been destroyed 567 | .\test.cpp(368) : warning: {Graphics} [Recoverable] The graphics card has been replaced 568 | .\test.cpp(372) : warning: {Audio} [Recoverable] The headphones are unplugged 569 | .\test.cpp(377) : info: Everything's back to normal 570 | */ 571 | 572 | LOGOG_INITIALIZE(); 573 | 574 | { 575 | Cerr err; 576 | 577 | #undef LOGOG_GROUP 578 | #undef LOGOG_CATEGORY 579 | #define LOGOG_GROUP "Graphics" 580 | #define LOGOG_CATEGORY "Unrecoverable" 581 | 582 | EMERGENCY(_LG("The graphics card has been destroyed")); 583 | 584 | #undef LOGOG_CATEGORY 585 | #define LOGOG_CATEGORY "Recoverable" 586 | 587 | WARN(_LG("The graphics card has been replaced")); 588 | 589 | #undef LOGOG_GROUP 590 | #define LOGOG_GROUP "Audio" 591 | 592 | WARN(_LG("The headphones are unplugged")); 593 | 594 | #undef LOGOG_CATEGORY 595 | #undef LOGOG_GROUP 596 | #define LOGOG_CATEGORY NULL 597 | #define LOGOG_GROUP NULL 598 | 599 | INFO(_LG("Everything's back to... %s!"), _LG("normal")); 600 | } 601 | 602 | LOGOG_SHUTDOWN(); 603 | //! [GroupCategory1] 604 | return 0; 605 | } 606 | 607 | UNITTEST( GroupCategory2 ) 608 | { 609 | //! [GroupCategory2] 610 | LOGOG_INITIALIZE(); 611 | 612 | { 613 | GetFilterDefault().Category(_LG("Unrecoverable")); 614 | Cerr err; 615 | 616 | WARN(_LG("Logging messages in the Unrecoverable category...")); 617 | 618 | 619 | #undef LOGOG_GROUP 620 | #undef LOGOG_CATEGORY 621 | #define LOGOG_GROUP "Graphics" 622 | #define LOGOG_CATEGORY "Unrecoverable" 623 | 624 | EMERGENCY(_LG("The graphics card has been destroyed")); 625 | 626 | #undef LOGOG_CATEGORY 627 | #define LOGOG_CATEGORY "Recoverable" 628 | 629 | WARN(_LG("The graphics card has been replaced")); 630 | 631 | #undef LOGOG_GROUP 632 | #define LOGOG_GROUP "Audio" 633 | 634 | WARN(_LG("The headphones are unplugged")); 635 | 636 | #undef LOGOG_CATEGORY 637 | #undef LOGOG_GROUP 638 | #define LOGOG_CATEGORY NULL 639 | #define LOGOG_GROUP NULL 640 | 641 | } 642 | 643 | LOGOG_SHUTDOWN(); 644 | //! [GroupCategory2] 645 | return 0; 646 | } 647 | 648 | UNITTEST( GroupCategory3 ) 649 | { 650 | LOGOG_INITIALIZE(); 651 | 652 | { 653 | /* Assigning a group twice does not leak memory. */ 654 | GetFilterDefault().Group(_LG("Controller")); 655 | GetFilterDefault().Group(_LG("Graphics")); 656 | 657 | Cerr err; 658 | WARN(_LG("This message won't happen because it's not in the Graphics group")); 659 | 660 | #undef LOGOG_GROUP 661 | #undef LOGOG_CATEGORY 662 | #define LOGOG_GROUP "Graphics" 663 | #define LOGOG_CATEGORY "Unrecoverable" 664 | 665 | EMERGENCY(_LG("The graphics card has been destroyed")); 666 | 667 | #undef LOGOG_CATEGORY 668 | #define LOGOG_CATEGORY "Recoverable" 669 | 670 | WARN(_LG("The graphics card has been replaced")); 671 | 672 | #undef LOGOG_GROUP 673 | #define LOGOG_GROUP "Audio" 674 | 675 | WARN(_LG("The headphones are unplugged")); 676 | 677 | #undef LOGOG_CATEGORY 678 | #undef LOGOG_GROUP 679 | #define LOGOG_CATEGORY NULL 680 | #define LOGOG_GROUP NULL 681 | } 682 | 683 | LOGOG_SHUTDOWN(); 684 | return 0; 685 | } 686 | 687 | UNITTEST( GroupCategory4 ) 688 | { 689 | //! [GroupCategory4] 690 | LOGOG_INITIALIZE(); 691 | 692 | { 693 | GetFilterDefault().Group(_LG("Graphics")); 694 | Filter filter; 695 | filter.Group(_LG("Audio")); 696 | Cerr err; 697 | 698 | WARN(_LG("This message won't happen because it's not in the Graphics group")); 699 | 700 | #undef LOGOG_GROUP 701 | #undef LOGOG_CATEGORY 702 | #define LOGOG_GROUP "Graphics" 703 | #define LOGOG_CATEGORY "Unrecoverable" 704 | 705 | EMERGENCY(_LG("The graphics card has been destroyed")); 706 | 707 | #undef LOGOG_CATEGORY 708 | #define LOGOG_CATEGORY "Recoverable" 709 | 710 | WARN(_LG("The graphics card has been replaced")); 711 | 712 | #undef LOGOG_GROUP 713 | #define LOGOG_GROUP "Audio" 714 | 715 | WARN(_LG("The headphones are unplugged")); 716 | 717 | #undef LOGOG_GROUP 718 | #define LOGOG_GROUP "Inputs" 719 | 720 | WARN(_LG("The inputs have been yanked off but this fact won't be reported!")); 721 | 722 | #undef LOGOG_CATEGORY 723 | #undef LOGOG_GROUP 724 | #define LOGOG_CATEGORY NULL 725 | #define LOGOG_GROUP NULL 726 | } 727 | 728 | LOGOG_SHUTDOWN(); 729 | //! [GroupCategory4] 730 | return 0; 731 | } 732 | 733 | 734 | UNITTEST( Info1 ) 735 | { 736 | LOGOG_INITIALIZE(); 737 | { 738 | Cout out; 739 | 740 | for ( int i = 0; i < 2; i++ ) 741 | { 742 | for ( int t = 0; t < 10; t++ ) 743 | { 744 | INFO( _LG("t is now: %d"), t ); 745 | if ( t > 8 ) 746 | WARN(_LG("t is pretty high now!")); 747 | } 748 | 749 | DBUG(_LG("This warning isn't very interesting")); 750 | EMERGENCY(_LG("THIS IS SUPER IMPORTANT!")); 751 | } 752 | 753 | LOGOG_SET_LEVEL( LOGOG_LEVEL_DEBUG ); 754 | LOGOG_DEBUG(_LG("Messages instantiated for the first time will be called now. However, debug messages")); 755 | LOGOG_DEBUG(_LG("instantiated before the LOGOG_SET_LEVEL won't be transmitted.")); 756 | 757 | } 758 | 759 | LOGOG_SHUTDOWN(); 760 | 761 | return 0; 762 | } 763 | 764 | void GeneratePseudoRandomErrorMessages() 765 | { 766 | int pr = 0xf8d92347; 767 | int ps; 768 | 769 | for ( int t = 0; t < TEST_STRESS_LEVEL * 10; t++ ) 770 | { 771 | pr = pr * 0xd9381381 + 0x13d7b; 772 | ps = pr % ( (1 << 19) - 1 ); 773 | 774 | ERR( _LG("We must inform you of this pseudo-random error code: %d"), ps ); 775 | 776 | // Do a non specific amount of busy work. 777 | int swap1 = 0, swap2 = 1; 778 | for ( int i = 0; i < ps; i++ ) 779 | { 780 | int swap3 = swap1; 781 | swap1 = swap2; 782 | swap2 = swap3; 783 | } 784 | } 785 | 786 | } 787 | 788 | UNITTEST( ImmediateLogging ) 789 | { 790 | LOGOG_INITIALIZE(); 791 | 792 | { 793 | LogFile logFile( "log.txt" ); 794 | 795 | GeneratePseudoRandomErrorMessages(); 796 | } 797 | 798 | LOGOG_SHUTDOWN(); 799 | 800 | return 0; 801 | } 802 | 803 | #ifdef LOGOG_UNICODE 804 | UNITTEST( UnicodeLogFile ) 805 | { 806 | LOGOG_INITIALIZE(); 807 | 808 | { 809 | LogFile logFile( "unicode.txt" ); 810 | 811 | // see http://blogs.msdn.com/b/michkap/archive/2008/03/18/8306597.aspx 812 | INFO(L"\x043a\x043e\x0448\x043a\x0430 \x65e5\x672c\x56fd"); 813 | WARN(L"\x043a\x043e\x0448\x043a\x0430 \x65e5\x672c\x56fd"); 814 | ERR(L"\x043a\x043e\x0448\x043a\x0430 \x65e5\x672c\x56fd"); 815 | } 816 | 817 | LOGOG_SHUTDOWN(); 818 | 819 | return 0; 820 | } 821 | #endif // LOGOG_UNICODE 822 | 823 | UNITTEST( DeferredCoutLogging ) 824 | { 825 | LOGOG_INITIALIZE(); 826 | 827 | { 828 | Cout out; 829 | LogBuffer logBuffer( &out ); 830 | 831 | // Make sure that out does not receive messages via the general filter mechanism. 832 | out.UnsubscribeToMultiple( AllFilters() ); 833 | 834 | for ( int i = 1; i <= 10; i++ ) 835 | { 836 | ERR(_LG("This is error %d of 10"), i); 837 | 838 | int q = 27832; 839 | 840 | for ( int j = 0; j < TEST_STRESS_LEVEL * 10000000; j++ ) 841 | q *= q; 842 | } 843 | } 844 | 845 | LOGOG_SHUTDOWN(); 846 | 847 | return 0; 848 | } 849 | 850 | UNITTEST( DeferredFileLogging ) 851 | { 852 | //! [DeferredFileLogging] 853 | LOGOG_INITIALIZE(); 854 | 855 | { 856 | LogFile logFile( "log.txt" ); 857 | LogBuffer logBuffer( &logFile ); 858 | 859 | /* Because the LogBuffer is not writing to a line device a la stdout or stderr, it does not need to 860 | * send null terminated strings to its destination (the LogFile). This is a particular peculiarity 861 | * of having a buffering target to a serial-type target, such as a socket or a file. 862 | */ 863 | logBuffer.SetNullTerminatesStrings( false ); 864 | 865 | // Make sure that the log file does not receive messages via the general filter mechanism. 866 | logFile.UnsubscribeToMultiple( AllFilters() ); 867 | 868 | for ( int i = 1; i <= 20; i++ ) 869 | { 870 | WARN(_LG("This is warning %d of 20"), i); 871 | 872 | int q = 27832; 873 | for ( int j = 0; j < TEST_STRESS_LEVEL * 10000000; j++ ) 874 | q *= q; 875 | } 876 | } 877 | 878 | LOGOG_SHUTDOWN(); 879 | //! [DeferredFileLogging] 880 | return 0; 881 | } 882 | 883 | UNITTEST( BufferedLoggingWithPeriodicDumping ) 884 | { 885 | //! [BufferedLoggingWithPeriodicDumping] 886 | LOGOG_INITIALIZE(); 887 | { 888 | /* Avoid output buffering on the log file by providing false to the 889 | * bEnableOutputBuffering parameter. 890 | */ 891 | LogFile logFile("log.txt", false); 892 | LogBuffer logBuffer(&logFile); 893 | logBuffer.SetNullTerminatesStrings(false); 894 | logFile.UnsubscribeToMultiple(AllFilters()); 895 | 896 | Formatter *pFormatter = &GetDefaultFormatter(); 897 | pFormatter->SetShowTimeOfDay(true); 898 | 899 | ERR(_LG("This is an error")); 900 | DBUG(_LG("This is debugging information")); 901 | /* At this point the LogBuffer will flush to the LogFile, and the 902 | * LogFile will immediately flush its output to the file system. 903 | */ 904 | logBuffer.Dump(); 905 | 906 | WARN(_LG("a WARNING MESSAGE WITH number %.3f"), 0.43543); 907 | /* The LogBuffer will immediately be flushed again and all log 908 | * information written to the file. 909 | */ 910 | logBuffer.Dump(); 911 | 912 | } 913 | LOGOG_SHUTDOWN(); 914 | //! [BufferedLoggingWithPeriodicDumping] 915 | 916 | return 0; 917 | } 918 | 919 | UNITTEST( DateAndTime ) 920 | { 921 | //! [DateAndTimeLogging] 922 | LOGOG_INITIALIZE(); 923 | 924 | { 925 | Cerr err; 926 | 927 | Formatter *pFormatter = &GetDefaultFormatter(); 928 | 929 | pFormatter->SetShowTimeOfDay( true ); 930 | 931 | for ( int i = 1; i <= 20; i++ ) 932 | { 933 | WARN(_LG("This is warning %d of 20... but with time!"), i); 934 | 935 | int q = 27832; 936 | for ( int j = 0; j < TEST_STRESS_LEVEL * 10000000; j++ ) 937 | q *= q; 938 | } 939 | } 940 | 941 | LOGOG_SHUTDOWN(); 942 | //! [DateAndTimeLogging] 943 | 944 | return 0; 945 | } 946 | 947 | UNITTEST ( InitializingStatements ) 948 | { 949 | for ( int i = 1; i <= 2; i++ ) 950 | if ( i == 1 ) 951 | LOGOG_INITIALIZE(); 952 | else if (i == 2) 953 | LOGOG_SHUTDOWN(); 954 | return 0; 955 | } 956 | 957 | UNITTEST ( LoggingInsideIfThenStatement ) 958 | { 959 | LOGOG_INITIALIZE(); 960 | { 961 | Cerr err; 962 | for ( int i = 1; i <= 11; i++ ) 963 | if ( i == 1 ) 964 | EMERGENCY(_LG("This message is EMERGENCY (%d)"), i); 965 | else if ( i == 2 ) 966 | ALERT(_LG("This message is ALERT (%d)"), i); 967 | else if ( i == 3 ) 968 | CRITICAL(_LG("This message is CRITICAL (%d)"), i); 969 | else if ( i == 4 ) 970 | ERR(_LG("This message is ERROR (%d)"), i); 971 | else if ( i == 5 ) 972 | WARN(_LG("This message is WARN (%d)"), i); 973 | else if ( i == 6 ) 974 | WARN1(_LG("This message is WARN1 (%d)"), i); 975 | else if ( i == 7 ) 976 | WARN2(_LG("This message is WARN2 (%d)"), i); 977 | else if ( i == 8 ) 978 | WARN3(_LG("This message is WARN3 (%d)"), i); 979 | else if ( i == 9 ) 980 | INFO(_LG("This message is INFO (%d)"), i); 981 | else if ( i == 10 ) 982 | DBUG(_LG("This message is DEBUG (%d)"), i); 983 | } 984 | LOGOG_SHUTDOWN(); 985 | return 0; 986 | } 987 | 988 | UNITTEST(ChangeLevelDynamically) 989 | { 990 | LOGOG_LEVEL_TYPE levels[] = { 991 | LOGOG_LEVEL_NONE, 992 | LOGOG_LEVEL_EMERGENCY, 993 | LOGOG_LEVEL_ALERT, 994 | LOGOG_LEVEL_CRITICAL, 995 | LOGOG_LEVEL_ERROR, 996 | LOGOG_LEVEL_WARN, 997 | LOGOG_LEVEL_WARN1, 998 | LOGOG_LEVEL_WARN2, 999 | LOGOG_LEVEL_WARN3, 1000 | LOGOG_LEVEL_INFO, 1001 | LOGOG_LEVEL_DEBUG, 1002 | LOGOG_LEVEL_ALL 1003 | }; 1004 | 1005 | /* All this double casting may seem like overkill but gcc complains unless we do it. */ 1006 | const LOGOG_CHAR *levelDescriptions[] = { 1007 | (const LOGOG_CHAR *)_LG("LOGOG_LEVEL_NONE"), 1008 | (const LOGOG_CHAR *)_LG("LOGOG_LEVEL_EMERGENCY"), 1009 | (const LOGOG_CHAR *)_LG("LOGOG_LEVEL_ALERT"), 1010 | (const LOGOG_CHAR *)_LG("LOGOG_LEVEL_CRITICAL"), 1011 | (const LOGOG_CHAR *)_LG("LOGOG_LEVEL_ERROR"), 1012 | (const LOGOG_CHAR *)_LG("LOGOG_LEVEL_WARN"), 1013 | (const LOGOG_CHAR *)_LG("LOGOG_LEVEL_WARN1"), 1014 | (const LOGOG_CHAR *)_LG("LOGOG_LEVEL_WARN2"), 1015 | (const LOGOG_CHAR *)_LG("LOGOG_LEVEL_WARN3"), 1016 | (const LOGOG_CHAR *)_LG("LOGOG_LEVEL_INFO"), 1017 | (const LOGOG_CHAR *)_LG("LOGOG_LEVEL_DEBUG"), 1018 | (const LOGOG_CHAR *)_LG("LOGOG_LEVEL_ALL") 1019 | }; 1020 | 1021 | int numLevels = 0; 1022 | while (levels[numLevels++] != LOGOG_LEVEL_ALL) 1023 | ; 1024 | numLevels--; 1025 | 1026 | LOGOG_INITIALIZE(); 1027 | { 1028 | Cerr err; 1029 | 1030 | int i, id; 1031 | i = numLevels / 2; 1032 | id = 1; 1033 | 1034 | for (int j = 0; j <= 100 * TEST_STRESS_LEVEL; j++) 1035 | { 1036 | EMERGENCY(_LG("Setting default level to %s..."), levelDescriptions[i]); 1037 | SetDefaultLevel(levels[i]); 1038 | 1039 | EMERGENCY(_LG("This message is EMERGENCY (%d)"), i); 1040 | ALERT(_LG("This message is ALERT (%d)"), i); 1041 | CRITICAL(_LG("This message is CRITICAL (%d)"), i); 1042 | ERR(_LG("This message is ERROR (%d)"), i); 1043 | WARN(_LG("This message is WARN (%d)"), i); 1044 | WARN1(_LG("This message is WARN1 (%d)"), i); 1045 | WARN2(_LG("This message is WARN2 (%d)"), i); 1046 | WARN3(_LG("This message is WARN3 (%d)"), i); 1047 | INFO(_LG("This message is INFO (%d)"), i); 1048 | DBUG(_LG("This message is DEBUG (%d)"), i); 1049 | 1050 | i += id; 1051 | if ((i == 0) || (i == numLevels)) 1052 | id = -id; 1053 | } 1054 | } 1055 | 1056 | LOGOG_SHUTDOWN(); 1057 | return 0; 1058 | } 1059 | 1060 | #ifndef LOGOG_UNICODE 1061 | UNITTEST ( SetTimeFormat ) 1062 | { 1063 | LOGOG_INITIALIZE(); 1064 | { 1065 | struct formatAndDescription { 1066 | const char *format; 1067 | const LOGOG_CHAR *description; 1068 | }; 1069 | 1070 | //Testing strtime support varies from one compiler to the other 1071 | //This test works in Visual Studio 2010 and up, as well as GCC 4.8.2 1072 | static formatAndDescription timeFormats[] = 1073 | { 1074 | { LOGOG_DEFAULT_TIME_FORMAT, _LG("The default LOGOG_DEFAULT_TIME_FORMAT") }, 1075 | { "%a", _LG("Abbreviated weekday name") }, 1076 | { "%A", _LG("Full weekday name") }, 1077 | { "%b", _LG("Abbreviated month name") }, 1078 | { "%B", _LG("Full month name") }, 1079 | { "%c", _LG("Date and time representation appropriate for locale") }, 1080 | { "%d", _LG("Day of month as decimal number (01 31)") }, 1081 | { "%H", _LG("Hour in 24-hour format (00 - 23)") }, 1082 | { "%I", _LG("Hour in 12-hour format (01 - 12)") }, 1083 | { "%j", _LG("Day of year as decimal number (001 - 366)") }, 1084 | { "%m", _LG("Month as decimal number (01 - 12)") }, 1085 | { "%M", _LG("Minute as decimal number (00 - 59)") }, 1086 | { "%p", _LG("Current locale's A.M./P.M. indicator for 12-hour clock") }, 1087 | { "%S", _LG("Second as decimal number (00 - 59)") }, 1088 | { "%U", _LG("Week of year as decimal number, with Sunday as first day of week (00 - 53)") }, 1089 | { "%w", _LG("Weekday as decimal number (0 - 6; Sunday is 0)") }, 1090 | { "%W", _LG("Week of year as decimal number, with Monday as first day of week (00 - 53)") }, 1091 | { "%x", _LG("Date representation for current locale") }, 1092 | { "%X", _LG("Time representation for current locale") }, 1093 | { "%y", _LG("Year without century, as decimal number (00 - 99)") }, 1094 | { "%Y", _LG("Year with century, as decimal number") }, 1095 | { "%Z", _LG("Either the time-zone name or time zone abbreviation; no characters if time zone is unknown") }, 1096 | { "%%", _LG("Percent sign") }, 1097 | }; 1098 | 1099 | Cerr err; 1100 | 1101 | Formatter *pFormatter = &GetDefaultFormatter(); 1102 | 1103 | pFormatter->SetShowTimeOfDay( true ); 1104 | 1105 | for(size_t i=0; i < (sizeof(timeFormats)/sizeof(*timeFormats)); ++i) 1106 | { 1107 | pFormatter->SetTimeOfDayFormat(timeFormats[i].format); 1108 | 1109 | EMERGENCY(_LG("Testing time format %s"), timeFormats[i].description); 1110 | } 1111 | } 1112 | LOGOG_SHUTDOWN(); 1113 | return 0; 1114 | } 1115 | #endif // LOGOG_UNICODE 1116 | 1117 | #ifndef LOGOG_TARGET_PS3 1118 | int DoPlatformSpecificTestInitialization() 1119 | { 1120 | return 0; 1121 | } 1122 | #endif 1123 | int main( int , char ** ) 1124 | { 1125 | #ifdef LOGOG_LEAK_DETECTION_WINDOWS 1126 | _CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF | _CRTDBG_CHECK_ALWAYS_DF ); 1127 | #endif // LOGOG_LEAK_DETECTION_WINDOWS 1128 | 1129 | //! [WindowsUnicodeSetup] 1130 | #ifdef LOGOG_UNICODE 1131 | #ifdef LOGOG_FLAVOR_WINDOWS 1132 | _setmode(_fileno(stdout), _O_U16TEXT); 1133 | _setmode(_fileno(stderr), _O_U16TEXT); 1134 | #endif // LOGOG_FLAVOR_WINDOWS 1135 | #endif // LOGOG_UNICODE 1136 | //! [WindowsUnicodeSetup] 1137 | 1138 | int nPlatformSpecific; 1139 | nPlatformSpecific = DoPlatformSpecificTestInitialization(); 1140 | if ( nPlatformSpecific != 0) 1141 | return nPlatformSpecific; 1142 | 1143 | int nResult; 1144 | nResult = RunAllTests(); 1145 | ShutdownTests(); 1146 | 1147 | #ifdef LOGOG_LEAK_DETECTION_WINDOWS 1148 | _CrtMemState crtMemState; 1149 | _CrtMemCheckpoint( &crtMemState ); 1150 | _CrtMemDumpStatistics( &crtMemState ); 1151 | _CrtDumpMemoryLeaks(); 1152 | #endif // LOGOG_LEAK_DETECTION_WINDOWS 1153 | 1154 | return nResult; 1155 | } 1156 | 1157 | --------------------------------------------------------------------------------