1. 程式人生 > >一個簡單而又不影響執行的日誌函式

一個簡單而又不影響執行的日誌函式

I/O操作比較耗時,在硬碟頻繁讀寫時尤其耗時,比如防毒軟體掃描,磁碟碎片整理,索引服務之類的。如果在執行效率有要求的環境下有大量I/O的話就不得不考慮如何讓I/O不影響執行。

I/O操作比如說日誌,可以輸出到DbgView,也可以輸出到檔案。當有bug時,可以告訴我們問題出在哪兒。輸出到DbgView與輸出到檔案各有利弊。輸出到DbgView,好處是不影響程式執行,壞處是需要開DbgView(有時客戶不方便開),要考慮DbgView記憶體開銷(這樣的話,無法看到時間靠前的日誌),不能動DbgView(否則會堵塞程式,拖動進度條,儲存檔案,清除log之類的操作)。輸出到檔案,好處是要求少,壞處是有大量I/O操作,必須合理的設計保證不影響程式執行。

最先想到也是最容易想到的解決方案是,將Log丟到緩衝,建立一個子執行緒專門將這些Log輸出到檔案。我問自己能不能在現有的檔案日誌基礎上做最少的改動以實現目的。

現有的日誌函式

static unsigned short  EnableLog = 1;	//允許寫日誌
static unsigned short  EnableDbgView = 0;	//允許寫到debug view
TCHAR g_szFilePath[MAX_PATH];
int MyIO(LPCSTR fmt, ...)
{
	va_list ap;
	char MsgBuf[512] = {0};	

	static char	szDate[50] = {0};
	static char	szTime[50] = {0};

	clock_t tStart = clock();
	va_start( ap,fmt );
	vsprintf( MsgBuf,fmt,ap );
	va_end( ap );
	if(MsgBuf[strlen(MsgBuf)-1] == '\n')  
		MsgBuf[strlen(MsgBuf)-1] = '\0';

	if(EnableLog)
	{
		SYSTEMTIME st;  
		char szLog[1024] = {0}
		GetLocalTime(&st);              // 取當前系統時間
		sprintf_s(szTime, sizeof(szTime), "%02d:%02d:%02d.%03d", \
			st.wHour, st.wMinute, st.wSecond, st.wMilliseconds);
		
		sprintf_s(szDate, sizeof(szDate), "%04d%02d%02d", st.wYear, st.wMonth, st.wDay);
		
		sprintf_s();
		int nBufLen = strlen(szBufferPerSec);
		sprintf(szLog, "%s -> %s\r\n", szTime, MsgBuf);	

		TCHAR szFileName[MAX_PATH];
		sprintf_s(szFileName, sizeof(szFileName), "%sMyIODemo_%s.txt", \
			g_szFilePath, szDate);
		HANDLE hLogFile = CreateFile(szFileName, 
				GENERIC_WRITE,
				FILE_SHARE_READ,//|FILE_SHARE_DELETE,
				NULL,
				OPEN_ALWAYS,//if not exist,creat new //CREATE_ALWAYS,
				FILE_ATTRIBUTE_NORMAL,
				0);

		if(hLogFile  == INVALID_HANDLE_VALUE)
		{
			TRACE("Error!!!can't open %s to write", szFileName);
			return -1;
		}
		SetFilePointer(hLogFile , NULL, NULL, FILE_END);		

		BOOL bRet = WriteFile(hLogFile , szLog, strlen(szLog), 
			NULL, NULL);

		if(!bRet)
		{
			TRACE( "Error!Could not write to file (error %d)\n", GetLastError());
		}

		if(clock()-tStart>2)
		{				
			OutputDebugString("Error!log time-consumingly to file");
		}
		if(hLogFile )
		{
			CloseHandle(hLogFile );
			hLogFile  = NULL;
		}
	}

	if(EnableDbgView)
		OutputDebugString(MsgBuf);

	return 0;
}

我想到了採用非同步I/O的方式,或許能解決這個問題。按照Windows核心程式設計的說法

Consider a thread that issues an asynchronous I/O request to a device. This I/O request is passed to a device driver,which assumes the responsibility of actually performing the I/O.While the device driver waits for the device to respond, the application's thread is not suspended as it waits for the I/O request to complete. Instead, this thread continues executing and performs other usefull tasks.

但是採用了非同步I/O之後,實際測試過程中,CreateFile有時候需要16ms,而WriteFile經常超過2ms,甚至超過100ms。因此我考慮檔案控制代碼放到全域性,一開始的時候建立,退出程式前銷燬,同時將日誌先丟到緩衝,每隔1s再一次性寫到檔案中。

改進後的日誌函式

