//============ Copyright (c) Valve Corporation, All rights reserved. ============
//
// Logging system definitions.
//
//===============================================================================

#include "pch_tier0.h"
#include "logging.h"

#include <string.h>
#include "dbg.h"
#include "threadtools.h"
#include "tier0_strtools.h" // this is from tier1, but only included for inline definition of V_isspace

#ifdef _PS3
#include <sys/tty.h>
#endif


//////////////////////////////////////////////////////////////////////////
// Define commonly used channels here
//////////////////////////////////////////////////////////////////////////
DEFINE_LOGGING_CHANNEL_NO_TAGS( LOG_GENERAL, "General" );

DEFINE_LOGGING_CHANNEL_NO_TAGS( LOG_ASSERT, "Assert" );

// Corresponds to ConMsg/ConWarning/etc. with a level <= 1.
// Only errors are spewed by default.
BEGIN_DEFINE_LOGGING_CHANNEL( LOG_CONSOLE, "Console", LCF_CONSOLE_ONLY, LS_ERROR );
ADD_LOGGING_CHANNEL_TAG( "Console" );
END_DEFINE_LOGGING_CHANNEL();

// Corresponds to DevMsg/DevWarning/etc. with a level <= 1.
// Only errors are spewed by default.
BEGIN_DEFINE_LOGGING_CHANNEL( LOG_DEVELOPER, "Developer", LCF_CONSOLE_ONLY, LS_ERROR );
ADD_LOGGING_CHANNEL_TAG( "Developer" );
END_DEFINE_LOGGING_CHANNEL();

// Corresponds to ConMsg/ConWarning/etc. with a level >= 2.
// Only errors are spewed by default.
BEGIN_DEFINE_LOGGING_CHANNEL( LOG_DEVELOPER_CONSOLE, "DeveloperConsole", LCF_CONSOLE_ONLY, LS_ERROR );
ADD_LOGGING_CHANNEL_TAG( "DeveloperVerbose" );
ADD_LOGGING_CHANNEL_TAG( "Console" );
END_DEFINE_LOGGING_CHANNEL();

// Corresponds to DevMsg/DevWarning/etc, with a level >= 2.
// Only errors are spewed by default.
BEGIN_DEFINE_LOGGING_CHANNEL( LOG_DEVELOPER_VERBOSE, "DeveloperVerbose", LCF_CONSOLE_ONLY, LS_ERROR, Color( 192, 128, 192, 255 ) );
ADD_LOGGING_CHANNEL_TAG( "DeveloperVerbose" );
END_DEFINE_LOGGING_CHANNEL();

//////////////////////////////////////////////////////////////////////////
// Globals
//////////////////////////////////////////////////////////////////////////

// The index of the logging state used by the current thread.  This defaults to 0 across all threads, 
// which indicates that the global listener set should be used (CLoggingSystem::m_nGlobalStateIndex).
//
// NOTE:
// Because our linux TLS implementation does not support embedding a thread local
// integer in a class, the logging system must use a global thread-local integer.
// This means that we can only have one instance of CLoggingSystem, although
// we could support additional instances if we are willing to lose support for
// thread-local spew handling.
// There is no other reason why this class must be a singleton, except
// for the fact that there's no reason to have more than one in existence.
bool g_bEnforceLoggingSystemSingleton = false;

#ifdef _PS3
#include "tls_ps3.h"
#else // _PS3
CTHREADLOCALINT g_nThreadLocalStateIndex;
#endif // _PS3

//////////////////////////////////////////////////////////////////////////
// Implementation
//////////////////////////////////////////////////////////////////////////

CLoggingSystem *g_pGlobalLoggingSystem = NULL;

// This function does not get inlined due to the static variable :(
CLoggingSystem *GetGlobalLoggingSystem_Internal()
{
	static CLoggingSystem globalLoggingSystem;
	g_pGlobalLoggingSystem = &globalLoggingSystem;
	return &globalLoggingSystem;
}

