Windows Mobile下使用Native C++开发日志类
2010-02-25 10:45:57 来源:WEB开发网核心提示:背景这段业余时间一直都在开发iToday,在iToday中加入日志管理,Windows Mobile下使用Native C++开发日志类,关于iToday,可以参考那些一些文章,直接调用Log()函数就可以了,可以参考printf的模式来使用,开源(Open Source)那些事儿 (一)开源那些事儿 (二) - iT
背景
这段业余时间一直都在开发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()的模式。
这段业余时间一直都在开发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()的模式。
[]
赞助商链接