c语言日志模块,一个简单又高效的日志系统

c语言日志模块,一个简单又高效的日志系统

2023年6月22日发(作者:)

c语⾔⽇志模块,⼀个简单⼜⾼效的⽇志系统下载源代码摘要:本⽂给出⼀个性能⾼,使⽤简单的⽇志解决⽅案。本模块实现⽇志信息的批量写⼊⽂件,定时⾃动flush到⽂件中,写⼊⽂件的⽇志级别可动态调整,单个⽇志⽂件⼤⼩可配置,循环对⽇志⽂件写⼊,这样不会造成机器空间被⽇志⽂件耗尽。关键字:⽇志 性能 ⽇志级别⼀、程序⽇志是商品程序中必不可少的部分。在正式商⽤的程序中⼀般对于⽇志都会有⼀些类似的要求:性能要求运⾏时⽇志级别可调整⽇志⽂件空间使⽤安全性问题下⾯逐⼀针对上⾯的问题⼀起分析程序实现。⼆、性能问题。客户对程序的要求当然是越⾼越好。如果对于⽇志打印采⽤普通的⽅法,来⼀条⽇志就写⼀条⽇志到⽂件中,这样性能是很低的。因为程序不断的与磁盘进⾏交付,对系统的冲击很⼤,有可能会影响到正常的磁盘IO请求。对于这个问题,⼀般的,都是采⽤批量写⼊的⽅法来解决。每写⼀条⽇志,并不是把⽇志⽴即写⼊⽂件中,⽽是先写到⼀个缓冲区中。当这个缓冲区达到⼀定的量时,再⼀次批量写⼊到⽂件中。见如下代码实现:if (!y()){m_strWriteStrInfo += GetCurTimeStr();// 增加⽇志级别信息if (enLevel == ENUM_LOG_LEVEL_ERROR){m_strWriteStrInfo += _T(“Error! “);}m_strWriteStrInfo += strLog;m_strWriteStrInfo += _T(“rn”);}if ( bForce|| m_gth() > 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::enLogStatusCSuperLog::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头部标识。intCSuperLog::WriteUnicodeHeadToFile(CFile * pFile){if (pFile == NULL){return -1;}try{if (pFile->GetLength() == 0){m_pFile->Write(“377376″, 2); // 就是FF FEif (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.h”WRITE_LOG(_T(“短信发送失败,重试⼀次。”), LOG_LEVEL_ERROR);  ⽇志线程是在全局变量的析构函数中通知退出的。这时有可能还要会打印⽇志。为了保证性能,在取得当前时间的字符串时使⽤了两个静态局部变量 CString& CSuperLog::GetCurTimeStr(){static CTime g_tmCurTime;g_tmCurTime = CTime::GetCurrentTime();// time(NULL);CString g_strTime;g_strTime = g_(_T(“%Y-%m-%d %H:%M:%S “));return g_strTime;}  在使⽤中发现,每次退出时,如果还有⽇志打印,程序总会异常。后来分析发现,静态全局变量每次都会先于全局变量析构,导致strTime析构后⽆效访问。只好把这个变量变成了全局变量规避。 CString& CSuperLog::GetCurTimeStr(){g_tmCurTime = CTime::GetCurrentTime();// time(NULL);g_strTime = g_(_T(“%Y-%m-%d %H:%M:%S “));return g_strTime;}四、结束语程序实现仓促,基本的功能都调试完毕,但⽬前还有带参数的写⽇志接⼝没有写,⼆进制内容⽇志信息的接⼝也没有实现。后续作者会及时完成。有兴趣的同不学可以发邮件联系。Email:y63508@lker2010-01-26 00:39:21

发布者:admin,转转请注明出处:http://www.yc00.com/web/1687428616a9328.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信