背景
這段業余時間一直都在開發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 useLogger::~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()的模式。