// This function can get inlined
CLoggingSystem *GetGlobalLoggingSystem()
{
	return ( g_pGlobalLoggingSystem == NULL ) ? GetGlobalLoggingSystem_Internal() : g_pGlobalLoggingSystem;
}

CLoggingSystem::CLoggingSystem() : 
m_nChannelCount( 0 ), 
m_nChannelTagCount( 0 ),
m_nTagNamePoolIndex( 0 ),
m_nGlobalStateIndex( 0 )
{ 
	Assert( !g_bEnforceLoggingSystemSingleton );
	g_bEnforceLoggingSystemSingleton = true;
#if !defined( _PS3 ) && !defined(POSIX) && !defined(PLATFORM_WINDOWS)
	// Due to uncertain constructor ordering (g_nThreadLocalStateIndex
	// may not be constructed yet so TLS index may not be available yet)
	// we cannot initialize the state index here without risking
	// AppVerifier errors and undefined behavior. Luckily TlsAlloc values
	// are guaranteed to be zero-initialized so we don't need to zero-init,
	// this, and in fact we can't for all threads.
	// TLS on PS3 is zero-initialized in global ELF section
	// TLS is also not accessible at this point before PRX entry point runs
	g_nThreadLocalStateIndex = 0;
#endif

	m_LoggingStates[0].m_nPreviousStackEntry = -1;

	m_LoggingStates[0].m_nListenerCount = 1;
	m_LoggingStates[0].m_RegisteredListeners[0] = &m_DefaultLoggingListener;
	m_LoggingStates[0].m_pLoggingResponse = &m_DefaultLoggingResponse;

	// Mark all other logging state blocks as unused.
	for ( int i = 1; i < MAX_LOGGING_STATE_COUNT; ++ i )
	{
		m_LoggingStates[i].m_nListenerCount = -1;
	}

	m_pStateMutex = NULL;
}

CLoggingSystem::~CLoggingSystem()
{
	g_bEnforceLoggingSystemSingleton = false;
	delete m_pStateMutex;
}

LoggingChannelID_t CLoggingSystem::RegisterLoggingChannel( const char *pChannelName, RegisterTagsFunc registerTagsFunc, int flags, LoggingSeverity_t severity, Color spewColor )
{
	if ( m_nChannelCount >= MAX_LOGGING_CHANNEL_COUNT )
	{
		// Out of logging channels... catastrophic fail!
		Log_Error( LOG_GENERAL, "Out of logging channels.\n" );
		Assert( 0 );
		return INVALID_LOGGING_CHANNEL_ID;
	}
	else
	{
		// Channels can be multiply defined, in which case return the ID of the existing channel.
		for ( int i = 0; i < m_nChannelCount; ++ i )
		{
			if ( V_tier0_stricmp( m_RegisteredChannels[i].m_Name, pChannelName ) == 0 )
			{
				// OK to call the tag registration callback; duplicates will be culled away.
				// This allows multiple people to register a logging channel, and the union of all tags will be registered.
				if ( registerTagsFunc != NULL )
				{
					registerTagsFunc();
				}

				// If a logging channel is registered multiple times, only one of the registrations should specify flags/severity/color.
				if ( m_RegisteredChannels[i].m_Flags == 0 && m_RegisteredChannels[i].m_MinimumSeverity == LS_MESSAGE && m_RegisteredChannels[i].m_SpewColor == UNSPECIFIED_LOGGING_COLOR )
				{
					m_RegisteredChannels[i].m_Flags = ( LoggingChannelFlags_t )flags;
					m_RegisteredChannels[i].m_MinimumSeverity = severity;
					m_RegisteredChannels[i].m_SpewColor = spewColor;
				}
				else
				{
					AssertMsg( flags == 0 || flags == m_RegisteredChannels[i].m_Flags, "Non-zero or mismatched flags specified in logging channel re-registration!" );
					AssertMsg( severity == LS_MESSAGE || severity == m_RegisteredChannels[i].m_MinimumSeverity, "Non-default or mismatched severity specified in logging channel re-registration!" );
					AssertMsg( spewColor == UNSPECIFIED_LOGGING_COLOR || spewColor == m_RegisteredChannels[i].m_SpewColor, "Non-default or mismatched color specified in logging channel re-registration!" );
				}

				return m_RegisteredChannels[i].m_ID;
			}
		}

		m_RegisteredChannels[m_nChannelCount].m_ID = m_nChannelCount;
		m_RegisteredChannels[m_nChannelCount].m_Flags = ( LoggingChannelFlags_t )flags;
		m_RegisteredChannels[m_nChannelCount].m_MinimumSeverity = severity;
		m_RegisteredChannels[m_nChannelCount].m_SpewColor = spewColor;
		strncpy( m_RegisteredChannels[m_nChannelCount].m_Name, pChannelName, MAX_LOGGING_IDENTIFIER_LENGTH );
		
		if ( registerTagsFunc != NULL ) 
		{
			registerTagsFunc();
		}
		return m_nChannelCount ++;
	}
}

