张家港网站建设培训,wordpress电子报,易无忧建站,wordpress多站点好用吗摘要#xff1a;本文给出一个性能高#xff0c;使用简单的日志解决方案。本模块实现日志信息的批量写入文件#xff0c;定时自动flush到文件中#xff0c;写入文件的日志级别可动态调整#xff0c;单个日志文件大小可配置#xff0c;循环对日志文件写入#xff0c;这样不… 摘要本文给出一个性能高使用简单的日志解决方案。本模块实现日志信息的批量写入文件定时自动flush到文件中写入文件的日志级别可动态调整单个日志文件大小可配置循环对日志文件写入这样不会造成机器空间被日志文件耗尽。 关键字日志 性能 日志级别 一、程序日志是商品程序中必不可少的部分。在正式商用的程序中一般对于日志都会有一些类似的要求 性能要求 运行时日志级别可调整 日志文件空间使用安全性问题 下面逐一针对上面的问题一起分析程序实现。 二、性能问题。 客户对程序的要求当然是越高越好。如果对于日志打印采用普通的方法来一条日志就写一条日志到文件中这样性能是很低的。因为程序不断的与磁盘进行交付对系统的冲击很大有可能会影响到正常的磁盘IO请求。对于这个问题一般的都是采用批量写入的方法来解决。每写一条日志并不是把日志立即写入文件中而是先写到一个缓冲区中。当这个缓冲区达到一定的量时再一次批量写入到文件中。见如下代码实现 if (!strLog.IsEmpty()) { m_strWriteStrInfo GetCurTimeStr(); // 增加日志级别信息 if (enLevel ENUM_LOG_LEVEL_ERROR) { m_strWriteStrInfo _T(Error! ); } m_strWriteStrInfo strLog; m_strWriteStrInfo _T(\r\n); } if ( bForce || m_strWriteStrInfo.GetLength() MAX_STR_LOG_INFO_LEN || m_iWriteBinLogLen MAX_BIN_LOG_INFO_LEN/10) { // write info达到一定量时才提交到文件中 WriteLogToFile(); } 但这样会带来一个问题如果日志量比较少很可能要很久才能达到批量提交的量这样就会造成程序写了日志但是日志写入器还是把消息写在缓冲区里文件中没有及时体现出来。我们可以采用定时又定时的办法来输出日志。程序对缓冲区内的日志消息定时强制刷新到文件中去。为了体现程序的使用简单性把这个功能放在日志模块中实现了从而调用日志的程序就不用考虑定时来刷新文件了。见如下程序实现 CSuperLog::CSuperLog(void){ // 初始化临界区变量 InitializeCriticalSection(m_csWriteLog); // 启动信息 m_strWriteStrInfo WELCOME_LOG_INFO; // Create the Logger thread. m_hThread (HANDLE)_beginthreadex( NULL, 0, LogProcStart, NULL, 0, m_uiThreadID );}unsigned __stdcall CSuperLog::LogProcStart( void* pArguments ){ int nCount 1; do { Sleep(300); if (nCount % 10 0 ) { WriteLog(strTemp, ENUM_LOG_LEVEL_ERROR, true); // 每隔三秒写一次日志 } } while (m_bRun);} 采有一个全局日志类变量在构造函数中启动线程线程每隔三秒去刷新一次文件。 二、日志级别可动态调整 程序的日志一般会进行日志分类比如说日志级别一般会有调试日志运行日志错误日志等分类。在程序发布后运行时一般都会设置在运行日志级别这时程序中的调试日志就不会被打印出来。如果程序运行中需要定位分析问题时又需要把日志级别调低把一些调试信息打印出来。见如下程序实现 int CSuperLog::WriteLog(CString strLog,enLogInfoLevel enLevel/* ENUM_LOG_LEVEL_RUN*/, bool bForce /* false*/){ if (enLevel m_iLogLevel) { return -1; } 。。。} 对于调整日志级别我没有把实现放在调用者去设置。而是把这个日志级别信息存放在共享内存中如果要调整日志级别则需要一个小工具去改那一个共享内存。实际上在整个设计中我一直想把日志系统设计得更独立一点尽量不和外部调用程序有更多牵连。 //创建共享文件。 m_hMapLogFile CreateFileMapping(INVALID_HANDLE_VALUE,NULL,PAGE_READWRITE,0,1024, _T(SuperLogShareMem)); if (m_hMapLogFile ! NULL) { //拷贝数据到共享文件里。 m_psMapAddr (LPTSTR)MapViewOfFile(m_hMapLogFile,FILE_MAP_ALL_ACCESS, 0,0,0); if (m_psMapAddr ! NULL) { _tcscpy_s(m_psMapAddr, 1024, g_pszLogLevel[m_iLogLevel]); FlushViewOfFile(m_psMapAddr, _tcslen(g_pszLogLevel[m_iLogLevel])); WriteLog(_T(设置默认日志级别到共享内存中成功。), ENUM_LOG_LEVEL_RUN); } } 在线程中定时去检查这个日志级别有否有变化有变化则立即调整当前的级别设置。 三、日志文件空间使用安全性问题 对于长期运行的商品程序来说一定会要考虑到文件系统安全性的问题。如果程序不停的打印垃圾信息用不了多太日志文件可能会变得很大。如果把用户空间占满了那有可能会引起更严重的问题。所以一定要限制日志文件的大小。程序中考虑到日志文件更换采用了三个文件轮换写写满一个时更换一个文件再写不用考虑到日志文件会耗尽磁盘。 CSuperLog::enLogStatus CSuperLog::OpenLogFile(void){ EnterCriticalSection(m_csWriteLog); for (int iRunCount 0; iRunCount MAX_LOG_FILE_COUNT; iRunCount) { if (m_pFile NULL) { m_pFile new CStdioFile; if (m_pFile NULL) { LeaveCriticalSection(m_csWriteLog); return m_enStatus ENUM_LOG_INVALID; } BOOL bRet m_pFile-Open( g_pszLogFileName[(m_iCurLogFileSeq)%MAX_LOG_FILE_COUNT], CFile::modeWrite | CFile::modeCreate | CFile::typeBinary | CFile::shareDenyNone | CFile::modeNoTruncate); if (bRet) { WriteUnicodeHeadToFile(m_pFile); } else { delete m_pFile; m_pFile NULL; LeaveCriticalSection(m_csWriteLog); return m_enStatus ENUM_LOG_INVALID; } } if (m_pFile-GetLength() MAX_LOG_FILE_LEN) { m_pFile-Close(); BOOL bRet FALSE; // 上一个文件是最大的那个文件或是写过一遍了的。 if (m_iCurLogFileSeq MAX_LOG_FILE_COUNT) { // 所有文件都是写满了则强制从第一个文件开始写同时先清空文件 bRet m_pFile-Open( g_pszLogFileName[(m_iCurLogFileSeq)%MAX_LOG_FILE_COUNT], CFile::modeWrite | CFile::modeCreate | CFile::typeBinary | CFile::shareDenyNone); } else { // 打开第二个文件再检查是否过了最大值 bRet m_pFile-Open( g_pszLogFileName[(m_iCurLogFileSeq)%MAX_LOG_FILE_COUNT], CFile::modeWrite | CFile::modeCreate | CFile::typeBinary | CFile::shareDenyNone | CFile::modeNoTruncate); } if (bRet) { WriteUnicodeHeadToFile(m_pFile); } else { delete m_pFile; m_pFile NULL; LeaveCriticalSection(m_csWriteLog); return m_enStatus ENUM_LOG_INVALID; } } else { break; } } m_pFile-SeekToEnd(); LeaveCriticalSection(m_csWriteLog); return m_enStatus ENUM_LOG_RUN;} 四、其它部分 程序中使用了CStdioFile来处理文件写入在实现中如果使用text模式打开文件写入会发现无法写入中文字符的问题。查找了一些资料发现是字符编码的问题。有一种解决方法是用二进制方式打开在文件的开头处写入unicode头部标识。 int CSuperLog::WriteUnicodeHeadToFile(CFile * pFile){ if (pFile NULL) { return -1; } try { if (pFile-GetLength() 0) { m_pFile-Write(\377\376, 2); // 就是FF FE if (m_enStatus ENUM_LOG_RUN) { m_pFile-WriteString(WELCOME_LOG_INFO); } m_pFile-Flush(); } } catch (...) { return -1; } return 0;} 为了保证调用者尽可能的简单程序把类接口都实现为静态方法调用都可以直接使用。 #define WRITE_LOG CSuperLog::WriteLog#define LOG_LEVEL_DEBUG CSuperLog::ENUM_LOG_LEVEL_DEBUG#define LOG_LEVEL_RUN CSuperLog::ENUM_LOG_LEVEL_RUN#define LOG_LEVEL_ERROR CSuperLog::ENUM_LOG_LEVEL_ERROR 调用者使用如下 // 包含头文件#include common/SuperLog.hWRITE_LOG(_T(短信发送失败重试一次。), LOG_LEVEL_ERROR); 日志线程是在全局变量的析构函数中通知退出的。这时有可能还要会打印日志。为了保证性能在取得当前时间的字符串时使用了两个静态局部变量 CString CSuperLog::GetCurTimeStr(){ static CTime g_tmCurTime; g_tmCurTime CTime::GetCurrentTime();// time(NULL); CString g_strTime; g_strTime g_tmCurTime.Format(_T(%Y-%m-%d %H:%M:%S )); return g_strTime;} 在使用中发现每次退出时如果还有日志打印程序总会异常。后来分析发现静态全局变量每次都会先于全局变量析构导致strTime析构后无效访问。只好把这个变量变成了全局变量规避。 CString CSuperLog::GetCurTimeStr(){ g_tmCurTime CTime::GetCurrentTime();// time(NULL); g_strTime g_tmCurTime.Format(_T(%Y-%m-%d %H:%M:%S )); return g_strTime;} 四、结束语 程序实现仓促基本的功能都调试完毕但目前还有带参数的写日志接口没有写二进制内容日志信息的接口也没有实现。后续作者会及时完成。有兴趣的同不学可以发邮件联系。Email:y63508vip.qq.com 转载于:https://www.cnblogs.com/cccc123/archive/2010/04/17/1714146.html