背景
這段業餘時間一直都在開發iToday。在iToday中加入日誌管理。關於iToday,可以參考那些一些文章。
開源(Open Source)那些事兒 (一)
開源那些事兒 (二) - iToday開源專案計劃
開源那些事兒(三)-iToday的總體設計
開源那些事兒(四)-如何使用CodePlex進行專案管理
簡介
日誌管理是程式不可以缺少的一個重要組成部分,對於長期啟動並執行背景程式尤為重要,儘管經過了大量的測試,但是在實際運行環境下,程式未免有出錯的時候。有時候由於第三方原因導致的,例如電信網路品質下載,掉包等等。在一些看似莫名其妙的問題下,記錄檔很多時候就成了救命繩。bug free是我們一直追求的目標,但是我永遠不能保證bug free,每次我在面試中說這句話,做銷售出生的人會翻白眼,做技術的人會會心一笑。我能保證的是如何儘快的troubleshooting,提高品質,記錄檔在這過程中又是最重要的手段之一。下面文章講述使用Native C++對Windows Embedded CE和Windows Mobile記錄檔類的封裝。
代碼
先上代碼,下面分析。需要iToday全部代碼也可以到codeplex上去下載。
類定義檔案
typedef enum tagLOG_LEVEL
{
LOG_TRACE,
LOG_INFO,
LOG_WARNING,
LOG_ERROR,
LOG_FATAL,
LOG_NONE = 10,
}LOG_LEVEL;
class Logger
{
public:
static Logger& Instance();
static void SetLogFilePath( const std::string& strFilePath);
static void SetLogLevel( const LOG_LEVEL enLogLevel);
static void Initialise();
static void Dispose();
//void Log( LOG_TRACE const TCHAR *format, ... );
//void LogInfo( const TCHAR *format, ... );
//void LogWarning( const TCHAR *format, ... );
//void LogError( const TCHAR *format, ... );
//void LogFatal( const TCHAR *format, ... );
void Log( LOG_LEVEL logLevel ,const TCHAR *format, ... );
private:
/* more (non-static) functions here */
Logger(); // ctor hidden
Logger(Logger const&); // copy ctor hidden
Logger& operator=(Logger const&); // assign op. hidden
~Logger(); // dtor hidden
static FILE* m_hLogFile;
static std::string m_strFilePath;
static LOG_LEVEL m_enLogLevel;
};
類實現檔案
FILE* Logger::m_hLogFile = NULL;
LOG_LEVEL Logger::m_enLogLevel = LOG_TRACE;
std::string Logger::m_strFilePath = "\\Storage Card\\DebugInfo.log";
TCHAR * LogLevelStr[]=
{
TEXT("TRACE"),
TEXT("INFO"),
TEXT("WARN"),
TEXT("ERROR"),
TEXT("FATAL"),
};
Logger& Logger::Instance()
{
static Logger oLogger;
return oLogger;
}
void Logger::SetLogFilePath( const std::string& strFilePath)
{
m_strFilePath = strFilePath;
Dispose();
Initialise();
}
void Logger::SetLogLevel( const LOG_LEVEL enLogLevel)
{
m_enLogLevel = enLogLevel;
}
Logger::Logger()
{
Initialise();
}
//never use
Logger::~Logger()
{
Dispose();
}
void Logger::Initialise()
{
if( m_strFilePath.length() > 0 )
{
m_hLogFile = fopen(m_strFilePath.c_str(), "a+");
}
}
void Logger::Dispose()
{
if( NULL != m_hLogFile )
{
fflush( m_hLogFile );
fclose( m_hLogFile );
m_hLogFile = NULL;
}
}
void Logger::Log( LOG_LEVEL enLogLevel ,const TCHAR *format, ... )
{
if( m_enLogLevel > enLogLevel)
{
return;
}
#ifndef DEBUG
if ( NULL == m_hLogFile )
{
return;
}
#endif
TCHAR szBuffer[1024];
va_list args;
va_start(args, format);
vswprintf(szBuffer, format, args);
va_end(args);
#ifdef DEBUG
wprintf(_T("%S THR:%8.8x %s\t%s\n"), GetCurrentTime(), GetCurrentThread(), LogLevelStr[enLogLevel], szBuffer);
#else
//combine time stamp, thread number and log level together.
if( 0 > fwprintf(m_hLogFile, _T("%S THR:%8.8x %s\t%s\n"), GetCurrentTime(), GetCurrentThreadId(), LogLevelStr[enLogLevel], szBuffer) )
{
Dispose();
}
else
{
fflush(m_hLogFile);
}
#endif
}
Singleton模式
這個Logger類使用Singleton模式來實現,不知道什麼時候開始部落格園已經不再流行設計模式了,一方面說明設計模式不再是陽春白雪,已經深入人間。另一方面又興起了反模式熱潮。在反模式的風潮中,Singleton是給人批評最多的模式,Singleton有點像變相的全域變數,破壞了封裝,混亂了各個類的依賴關係。
我還是那句話,模式本身沒有錯,看用的人是否把特定的模式用在特定的情境下。Singleton我還是會用到,如果某個資源類有且只有一份,我就使用Singleton。沒有必要產生多個對象,而且多個對象訪問獨佔資源會有同步問題。在Logger類,我還是使用Singleton,因為我唯寫一個檔案。
Singleton的具體實現一般關心三個問題: 1. 有且只有一個對象執行個體化。 2.多線程的控制。其實第二個問題也是為了保證第一個問題。3. 按需執行個體化。
private:
/* more (non-static) functions here */
Logger(); // ctor hidden
Logger(Logger const&); // copy ctor hidden
Logger& operator=(Logger const&); // assign op. hidden
~Logger(); // dtor hidden
上面的代碼用於保證只有一個對象的執行個體化,很多做C#的開發人員會忽略上面的代碼,因為C#沒有深拷貝的概念,也沒有運算子多載的概念。
Logger& Logger::Instance()
{
static Logger oLogger;
return oLogger;
}
上面的代碼保證安全執行緒以及按需執行個體化。我覺得這個實現模式很好,同時滿足三個願望。
日誌分級管理
列印日誌的時候,分級管理很重要,不同時期需要顯示不同層級的日誌,開發時期,可能需要Trace層級的日誌,到了運行時可能只需要Error以上層級的日誌了,日誌分級管理能均衡時間與空間的合理利用。
通過層級管理,列印層級高於需要顯示層級的日誌。
if( m_enLogLevel > enLogLevel)
{
return;
}
在列印過程中,顯示層級,我在找問題的時候都是從進階往低級找。
if( 0 > fwprintf(m_hLogFile, _T("%S THR:%8.8x %s\t%s\n"), GetCurrentTime(), GetCurrentThreadId(), LogLevelStr[enLogLevel], szBuffer) )
{
Dispose();
}
由於這是在Windows Embedded CE和Windows Mobile平台下的實現,所以都是有Unicode的API。下面是列印的日誌,包含了層級。
2010-02-24T09:17:38 THR:de428d7e TRACE FILE=[.\iToday.cpp], LINE=[44]
2010-02-24T09:17:39 THR:de428d7e INFO FILE=[.\iToday.cpp], LINE=[47]
2010-02-24T09:17:39 THR:de428d7e WARN FILE=[.\iToday.cpp], LINE=[50]
2010-02-24T09:17:40 THR:de428d7e ERROR FILE=[.\iToday.cpp], LINE=[53]
2010-02-24T09:17:40 THR:de428d7e FATAL FILE=[.\iToday.cpp], LINE=[56]
2010-02-24T09:17:41 THR:de428d7e WARN FILE=[.\iToday.cpp], LINE=[67]
2010-02-24T09:17:42 THR:de428d7e ERROR FILE=[.\iToday.cpp], LINE=[70]
2010-02-24T09:17:42 THR:de428d7e FATAL FILE=[.\iToday.cpp], LINE=[73]
時間與線程號
在多線程環境下,列印時間和線程十分重要,這樣能查線程同步問題。時間和線程號見上面的日誌。
使用Logger
void LoggerTest()
{
Logger::Instance().Log(LOG_TRACE, _T("FILE=[%S], LINE=[%d]"), __FILE__, __LINE__);
Sleep(500);
Logger::Instance().Log(LOG_INFO, _T("FILE=[%S], LINE=[%d]"), __FILE__, __LINE__);
Sleep(500);
Logger::Instance().Log(LOG_WARNING, _T("FILE=[%S], LINE=[%d]"), __FILE__, __LINE__);
Sleep(500);
Logger::Instance().Log(LOG_ERROR, _T("FILE=[%S], LINE=[%d]"), __FILE__, __LINE__);
Sleep(500);
Logger::Instance().Log(LOG_FATAL, _T("FILE=[%S], LINE=[%d]"), __FILE__, __LINE__);
Sleep(500);
Logger::SetLogLevel(LOG_WARNING);
Logger::Instance().Log(LOG_TRACE, _T("FILE=[%S], LINE=[%d]"), __FILE__, __LINE__);
Sleep(500);
Logger::Instance().Log(LOG_INFO, _T("FILE=[%S], LINE=[%d]"), __FILE__, __LINE__);
Sleep(500);
Logger::Instance().Log(LOG_WARNING, _T("FILE=[%S], LINE=[%d]"), __FILE__, __LINE__);
Sleep(500);
Logger::Instance().Log(LOG_ERROR, _T("FILE=[%S], LINE=[%d]"), __FILE__, __LINE__);
Sleep(500);
Logger::Instance().Log(LOG_FATAL, _T("FILE=[%S], LINE=[%d]"), __FILE__, __LINE__);
Sleep(500);
Logger::SetLogLevel(LOG_INFO);
Logger::SetLogFilePath("\\Storage Card\\DebugInfo2.log");
Logger::Instance().Log(LOG_TRACE, _T("FILE=[%S], LINE=[%d]"), __FILE__, __LINE__);
Sleep(500);
Logger::Instance().Log(LOG_INFO, _T("FILE=[%S], LINE=[%d]"), __FILE__, __LINE__);
Sleep(500);
Logger::Instance().Log(LOG_WARNING, _T("FILE=[%S], LINE=[%d]"), __FILE__, __LINE__);
Sleep(500);
Logger::Instance().Log(LOG_ERROR, _T("FILE=[%S], LINE=[%d]"), __FILE__, __LINE__);
Sleep(500);
Logger::Instance().Log(LOG_FATAL, _T("FILE=[%S], LINE=[%d]"), __FILE__, __LINE__);
Sleep(500);
}
使用Logger類很簡單,直接調用Log()函數就可以了,可以參考printf的模式來使用,也就是C#的String.Format()的模式。
這是我封裝的Logger類,歡迎拍板,這樣可以讓我不斷改進這個類的實現。