LoggingChannelID_t CLoggingSystem::FindChannel( const char *pChannelName ) const
{
	for ( int i = 0; i < m_nChannelCount; ++ i )
	{
		if ( V_tier0_stricmp( m_RegisteredChannels[i].m_Name, pChannelName ) == 0 )
		{
			return i;
		}
	}

	return INVALID_LOGGING_CHANNEL_ID;
}

void CLoggingSystem::AddTagToCurrentChannel( const char *pTagName )
{
	// Add tags at the head of the tag-list of the most recently added channel.
	LoggingChannel_t *pChannel = &m_RegisteredChannels[m_nChannelCount];

	// First check for duplicates
	if ( pChannel->HasTag( pTagName ) )
	{
		return;
	}
	
	LoggingTag_t *pTag = AllocTag( pTagName );
	
	pTag->m_pNextTag = pChannel->m_pFirstTag;
	pChannel->m_pFirstTag = pTag;
}

void CLoggingSystem::SetChannelSpewLevel( LoggingChannelID_t channelID, LoggingSeverity_t minimumSeverity )
{
	GetChannel( channelID )->SetSpewLevel( minimumSeverity );
}

void CLoggingSystem::SetChannelSpewLevelByName( const char *pName, LoggingSeverity_t minimumSeverity )
{
	for ( int i = 0; i < m_nChannelCount; ++ i )
	{
		if ( V_tier0_stricmp( m_RegisteredChannels[i].m_Name, pName ) == 0 )
		{
			m_RegisteredChannels[i].SetSpewLevel( minimumSeverity );
		}
	}
}

void CLoggingSystem::SetChannelSpewLevelByTag( const char *pTag, LoggingSeverity_t minimumSeverity )
{
	for ( int i = 0; i < m_nChannelCount; ++ i )
	{
		if ( m_RegisteredChannels[i].HasTag( pTag ) )
		{
			m_RegisteredChannels[i].SetSpewLevel( minimumSeverity );
		}
	}
}

void CLoggingSystem::PushLoggingState( bool bThreadLocal, bool bClearState )
{
	if ( !m_pStateMutex )
		m_pStateMutex = new CThreadFastMutex();

	m_pStateMutex->Lock();
	
	int nNewState = FindUnusedStateIndex();
	// Ensure we're not out of state blocks.
	Assert( nNewState != -1 );

	int nCurrentState = bThreadLocal ? (int)g_nThreadLocalStateIndex : m_nGlobalStateIndex;

	if ( bClearState )
	{
		m_LoggingStates[nNewState].m_nListenerCount = 0;
		m_LoggingStates[nNewState].m_pLoggingResponse = &m_DefaultLoggingResponse;	
	}
	else
	{
		m_LoggingStates[nNewState] = m_LoggingStates[nCurrentState];
	}

	m_LoggingStates[nNewState].m_nPreviousStackEntry = nCurrentState;

	if ( bThreadLocal )
	{
		g_nThreadLocalStateIndex = nNewState;
	}
	else
	{
		m_nGlobalStateIndex = nNewState;
	}

	m_pStateMutex->Unlock();
}

