一个简单又高效的日志系统
摘要:本文給出一個(gè)性能高,使用簡單的日志解決方案。本模塊實(shí)現(xiàn)日志信息的批量寫入文件,定時(shí)自動(dòng)flush到文件中,寫入文件的日志級(jí)別可動(dòng)態(tài)調(diào)整,單個(gè)日志文件大小可配置,循環(huán)對(duì)日志文件寫入,這樣不會(huì)造成機(jī)器空間被日志文件耗盡。
關(guān)鍵字:日志 性能 日志級(jí)別
一、程序日志是商品程序中必不可少的部分。在正式商用的程序中一般對(duì)于日志都會(huì)有一些類似的要求:
下面逐一針對(duì)上面的問題一起分析程序?qū)崿F(xiàn)。
二、性能問題。
客戶對(duì)程序的要求當(dāng)然是越高越好。如果對(duì)于日志打印采用普通的方法,來一條日志就寫一條日志到文件中,這樣性能是很低的。因?yàn)槌绦虿粩嗟呐c磁盤進(jìn)行交付,對(duì)系統(tǒng)的沖擊很大,有可能會(huì)影響到正常的磁盤IO請(qǐng)求。
對(duì)于這個(gè)問題,一般的,都是采用批量寫入的方法來解決。每寫一條日志,并不是把日志立即寫入文件中,而是先寫到一個(gè)緩沖區(qū)中。當(dāng)這個(gè)緩沖區(qū)達(dá)到一定的量時(shí),再一次批量寫入到文件中。見如下代碼實(shí)現(xiàn):
但這樣會(huì)帶來一個(gè)問題,如果日志量比較少,很可能要很久才能達(dá)到批量提交的量,這樣就會(huì)造成程序?qū)懥巳罩?#xff0c;但是日志寫入器還是把消息寫在緩沖區(qū)里,文件中沒有及時(shí)體現(xiàn)出來。我們可以采用定時(shí)又定時(shí)的辦法來輸出日志。程序?qū)彌_區(qū)內(nèi)的日志消息定時(shí)強(qiáng)制刷新到文件中去。為了體現(xiàn)程序的使用簡單性,把這個(gè)功能放在日志模塊中實(shí)現(xiàn)了,從而調(diào)用日志的程序就不用考慮定時(shí)來刷新文件了。見如下程序?qū)崿F(xiàn):
CSuperLog::CSuperLog(void){ // 初始化臨界區(qū)變量 InitializeCriticalSection(&m_csWriteLog); // 啟動(dòng)信息 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);}采有一個(gè)全局日志類變量,在構(gòu)造函數(shù)中啟動(dòng)線程,線程每隔三秒去刷新一次文件。
二、日志級(jí)別可動(dòng)態(tài)調(diào)整
程序的日志一般會(huì)進(jìn)行日志分類,比如說日志級(jí)別一般會(huì)有調(diào)試日志,運(yùn)行日志,錯(cuò)誤日志等分類。在程序發(fā)布后運(yùn)行時(shí)一般都會(huì)設(shè)置在運(yùn)行日志級(jí)別,這時(shí)程序中的調(diào)試日志就不會(huì)被打印出來。如果程序運(yùn)行中需要定位分析問題時(shí),又需要把日志級(jí)別調(diào)低,把一些調(diào)試信息打印出來。見如下程序?qū)崿F(xiàn):
int CSuperLog::WriteLog(CString &strLog,enLogInfoLevel enLevel/* = ENUM_LOG_LEVEL_RUN*/, bool bForce /*= false*/){ if (enLevel < m_iLogLevel) { return -1; } 。。。}對(duì)于調(diào)整日志級(jí)別,我沒有把實(shí)現(xiàn)放在調(diào)用者去設(shè)置。而是把這個(gè)日志級(jí)別信息存放在共享內(nèi)存中,如果要調(diào)整日志級(jí)別,則需要一個(gè)小工具去改那一個(gè)共享內(nèi)存。實(shí)際上在整個(gè)設(shè)計(jì)中我一直想把日志系統(tǒng)設(shè)計(jì)得更獨(dú)立一點(diǎn),盡量不和外部調(diào)用程序有更多牽連。
//創(chuàng)建共享文件。 m_hMapLogFile = CreateFileMapping(INVALID_HANDLE_VALUE,NULL,PAGE_READWRITE,0,1024, _T("SuperLogShareMem")); if (m_hMapLogFile != NULL) { //拷貝數(shù)據(jù)到共享文件里。 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("設(shè)置默認(rèn)日志級(jí)別到共享內(nèi)存中成功。"), ENUM_LOG_LEVEL_RUN); } }在線程中定時(shí)去檢查這個(gè)日志級(jí)別有否有變化,有變化則立即調(diào)整當(dāng)前的級(jí)別設(shè)置。
三、日志文件空間使用安全性問題
對(duì)于長期運(yùn)行的商品程序來說,一定會(huì)要考慮到文件系統(tǒng)安全性的問題。如果程序不停的打印垃圾信息,用不了多太,日志文件可能會(huì)變得很大。如果把用戶空間占滿了,那有可能會(huì)引起更嚴(yán)重的問題。所以一定要限制日志文件的大小。程序中考慮到日志文件更換,采用了三個(gè)文件輪換寫,寫滿一個(gè)時(shí),更換一個(gè)文件再寫,不用考慮到日志文件會(huì)耗盡磁盤。
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; // 上一個(gè)文件是最大的那個(gè)文件或是寫過一遍了的。 if (m_iCurLogFileSeq >= MAX_LOG_FILE_COUNT) { // 所有文件都是寫滿了,則強(qiáng)制從第一個(gè)文件開始寫,同時(shí)先清空文件 bRet = m_pFile->Open( g_pszLogFileName[(m_iCurLogFileSeq++)%MAX_LOG_FILE_COUNT], CFile::modeWrite | CFile::modeCreate | CFile::typeBinary | CFile::shareDenyNone); } else { // 打開第二個(gè)文件,再檢查是否過了最大值 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來處理文件寫入,在實(shí)現(xiàn)中如果使用text模式打開文件寫入,會(huì)發(fā)現(xiàn)無法寫入中文字符的問題。查找了一些資料,發(fā)現(xiàn)是字符編碼的問題。有一種解決方法是用二進(jìn)制方式打開,在文件的開頭處寫入unicode頭部標(biāo)識(shí)。
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;}為了保證調(diào)用者盡可能的簡單,程序把類接口都實(shí)現(xiàn)為靜態(tài)方法,調(diào)用都可以直接使用。
#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調(diào)用者使用如下:
// 包含頭文件#include "common/SuperLog.h"WRITE_LOG(_T("短信發(fā)送失敗,重試一次。"), LOG_LEVEL_ERROR);日志線程是在全局變量的析構(gòu)函數(shù)中通知退出的。這時(shí)有可能還要會(huì)打印日志。為了保證性能,在取得當(dāng)前時(shí)間的字符串時(shí)使用了兩個(gè)靜態(tài)局部變量
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;}在使用中發(fā)現(xiàn),每次退出時(shí),如果還有日志打印,程序總會(huì)異常。后來分析發(fā)現(xiàn),靜態(tài)全局變量每次都會(huì)先于全局變量析構(gòu),導(dǎo)致strTime析構(gòu)后無效訪問。只好把這個(gè)變量變成了全局變量規(guī)避。
CString& CSuperLog::GetCurTimeStr(){ g_tmCurTime = CTime::GetCurrentTime();// time(NULL); g_strTime = g_tmCurTime.Format(_T("%Y-%m-%d %H:%M:%S ")); return g_strTime;}四、結(jié)束語
程序?qū)崿F(xiàn)倉促,基本的功能都調(diào)試完畢,但目前還有帶參數(shù)的寫日志接口沒有寫,二進(jìn)制內(nèi)容日志信息的接口也沒有實(shí)現(xiàn)。后續(xù)作者會(huì)及時(shí)完成。有興趣的同不學(xué)可以發(fā)郵件聯(lián)系。Email:y63508@vip.qq.com
轉(zhuǎn)載于:https://www.cnblogs.com/cccc123/archive/2010/04/17/1714146.html
總結(jié)
以上是生活随笔為你收集整理的一个简单又高效的日志系统的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: AE 动效工作流技巧 —— 减少 Bod
- 下一篇: switchhost