Логирование¶
Логирование (журналирование) служит для разных целей. Для отладки программы здесь и сейчас; для информирования пользователя о каких-либо факторах; в конце концов, для того, чтобы пользователь смог, связавшись в Вами по повожу ошибки, предоставить больше информации для её отлова. Если для небольших проектов отсутствие журналирования некритично, то для крупных без него будет впоследствии ну крайне неудобно.
Обычно логирование делится на несколько уровней, где каждый следующий включает в себя все предыдущие. Например:
- Критические ошибки (неотключаемый уровень);
- Предупреждения и важная информация (уровень по умолчанию);
- Подробная информация;
- Информация для разработчика;
- Сообщения отладки/трассировки.
Зачастую игроками используется второй или третий уровень.
Реализация¶
Так как желательно, чтобы пользователь мог сам выбирать текущий уровень журналирования, лучше и проще всего для уровня использовать CVar (см. 3.14). Вероятнее всего, её переключение будет в самом низу списка настроек. Мне всегда хватало четырёх из пяти уровней — критический, основной, с дополнительной информацией и отладочный. Уведомления разработчика распределяю в последние два.
Для простоты положим, что наша консольная переменная будет называться "loglevel" (о культуре названия переменных также см. 3.14), а также что она серверная. Условимся, что у нас также есть следующие константы:
Первый вариант реализации¶
Самый простой способ — каждый раз перед участком кода получать CVar и отправлять сообщение, если его значение меньше либо равно указанному.
int loglevel = CVar.FindCVar( "loglevel" ).GetInt();
if ( loglevel >= LL_Detailed )
console.printf( "Something started." );
if ( loglevel >= LL_Debug )
console.printf( "[Debug] Consoleplayer: " .. consoleplayer );
if ( loglevel >= LL_Main )
console.printf( "Ready." );
Очевидно, что постоянно прописывать сравнения не совсем удобно, да и программа может бесплатно разрастись. С учётом этого вытекает другой вариант, гораздо более удобный для крупных программ.
Второй вариант реализации¶
Основан на очевидном утверждении, что гораздо проще использовать API специализированного класса.
class Logger: Thinker {
const CEMERGENCY = TEXTCOLOR_FIRE;
const CINFO = "\c-";
const CDEBUG = TEXTCOLOR_DARKGRAY;
int loglevel;
void UpdateLogLevel( void ) {
loglevel = CVar.FindCVar( "loglevel" ).GetInt();
}
static play Logger Get( void ) {
Logger it = Logger( ThinkerIterator.Create( "Logger", STAT_STATIC ).Next() );
if ( !it ) {
// Creating and initializating a singleton global object:
it = new( "Logger" );
it.ChangeStatNum( STAT_STATIC );
it.UpdateLogLevel();
}
return it;
} // of static play Logger Get( void ) {
// Prints well-decorated information string:
static clearscope void ClearscopeLog( ELogLevels loglevel, String text ) {
String logPrefix = "";
if ( !( loglevel & LL_NoPrefix ) ) {
switch ( loglevel & ~LL_NoPrefix ) {
case LL_Debug:
logPrefix = CDEBUG .. "[ZC Debug] ";
break;
case LL_Emergency:
logPrefix = TEXTCOLOR_RED .. "[ZChecker emergency] " .. CEMERGENCY;
break;
default:
logPrefix = TEXTCOLOR_GRAY .. "[ZC] " .. CINFO;
break;
} // of switch ( loglevel & ~LL_NoPrefix ) {
} // of if ( !( loglevel & LL_NoPrefix ) ) {
console.printf( logPrefix .. text .. ( ( loglevel & LL_NoDot ) || ( text.Mid( text.Length() - 1 ) == "." )? "" : "." ) );
} // of static clearscope void ClearscopeLog( ELogLevels loglevel, String text ) {}
// A play-scope wrapper around a data-scope method (mostly used):
static play void Log( ELogLevels loglevel, String text ) {
if ( Logger.Get().loglevel >= ( loglevel & ~LL_NoPrefix ) )
ClearscopeLog( loglevel, text );
}
}
Logger.Log( LL_Detailed, "Something started." );
Logger.Log( LL_Debug, "Consoleplayer: " .. consoleplayer );
Logger.Log( LL_Main, "Ready." );