void CLoggingSystem::PopLoggingState( bool bThreadLocal )
{
	if ( !m_pStateMutex )
		m_pStateMutex = new CThreadFastMutex();

	m_pStateMutex->Lock();

	int nCurrentState = bThreadLocal ? (int)g_nThreadLocalStateIndex : m_nGlobalStateIndex;
	
	// Shouldn't be less than 0 (implies error during Push()) or 0 (implies that Push() was never called)
	Assert( nCurrentState > 0 );

	// Mark the current state as unused.
	m_LoggingStates[nCurrentState].m_nListenerCount = -1;

	if ( bThreadLocal )
	{
		g_nThreadLocalStateIndex = m_LoggingStates[nCurrentState].m_nPreviousStackEntry;
	}
	else
	{
		m_nGlobalStateIndex = m_LoggingStates[nCurrentState].m_nPreviousStackEntry;
	}

	m_pStateMutex->Unlock();
}

void CLoggingSystem::RegisterLoggingListener( ILoggingListener *pListener )
{
	if ( !m_pStateMutex )
		m_pStateMutex = new CThreadFastMutex();

	m_pStateMutex->Lock();
	LoggingState_t *pState = GetCurrentState();
	if ( pState->m_nListenerCount > MAX_LOGGING_CHANNEL_COUNT )
	{
		// Out of logging listener slots... catastrophic fail!
		Assert( 0 );
	}
	else
	{
		pState->m_RegisteredListeners[pState->m_nListenerCount] = pListener;
		++ pState->m_nListenerCount;
	}
	m_pStateMutex->Unlock();
}

void CLoggingSystem::UnregisterLoggingListener( ILoggingListener *pListener )
{
	if ( !m_pStateMutex )
		m_pStateMutex = new CThreadFastMutex();

	m_pStateMutex->Lock();
	LoggingState_t *pState = GetCurrentState();
	for ( int i = 0; i < pState->m_nListenerCount; ++ i )
	{
		if ( pState->m_RegisteredListeners[i] == pListener )
		{
			// Shuffle all the listeners ahead over these, and reduce the count.
			for ( int j = i; j < (pState->m_nListenerCount-1); ++ j )
			{
				pState->m_RegisteredListeners[j] = pState->m_RegisteredListeners[j+1];
			}
			pState->m_nListenerCount--;
			break;
		}
	}
	m_pStateMutex->Unlock();
}

bool CLoggingSystem::IsListenerRegistered( ILoggingListener *pListener )
{
	if ( !m_pStateMutex )
		m_pStateMutex = new CThreadFastMutex();

	m_pStateMutex->Lock();
	const LoggingState_t *pState = GetCurrentState();
	bool bFound = false;
	for ( int i = 0; i < pState->m_nListenerCount; ++ i )
	{
		if ( pState->m_RegisteredListeners[i] == pListener )
		{
			bFound = true;
			break;
		}
	}
	m_pStateMutex->Unlock();
	return bFound;
}

void CLoggingSystem::ResetCurrentLoggingState()
{
	if ( !m_pStateMutex )
		m_pStateMutex = new CThreadFastMutex();

	m_pStateMutex->Lock();
	LoggingState_t *pState = GetCurrentState();
	pState->m_nListenerCount = 0;
	pState->m_pLoggingResponse = &m_DefaultLoggingResponse;
	m_pStateMutex->Unlock();
}

void CLoggingSystem::SetLoggingResponsePolicy( ILoggingResponsePolicy *pLoggingResponse )
{
	if ( !m_pStateMutex )
		m_pStateMutex = new CThreadFastMutex();

	m_pStateMutex->Lock();
	LoggingState_t *pState = GetCurrentState();
	if ( pLoggingResponse == NULL )
	{
		pState->m_pLoggingResponse = &m_DefaultLoggingResponse;
	}
	else
	{
		pState->m_pLoggingResponse = pLoggingResponse;
	}
	m_pStateMutex->Unlock();
}