static unsigned short  EnableLog = 1;		//允許寫日誌
static unsigned short  EnableDbgView = 0;	//允許寫到debug view
static HANDLE g_hLogFile = NULL;
static OVERLAPPED g_laped = {0};
TCHAR g_szFilePath[MAX_PATH];

int MyIO(LPCSTR fmt, ...)
{
	va_list ap;
	SYSTEMTIME _SysTime;


	char MsgBuf[512] = {0};	


	bool bFlush = false;
	static clock_t tLastLog = clock()-1;
	static clock_t tLast = clock()-1;
	clock_t tStart = clock();


	char szToday[50] = {0};
	static char	szDate[50] = {0};
	static char	szTime[50] = {0};
	static char szBufferPerSec[1024*1024] = {0};
	static int nCurFileSize = 1024*1024;


	va_start( ap,fmt );
	vsprintf( MsgBuf,fmt,ap );
	va_end( ap );
	if(MsgBuf[strlen(MsgBuf)-1] == '\n')  
		MsgBuf[strlen(MsgBuf)-1] = '\0';


	if(EnableLog)
	{
		SYSTEMTIME st;  
		if(tStart>tLast)
		{
			GetLocalTime(&st);              // 取當前系統時間
			sprintf_s(szTime, sizeof(szTime), "%02d:%02d:%02d.%03d", \
					st.wHour, st.wMinute, st.wSecond, st.wMilliseconds);


			if(tStart-tLastLog>1000)
			{
				bFlush = true;	
				tLastLog = tStart;
			}		
			tLast = tStart;
		}
		
		sprintf_s(szToday, sizeof(szToday), "%04d%02d%02d", st.wYear, st.wMonth, st.wDay);
		if(strcmp(szToday, szDate))
		{
			if(g_hLogFile!=NULL)
			{
				CloseHandle(g_hLogFile);
				g_hLogFile = NULL;
			}
			strcpy(szDate, szToday);
		}


		int nBufLen = strlen(szBufferPerSec);
		sprintf(szBufferPerSec+strlen(szBufferPerSec)/*, sizeof(szBufferPerSec)*/, "%s -> %s\r\n", szTime, MsgBuf);	


		if(bFlush)
		{
			if(g_hLogFile == NULL)
			{
				TCHAR szFileName[MAX_PATH];
				sprintf_s(szFileName, sizeof(szFileName), "%sMyIODemo_%s.txt", \
					g_szFilePath, szDate);
				g_hLogFile = CreateFile(szFileName, 
						GENERIC_WRITE,
						FILE_SHARE_READ,//|FILE_SHARE_DELETE,
						NULL,
						OPEN_ALWAYS,//if not exist,creat new //CREATE_ALWAYS,
						FILE_FLAG_OVERLAPPED/*FILE_ATTRIBUTE_NORMAL*/,
						0);


				if(g_hLogFile == INVALID_HANDLE_VALUE)
				{
					TRACE("Error!!!can't open %s to write", szFileName);
					return -1;
				}
				SetFilePointer(g_hLogFile, nCurFileSize, NULL, FILE_BEGIN);
				SetEndOfFile(g_hLogFile);
			}		
                        //!sld! 2011.9.13 快滿的時候,檔案尺寸翻倍。如果不加這個的話,一旦寫入超過檔案尺寸,最後檔案Log將沒有內容,全是NULL。
			if((int)(nCurFileSize - g_laped.Offset - strlen(szBufferPerSec))<=0)
			{
				nCurFileSize = nCurFileSize*2;
				SetFilePointer(g_hLogFile, nCurFileSize, NULL, FILE_BEGIN);
				SetEndOfFile(g_hLogFile);
			}
			BOOL bRet = WriteFile(g_hLogFile, szBufferPerSec, strlen(szBufferPerSec), 
				NULL, &g_laped);


			if(!bRet)
			{
				DWORD dwErrNo = GetLastError();
				if(dwErrNo!=ERROR_IO_PENDING)
				{
					char szErr[1024];
					sprintf_s(szErr, sizeof(szErr), "Error!Could not write to file (error %d)\n", dwErrNo);      
					OutputDebugString(szErr);
				}
			}


			if(clock()-tStart>2)
			{				
				OutputDebugString("Error!log time-consumingly to file");
			}
			g_laped.Offset+=strlen(szBufferPerSec);
			memset(szBufferPerSec, 0, sizeof(szBufferPerSec));
			
		}
	}


	if(EnableDbgView)
		OutputDebugString(MsgBuf);


	return 0;
}
現在經過測試WriteFile開銷還是像之前一樣,但是已經可以容忍了。沒有在多執行緒環境下測試,但是料想應該能正常工作。