LoggingResponse_t CLoggingSystem::LogDirect( LoggingChannelID_t channelID, LoggingSeverity_t severity, Color color, const tchar *pMessage )
{
	Assert( IsValidChannelID( channelID ) );
	if ( !IsValidChannelID( channelID ) )
		return LR_CONTINUE;

	LoggingContext_t context;
	context.m_ChannelID = channelID;
	context.m_Flags = m_RegisteredChannels[channelID].m_Flags;
	context.m_Severity = severity;
	context.m_Color = ( color == UNSPECIFIED_LOGGING_COLOR ) ? m_RegisteredChannels[channelID].m_SpewColor : color;
	
	// It is assumed that the mutex is reentrant safe on all platforms.
	if ( !m_pStateMutex )
		m_pStateMutex = new CThreadFastMutex();

	m_pStateMutex->Lock();

	LoggingState_t *pState = GetCurrentState();
	
	for ( int i = 0; i < pState->m_nListenerCount; ++ i )
	{
		pState->m_RegisteredListeners[i]->Log( &context, pMessage );
	}

#if defined( _PS3 ) && !defined( _CERT )
	if ( !pState->m_nListenerCount )
	{
		unsigned int unBytesWritten;
		sys_tty_write( SYS_TTYP15, pMessage, strlen( pMessage ), &unBytesWritten );
	}
#endif
	
	LoggingResponse_t response = pState->m_pLoggingResponse->OnLog( &context );

	m_pStateMutex->Unlock();

	switch( response )
	{
	case LR_DEBUGGER:
		// Asserts put the debug break in the macro itself so the code breaks at the failure point.
		if ( severity != LS_ASSERT )
		{
			DebuggerBreakIfDebugging();
		}
		break;

	case LR_ABORT:
		Log_Msg( LOG_DEVELOPER_VERBOSE, "Exiting due to logging LR_ABORT request.\n" );
		Plat_ExitProcess( EXIT_FAILURE );
		break;
	}

	return response;
}

CLoggingSystem::LoggingChannel_t *CLoggingSystem::GetChannel( LoggingChannelID_t channelID )
{
	Assert( IsValidChannelID( channelID ) );
	return &m_RegisteredChannels[channelID];
}

const CLoggingSystem::LoggingChannel_t *CLoggingSystem::GetChannel( LoggingChannelID_t channelID ) const
{
	Assert( IsValidChannelID( channelID ) );
	return &m_RegisteredChannels[channelID];
}

CLoggingSystem::LoggingState_t *CLoggingSystem::GetCurrentState()
{
	// Assume the caller grabbed the mutex.
	int nState = g_nThreadLocalStateIndex;
	if ( nState != 0 )
	{
		Assert( nState > 0 && nState < MAX_LOGGING_STATE_COUNT );
		return &m_LoggingStates[nState];
	}
	else
	{
		Assert( m_nGlobalStateIndex >= 0 && m_nGlobalStateIndex < MAX_LOGGING_STATE_COUNT );
		return &m_LoggingStates[m_nGlobalStateIndex];
	}
}

const CLoggingSystem::LoggingState_t *CLoggingSystem::GetCurrentState() const
{
	// Assume the caller grabbed the mutex.
	int nState = g_nThreadLocalStateIndex;
	if ( nState != 0 )
	{
		Assert( nState > 0 && nState < MAX_LOGGING_STATE_COUNT );
		return &m_LoggingStates[nState];
	}
	else
	{
		Assert( m_nGlobalStateIndex >= 0 && m_nGlobalStateIndex < MAX_LOGGING_STATE_COUNT );
		return &m_LoggingStates[m_nGlobalStateIndex];
	}
}

int CLoggingSystem::FindUnusedStateIndex()
{
	for ( int i = 0; i < MAX_LOGGING_STATE_COUNT; ++ i )
	{
		if ( m_LoggingStates[i].m_nListenerCount < 0 )
		{
			return i;
		}
	}
	return -1;
}

CLoggingSystem::LoggingTag_t *CLoggingSystem::AllocTag( const char *pTagName )
{
	Assert( m_nChannelTagCount < MAX_LOGGING_TAG_COUNT );
	LoggingTag_t *pTag = &m_ChannelTags[m_nChannelTagCount ++];
	
	pTag->m_pNextTag = NULL;
	pTag->m_pTagName = m_TagNamePool + m_nTagNamePoolIndex;
	
	// Copy string into pool.
	size_t nTagLength = strlen( pTagName );
	Assert( m_nTagNamePoolIndex + nTagLength + 1 <= MAX_LOGGING_TAG_CHARACTER_COUNT );
	strcpy( m_TagNamePool + m_nTagNamePoolIndex, pTagName );
	m_nTagNamePoolIndex += ( int )nTagLength + 1;

	return pTag;
}

LoggingChannelID_t LoggingSystem_RegisterLoggingChannel( const char *pName, RegisterTagsFunc registerTagsFunc, int flags, LoggingSeverity_t severity, Color color )
{
	return GetGlobalLoggingSystem()->RegisterLoggingChannel( pName, registerTagsFunc, flags, severity, color );
}

void LoggingSystem_ResetCurrentLoggingState()
{
	GetGlobalLoggingSystem()->ResetCurrentLoggingState();
}

void LoggingSystem_RegisterLoggingListener( ILoggingListener *pListener )
{
	GetGlobalLoggingSystem()->RegisterLoggingListener( pListener );
}

void LoggingSystem_UnregisterLoggingListener( ILoggingListener *pListener )
{
	GetGlobalLoggingSystem()->UnregisterLoggingListener( pListener );
}

void LoggingSystem_SetLoggingResponsePolicy( ILoggingResponsePolicy *pResponsePolicy )
{
	GetGlobalLoggingSystem()->SetLoggingResponsePolicy( pResponsePolicy );
}

void LoggingSystem_PushLoggingState( bool bThreadLocal, bool bClearState )
{
	GetGlobalLoggingSystem()->PushLoggingState( bThreadLocal, bClearState );
}

void LoggingSystem_PopLoggingState( bool bThreadLocal )
{
	GetGlobalLoggingSystem()->PopLoggingState( bThreadLocal );
}

void LoggingSystem_AddTagToCurrentChannel( const char *pTagName )
{
	GetGlobalLoggingSystem()->AddTagToCurrentChannel( pTagName );
}

LoggingChannelID_t LoggingSystem_FindChannel( const char *pChannelName )
{
	return GetGlobalLoggingSystem()->FindChannel( pChannelName );
}

int LoggingSystem_GetChannelCount()
{
	return GetGlobalLoggingSystem()->GetChannelCount();
}

LoggingChannelID_t LoggingSystem_GetFirstChannelID()
{
	return ( GetGlobalLoggingSystem()->GetChannelCount() > 0 ) ? 0 : INVALID_LOGGING_CHANNEL_ID;
}

LoggingChannelID_t LoggingSystem_GetNextChannelID( LoggingChannelID_t channelID )
{
	int nChannelCount = GetGlobalLoggingSystem()->GetChannelCount();
	int nNextChannel = channelID + 1;
	return ( nNextChannel < nChannelCount ) ? nNextChannel : INVALID_LOGGING_CHANNEL_ID;
}

const CLoggingSystem::LoggingChannel_t *LoggingSystem_GetChannel( LoggingChannelID_t channelIndex )
{
	return GetGlobalLoggingSystem()->GetChannel( channelIndex );
}

bool LoggingSystem_HasTag( LoggingChannelID_t channelID, const char *pTag )
{
	return GetGlobalLoggingSystem()->HasTag( channelID, pTag );
}

bool LoggingSystem_IsChannelEnabled( LoggingChannelID_t channelID, LoggingSeverity_t severity )
{
	return GetGlobalLoggingSystem()->IsChannelEnabled( channelID, severity );
}

void LoggingSystem_SetChannelSpewLevel( LoggingChannelID_t channelID, LoggingSeverity_t minimumSeverity )
{
	GetGlobalLoggingSystem()->SetChannelSpewLevel( channelID, minimumSeverity );
}

void LoggingSystem_SetChannelSpewLevelByName( const char *pName, LoggingSeverity_t minimumSeverity )
{
	GetGlobalLoggingSystem()->SetChannelSpewLevelByName( pName, minimumSeverity );
}

void LoggingSystem_SetChannelSpewLevelByTag( const char *pTag, LoggingSeverity_t minimumSeverity )
{
	GetGlobalLoggingSystem()->SetChannelSpewLevelByTag( pTag, minimumSeverity );
}

int32 LoggingSystem_GetChannelColor( LoggingChannelID_t channelID )
{
	return GetGlobalLoggingSystem()->GetChannelColor( channelID ).GetRawColor();
}

void LoggingSystem_SetChannelColor( LoggingChannelID_t channelID, int color )
{
	Color c;
	c.SetRawColor( color );
	GetGlobalLoggingSystem()->SetChannelColor( channelID, c );
}

LoggingChannelFlags_t LoggingSystem_GetChannelFlags( LoggingChannelID_t channelID )
{
	return GetGlobalLoggingSystem()->GetChannelFlags( channelID );
}

void LoggingSystem_SetChannelFlags( LoggingChannelID_t channelID, LoggingChannelFlags_t flags )
{
	GetGlobalLoggingSystem()->SetChannelFlags( channelID, flags );
}

LoggingResponse_t LoggingSystem_Log( LoggingChannelID_t channelID, LoggingSeverity_t severity, const char *pMessageFormat, ... )
{
	if ( !GetGlobalLoggingSystem()->IsChannelEnabled( channelID, severity ) )
		return LR_CONTINUE;

	tchar formattedMessage[MAX_LOGGING_MESSAGE_LENGTH];

	va_list args;
	va_start( args, pMessageFormat );
	Tier0Internal_vsntprintf( formattedMessage, MAX_LOGGING_MESSAGE_LENGTH, pMessageFormat, args );
	va_end( args );

	return GetGlobalLoggingSystem()->LogDirect( channelID, severity, UNSPECIFIED_LOGGING_COLOR, formattedMessage );
}

LoggingResponse_t LoggingSystem_Log( LoggingChannelID_t channelID, LoggingSeverity_t severity, Color spewColor, const char *pMessageFormat, ... ) 
{
	if ( !GetGlobalLoggingSystem()->IsChannelEnabled( channelID, severity ) )
		return LR_CONTINUE;

	tchar formattedMessage[MAX_LOGGING_MESSAGE_LENGTH];

	va_list args;
	va_start( args, pMessageFormat );
	Tier0Internal_vsntprintf( formattedMessage, MAX_LOGGING_MESSAGE_LENGTH, pMessageFormat, args );
	va_end( args );

	return GetGlobalLoggingSystem()->LogDirect( channelID, severity, spewColor, formattedMessage );
}

LoggingResponse_t LoggingSystem_LogDirect( LoggingChannelID_t channelID, LoggingSeverity_t severity, Color spewColor, const char *pMessage )
{
	if ( !GetGlobalLoggingSystem()->IsChannelEnabled( channelID, severity ) )
		return LR_CONTINUE;
	return GetGlobalLoggingSystem()->LogDirect( channelID, severity, spewColor, pMessage );
}

LoggingResponse_t LoggingSystem_LogAssert( const char *pMessageFormat, ... ) 
{
	if ( !GetGlobalLoggingSystem()->IsChannelEnabled( LOG_ASSERT, LS_ASSERT ) )
		return LR_CONTINUE;

	tchar formattedMessage[MAX_LOGGING_MESSAGE_LENGTH];

	va_list args;
	va_start( args, pMessageFormat );
	Tier0Internal_vsntprintf( formattedMessage, MAX_LOGGING_MESSAGE_LENGTH, pMessageFormat, args );
	va_end( args );

	return GetGlobalLoggingSystem()->LogDirect( LOG_ASSERT, LS_ASSERT, UNSPECIFIED_LOGGING_COLOR, formattedMessage );
}