[MFC] 로그(Log) 출력 Logger

반응형

이번 포스팅에서는 C/C++ GUI 개발 환경에서 로그 출력을 위한 Logger 매크로를 작성하여 효율적으로 디버깅 메시지를 출력하는 방법을 알아보도록 하겠습니다.

Logger 요구사항은 다음과 같습니다.

  1. MFC 프로그램 개발 시 콘솔 로그를 확인할 수 있어야 한다.
  2. 로그가 파일로 저장되어야 한다.

1. 콘솔창에 로그 출력하는 방법

MFC와 같은 GUI 개발환경에서 콘솔로 로그를 출력하려면 컴파일 단계에서 링커에게 콘솔을 띄우도록 알려줘야 합니다.

그 방법은 아래 코드를 참조하면 됩니다.

 

framework.h 에 아래 내용 추가 후 코드에서 DPRINTF 함수를 호출하면 별도 콘솔창에 로그가 출력됩니다.

#ifdef _DEBUG
#ifdef _UNICODE
#define WIDEN(x)           L ## x
#define WIDEN2(x)         WIDEN(x)
#define __WFILE__          WIDEN2(__FILE__)
#define __WFUNCTION__ WIDEN2(__FUNCTION__)
#pragma comment(linker, "/entry:wWinMainCRTStartup /subsystem:console")	/* OPEN CONSOLE DEBUG UNICODE */
#define DPRINTF(fmt, ...) do { _tprintf(_T("[%s:%d]" fmt), __WFUNCTION__, __LINE__, __VA_ARGS__); } while(0)
#else
#pragma comment(linker, "/entry:WinMainCRTStartup /subsystem:console")	/* OPEN CONSOLE DEBUG */
#define DPRINTF(fmt, ...) do { printf(_T("[%s:%d]" fmt), __func__, __LINE__, __VA_ARGS__); } while(0)
#endif
#else
#define DPRINTF(fmt, ...)	do{;} while(0)
#endif /* _DEBUG */

 

2. 로그를 파일에 저장하는 방법

2.1 로그 카테고리에 대해

로그를 카테고리별로 분류할 필요가 있습니다. 모듈별, 레벨별로 분류 가능합니다.

이를 저장 또는 소켓으로 출력할 수 있습니다..

모듈은 모바일 기기를 예로 들면 전화, 문자, 인터넷 및 각 어플들이 각각의 모듈로 구분되고

레벨은 LOW, MID, HIGH, ERR, FATAL 등으로 나눌 수 있습니다.

이렇게 구분하여 두면 로컬이 아닌 Diagnostics를 통한 소켓으로 전송 시 모듈과 레벨을 필터링할 수 있습니다.

여러 사람들이 로그 출력시 간과하는 사실 중 하나는 부하입니다.

프로그래밍에서 가장 처음 배우는 게 printf("Hello World!");  라서 그런지는 몰라도 printf를 가볍게 보는 것 같은데

가변인자를 파싱해서 메모리버퍼에 직렬화 후 마지막에는 1바이트 단위로 하나씩 하나씩 하드웨어 지정된 메모리에 쓰게 됩니다. (Trace32 물려서 어셈블리 레벨로 확인하였습니다.)

 

2.2 로그 부하에 대해

시스템 여기저기서 로그를 출력하면 메모리버퍼에 쓸 때까지는 몰라도 실제 화면(stdout) 또는 파일기록 시 상당한 부하가 걸립니다. 멀티코어, 고 클럭 하드웨어에서는 별로 표가 안 날 수 있지만 단일 스레드 레벨에서 보면 부하가 걸리는 건 맞습니다. 모 회사에서 아무리 최적화를 해도 시스템 속도 개선이 안되어 거액의 돈을 주고 외주를 맡겼는데 주요 부하 해결 요인 중 하나가 불필요 로그 출력 제거였습니다. 그렇기 때문에 로그를 출력할 경우에도 가급적 중간에 버퍼를 두고 로그가 print 호출과 함께 동기적으로 동작하여 그 자체가 주 로직이 되게 하는 것보다는 가급적 유휴타임에 주기적으로 flush 되도록 관리할 필요가 있습니다. CFile로 Open 한 핸들에 바로 기록하지 말고 CArchive에 한번 더 연결 후 메모리 버퍼를 충분히 주어 주기적으로 한 번씩 파일에 기록하도록 하는 것을 권장합니다.

 

데이터를 주고받는 객체 사이에는 한쪽에 대기가 걸리지 않도록 서로의 처리 속도를 보완해 줄 중간버퍼(Queue)는 필수인데 FTP 서버에 왜 큐를 두었는지 도무지 이해가 안 가네를 연발하시던 국내 유명 인공위성 개발업체 팀장님의 발언이 생각납니다. 맥도널드에서 햄버거 주문하는데 주문받는 사람이 주방에 가서 햄버거도 만들어서 갖다 주면 2번째 3번째 주문하려고 기다리는 사람들은 1번 햄버거 나올 때까지 주문 접수도 못하게 되는데 마찬가지로 디스크에 파일을 Read, Write 하는 스레드와 클라이언트 상대하는 스레드 사이에는 당연히 큐를 넣어야 하는 게 아닌지, 요즘도 그렇게 인공위성을 개발하고 계시는지 궁금합니다. 

 

2.3 예제 코드

- CLogger.h 예시

파일별로 라인 시퀀스넘버를 관리하는데 MAX_LINE_PER_FILE에 도달하면 현재 쓰는 파일핸들을 flush 후 닫고 새 파일을 엽니다. 향후 Diag을 통한 소켓으로 로그 전송 필요시 LOG_MSG 하단에서 버퍼와 모듈, 로그카테고리 정보를 관련 함수에 던져주면 됩니다. 소켓전송 시에는 compress, aggregation 고려 필요합니다. 더 고도화 시에는 파라미터 수를 제한시키고(매크로가 파라미터 0개, 1개, 2개, 3개짜리 있음) 각 로그 메시지에 ID 부여 후 ID와 파라미터 변수값만 전송합니다.(퀄컴 QXDM 문서 참조)

#pragma once


#define MAX_PRINT_STR_LEN	(512)

typedef enum class _LOGGER_CATEGORY {
	NORMAL,
	TRADING_EVENT,
	NETWORK,
	DBG,	/* DEBUG */
	INFO,
	ERR,
	MAX
} TEN_LOGGER_CATEGORY;

typedef enum class _LOGGER_LEVEL {
	LOW,
	MID,
	HIGH,
	MAX
} TEN_LOGGER_LEVEL;

typedef struct _LOGGER_SET {
	CCriticalSection critSect;
	UINT32 u32SeqNo;
	CFile* pcfp;
	CArchive* pcarp;
	BOOL boIsInit;
	_LOGGER_SET() {
		u32SeqNo = 0;
		pcfp = nullptr;
		pcarp = nullptr;
		boIsInit = FALSE;
	}
} TST_LOGGER_SET, *PTST_LOGGER_SET;


class CLogger
{
public:
	CLogger();
	~CLogger();
private:
	const UINT32 MAX_LINE_PER_FILE;	/* To be initialzed by constructor */
	TST_LOGGER_SET astLogger[static_cast<unsigned int>(TEN_LOGGER_CATEGORY::MAX)];
	void _logger_renew_logfile(TEN_LOGGER_CATEGORY enCat);
public:
	void logger_write_to_file(TEN_LOGGER_CATEGORY enCat, LPCTSTR buf, size_t bufcount);
};

void format_log_msg(char* buf_ptr, int buf_size, char* fmt, ...);
//void logger_msg_to_file(TEN_LOGGER_CATEGORY enCat, LPCTSTR buf, size_t countof);

#define LOG_MSG(ssid, cat, fmt, ... )                                       \
   {                                                                             \
	 SYSTEMTIME tm;																\
     char buf[MAX_PRINT_STR_LEN];                                                \
	 char newbuf[MAX_PRINT_STR_LEN]; \
     char *p_front = __FILE__;                                                   \
     char *p_end = p_front + strlen( p_front );                                  \
	GetLocalTime(&tm);															\
     while ( p_end != p_front )                                                  \
     {                                                                           \
       if ( ( *p_end == '\\' ) || ( *p_end == ':' ) || ( *p_end == '/') )        \
       {                                                                         \
         p_end++;                                                                \
         break;                                                                  \
       }                                                                         \
       p_end--;                                                                  \
     }                                                                           \
                                                                                 \
     /* Format message for logging */                                            \
     format_log_msg( buf, MAX_PRINT_STR_LEN, fmt, ##__VA_ARGS__ );              \
	(void) _stprintf_s((TCHAR*)newbuf, _countof(newbuf), "\r\n%02d:%02d:%02d,%03ld,%d,%d,%s,%s,%d,%s", \
	tm.wHour, tm.wMinute, tm.wSecond, tm.wMilliseconds, ssid, cat, p_end, __func__, __LINE__, buf);		\
	theApp.Logger.logger_write_to_file(cat, newbuf, _tcslen(newbuf));	\
	(void)printf("%s\n", newbuf);	\
    /* (void)printf("%s:%s:%d] %s\n", p_end, __func__, __LINE__, buf);     */     \
     /* TODO: Send to socket, etc, check ssid, lvl ... */ \
   }

/* SSID (Service Set Identifier) */
#define SSID_NOYECUBE	(0)
#define SSID_x			(1)
#define SSID_xx			(2)
#define SSID_xxx		(3)

- CLogger.cpp 예시

#include "pch.h"
#include "CLogger.h"
#include <strsafe.h>

CLogger::CLogger():MAX_LINE_PER_FILE(4096)
{
	SYSTEMTIME tm;
	TCHAR szBuf[512];
	CString cstrFilePath;
	CString cstrTemp;
	CFileFind cffFindFile;
	CFile* pcfp = nullptr;
//	CArchive* pcarp = nullptr;
//	PTST_LOGGER_SET pstLogger = nullptr;
	BOOL boRet;

	GetLocalTime(&tm);

	memset(szBuf, 0x0, sizeof(szBuf));

	GetModuleFileName(NULL, szBuf, sizeof(szBuf));
	cstrFilePath.Format("%s", szBuf);

	cstrFilePath = cstrFilePath.Left(cstrFilePath.ReverseFind(_T('\\')));

	boRet = cffFindFile.FindFile(cstrFilePath + _T("\\logs"));
	if (!boRet)
		boRet = CreateDirectory(cstrFilePath + _T("\\logs"), NULL);

	boRet = cffFindFile.FindFile(cstrFilePath + _T("\\dumps"));
	if (!boRet)
		boRet = CreateDirectory(cstrFilePath + _T("\\dumps"), NULL);

	cstrTemp.Empty();
	cstrTemp = cstrFilePath;
	cstrTemp.AppendFormat(_T("\\logs\\log-normal-%04d-%02d-%02d-%02d-%02d-%02d-%03ld.csv"),
		tm.wYear, tm.wMonth, tm.wDay, tm.wHour, tm.wMinute, tm.wSecond, tm.wMilliseconds);
	this->astLogger[static_cast<unsigned int>(TEN_LOGGER_CATEGORY::NORMAL)].pcfp = new CFile();
	pcfp = this->astLogger[static_cast<unsigned int>(TEN_LOGGER_CATEGORY::NORMAL)].pcfp;
	boRet = pcfp->Open(cstrTemp, CFile::modeWrite | CFile::modeCreate | CFile::modeNoTruncate | CFile::shareDenyNone);
	if (FALSE == boRet) {
		//TODO: erro handling 
	}
	this->astLogger[static_cast<unsigned int>(TEN_LOGGER_CATEGORY::NORMAL)].pcarp = new CArchive(pcfp, CArchive::store, 4096 * 10);
	this->astLogger[static_cast<unsigned int>(TEN_LOGGER_CATEGORY::NORMAL)].pcarp->WriteString(_T("hhmmss,msec,ssid,category,file,func,line,msg"));
	this->astLogger[static_cast<unsigned int>(TEN_LOGGER_CATEGORY::NORMAL)].boIsInit = TRUE;

	cstrTemp.Empty();
	cstrTemp = cstrFilePath;
	cstrTemp.AppendFormat(_T("\\logs\\log-trading-%04d-%02d-%02d-%02d-%02d-%02d-%03ld.csv"),
		tm.wYear, tm.wMonth, tm.wDay, tm.wHour, tm.wMinute, tm.wSecond, tm.wMilliseconds);
	this->astLogger[static_cast<unsigned int>(TEN_LOGGER_CATEGORY::TRADING_EVENT)].pcfp = new CFile();
	pcfp = this->astLogger[static_cast<unsigned int>(TEN_LOGGER_CATEGORY::TRADING_EVENT)].pcfp;
	boRet = pcfp->Open(cstrTemp, CFile::modeWrite | CFile::modeCreate | CFile::modeNoTruncate | CFile::shareDenyNone);
	if (FALSE == boRet) {
		//TODO: erro handling 
	}
	this->astLogger[static_cast<unsigned int>(TEN_LOGGER_CATEGORY::TRADING_EVENT)].pcarp = new CArchive(pcfp, CArchive::store, 4096 * 10);
	this->astLogger[static_cast<unsigned int>(TEN_LOGGER_CATEGORY::TRADING_EVENT)].pcarp->WriteString(_T("hhmmss,msec,ssid,category,file,func,line,msg"));
	this->astLogger[static_cast<unsigned int>(TEN_LOGGER_CATEGORY::TRADING_EVENT)].boIsInit = TRUE;

	cstrTemp.Empty();
	cstrTemp = cstrFilePath;
	cstrTemp.AppendFormat(_T("\\logs\\log-network-%04d-%02d-%02d-%02d-%02d-%02d-%03ld.csv"),
		tm.wYear, tm.wMonth, tm.wDay, tm.wHour, tm.wMinute, tm.wSecond, tm.wMilliseconds);
	this->astLogger[static_cast<unsigned int>(TEN_LOGGER_CATEGORY::NETWORK)].pcfp = new CFile();
	pcfp = this->astLogger[static_cast<unsigned int>(TEN_LOGGER_CATEGORY::NETWORK)].pcfp;
	boRet = this->astLogger[static_cast<unsigned int>(TEN_LOGGER_CATEGORY::NETWORK)].pcfp->Open(cstrTemp,
		CFile::modeWrite | CFile::modeCreate | CFile::modeNoTruncate | CFile::shareDenyNone);
	if (FALSE == boRet) {
		//TODO: erro handling 
	}
	this->astLogger[static_cast<unsigned int>(TEN_LOGGER_CATEGORY::NETWORK)].pcarp = new CArchive(pcfp, CArchive::store, 4096 * 10);
	this->astLogger[static_cast<unsigned int>(TEN_LOGGER_CATEGORY::NETWORK)].pcarp->WriteString(_T("hhmmss,msec,ssid,category,file,func,line,msg"));
	this->astLogger[static_cast<unsigned int>(TEN_LOGGER_CATEGORY::NETWORK)].boIsInit = TRUE;

	cstrTemp.Empty();
	cstrTemp = cstrFilePath;
	cstrTemp.AppendFormat(_T("\\logs\\log-debug-%04d-%02d-%02d-%02d-%02d-%02d-%03ld.csv"),
		tm.wYear, tm.wMonth, tm.wDay, tm.wHour, tm.wMinute, tm.wSecond, tm.wMilliseconds);
	this->astLogger[static_cast<unsigned int>(TEN_LOGGER_CATEGORY::DBG)].pcfp = new CFile();
	pcfp = this->astLogger[static_cast<unsigned int>(TEN_LOGGER_CATEGORY::DBG)].pcfp;
	boRet = this->astLogger[static_cast<unsigned int>(TEN_LOGGER_CATEGORY::DBG)].pcfp->Open(cstrTemp,
		CFile::modeWrite | CFile::modeCreate | CFile::modeNoTruncate | CFile::shareDenyNone);
	if (FALSE == boRet) {
		//TODO: erro handling 
	}
	this->astLogger[static_cast<unsigned int>(TEN_LOGGER_CATEGORY::DBG)].pcarp = new CArchive(pcfp, CArchive::store, 4096 * 10);
	this->astLogger[static_cast<unsigned int>(TEN_LOGGER_CATEGORY::DBG)].pcarp->WriteString(_T("hhmmss,msec,ssid,category,file,func,line,msg"));
	this->astLogger[static_cast<unsigned int>(TEN_LOGGER_CATEGORY::DBG)].boIsInit = TRUE;

	cstrTemp.Empty();
	cstrTemp = cstrFilePath;
	cstrTemp.AppendFormat(_T("\\logs\\log-info-%04d-%02d-%02d-%02d-%02d-%02d-%03ld.csv"),
		tm.wYear, tm.wMonth, tm.wDay, tm.wHour, tm.wMinute, tm.wSecond, tm.wMilliseconds);
	this->astLogger[static_cast<unsigned int>(TEN_LOGGER_CATEGORY::INFO)].pcfp = new CFile();
	pcfp = this->astLogger[static_cast<unsigned int>(TEN_LOGGER_CATEGORY::INFO)].pcfp;
	boRet = this->astLogger[static_cast<unsigned int>(TEN_LOGGER_CATEGORY::INFO)].pcfp->Open(cstrTemp,
		CFile::modeWrite | CFile::modeCreate | CFile::modeNoTruncate | CFile::shareDenyNone);
	if (FALSE == boRet) {
		//TODO: erro handling 
	}
	this->astLogger[static_cast<unsigned int>(TEN_LOGGER_CATEGORY::INFO)].pcarp = new CArchive(pcfp, CArchive::store, 4096 * 10);
	this->astLogger[static_cast<unsigned int>(TEN_LOGGER_CATEGORY::INFO)].pcarp->WriteString(_T("hhmmss,msec,ssid,category,file,func,line,msg"));
	this->astLogger[static_cast<unsigned int>(TEN_LOGGER_CATEGORY::INFO)].boIsInit = TRUE;

	cstrTemp.Empty();
	cstrTemp = cstrFilePath;
	cstrTemp.AppendFormat(_T("\\logs\\log-error-%04d-%02d-%02d-%02d-%02d-%02d-%03ld.csv"),
		tm.wYear, tm.wMonth, tm.wDay, tm.wHour, tm.wMinute, tm.wSecond, tm.wMilliseconds);
	this->astLogger[static_cast<unsigned int>(TEN_LOGGER_CATEGORY::ERR)].pcfp = new CFile();
	pcfp = this->astLogger[static_cast<unsigned int>(TEN_LOGGER_CATEGORY::ERR)].pcfp;
	boRet = this->astLogger[static_cast<unsigned int>(TEN_LOGGER_CATEGORY::ERR)].pcfp->Open(cstrTemp,
		CFile::modeWrite | CFile::modeCreate | CFile::modeNoTruncate | CFile::shareDenyNone);
	if (FALSE == boRet) {
		//TODO: erro handling 
	}
	this->astLogger[static_cast<unsigned int>(TEN_LOGGER_CATEGORY::ERR)].pcarp = new CArchive(pcfp, CArchive::store, 4096 * 10);
	this->astLogger[static_cast<unsigned int>(TEN_LOGGER_CATEGORY::ERR)].pcarp->WriteString(_T("hhmmss,msec,ssid,category,file,func,line,msg"));
	this->astLogger[static_cast<unsigned int>(TEN_LOGGER_CATEGORY::ERR)].boIsInit = TRUE;
}

CLogger::~CLogger()
{
	CFile* pcfp = nullptr;
	CArchive* pcarp = nullptr;
	for (int i = 0; i < static_cast<int>(TEN_LOGGER_CATEGORY::MAX); i++) {
		pcfp = this->astLogger[i].pcfp;
		pcarp = this->astLogger[i].pcarp;
		if (nullptr != pcarp) {
			pcarp->Flush();
			pcarp->Abort();
			delete pcarp;
			pcarp = nullptr;
		}

		if (nullptr != pcfp) {
			pcfp->Flush();
			pcfp->Abort();
			delete pcfp;
			pcfp = nullptr;
		}
		this->astLogger[i].boIsInit = FALSE;
	}
}

void CLogger::_logger_renew_logfile(TEN_LOGGER_CATEGORY enCat)
{
	SYSTEMTIME tm;
	TCHAR szBuf[512];
	CString cstrFilePath;
	CString cstrTemp;
	CFileFind cffFindFile;
	CFile* pcfp = nullptr;
	//	CArchive* pcarp = nullptr;
	//	PTST_LOGGER_SET pstLogger = nullptr;
	BOOL boRet;

	PTST_LOGGER_SET p = &(this->astLogger[static_cast<unsigned int>(enCat)]);

	if (nullptr != p->pcarp) {
		p->pcarp->Flush();
		p->pcarp->Abort();
		delete p->pcarp;
		p->pcarp = nullptr;
	}

	if (nullptr != p->pcfp) {
		p->pcfp->Flush();
		p->pcfp->Abort();
		delete p->pcfp;
		p->pcfp = nullptr;
	}

	GetLocalTime(&tm);

	memset(szBuf, 0x0, sizeof(szBuf));

	GetModuleFileName(NULL, szBuf, sizeof(szBuf));
	cstrFilePath.Format("%s", szBuf);

	cstrFilePath = cstrFilePath.Left(cstrFilePath.ReverseFind(_T('\\')));

	boRet = cffFindFile.FindFile(cstrFilePath + _T("\\logs"));
	if (!boRet)
		boRet = CreateDirectory(cstrFilePath + _T("\\logs"), NULL);
	
	cstrTemp.Empty();
	cstrTemp = cstrFilePath;
	switch (enCat) {
	case TEN_LOGGER_CATEGORY::NORMAL:
		cstrTemp.AppendFormat(_T("\\logs\\log-normal-%04d-%02d-%02d-%02d-%02d-%02d-%03ld.csv"),
			tm.wYear, tm.wMonth, tm.wDay, tm.wHour, tm.wMinute, tm.wSecond, tm.wMilliseconds);
		break;
	case TEN_LOGGER_CATEGORY::TRADING_EVENT:
		cstrTemp.AppendFormat(_T("\\logs\\log-trading-%04d-%02d-%02d-%02d-%02d-%02d-%03ld.csv"),
			tm.wYear, tm.wMonth, tm.wDay, tm.wHour, tm.wMinute, tm.wSecond, tm.wMilliseconds);
		break;
	case TEN_LOGGER_CATEGORY::NETWORK:
		cstrTemp.AppendFormat(_T("\\logs\\log-network-%04d-%02d-%02d-%02d-%02d-%02d-%03ld.csv"),
			tm.wYear, tm.wMonth, tm.wDay, tm.wHour, tm.wMinute, tm.wSecond, tm.wMilliseconds);
		break;
	case TEN_LOGGER_CATEGORY::DBG:
		cstrTemp.AppendFormat(_T("\\logs\\log-debug-%04d-%02d-%02d-%02d-%02d-%02d-%03ld.csv"),
			tm.wYear, tm.wMonth, tm.wDay, tm.wHour, tm.wMinute, tm.wSecond, tm.wMilliseconds);
		break;
	case TEN_LOGGER_CATEGORY::INFO:
		cstrTemp.AppendFormat(_T("\\logs\\log-info-%04d-%02d-%02d-%02d-%02d-%02d-%03ld.csv"),
			tm.wYear, tm.wMonth, tm.wDay, tm.wHour, tm.wMinute, tm.wSecond, tm.wMilliseconds);
		break;
	case TEN_LOGGER_CATEGORY::ERR:
		cstrTemp.AppendFormat(_T("\\logs\\log-error-%04d-%02d-%02d-%02d-%02d-%02d-%03ld.csv"),
			tm.wYear, tm.wMonth, tm.wDay, tm.wHour, tm.wMinute, tm.wSecond, tm.wMilliseconds);
		break;
	default:
		cstrTemp.AppendFormat(_T("\\logs\\log-unknown-%04d-%02d-%02d-%02d-%02d-%02d-%03ld.csv"),
			tm.wYear, tm.wMonth, tm.wDay, tm.wHour, tm.wMinute, tm.wSecond, tm.wMilliseconds);
	}

	this->astLogger[static_cast<unsigned int>(enCat)].pcfp = new CFile();
	pcfp = this->astLogger[static_cast<unsigned int>(enCat)].pcfp;
	boRet = pcfp->Open(cstrTemp, CFile::modeWrite | CFile::modeCreate | CFile::modeNoTruncate | CFile::shareDenyNone);
	if (FALSE == boRet) {
		//TODO: erro handling 
	}
	this->astLogger[static_cast<unsigned int>(enCat)].pcarp = new CArchive(pcfp, CArchive::store, 4096 * 10);
	this->astLogger[static_cast<unsigned int>(enCat)].pcarp->WriteString(_T("hh:mm:ss,msec,ssid,category,file,func,line,msg"));
	this->astLogger[static_cast<unsigned int>(enCat)].boIsInit = TRUE;

	return;
}

void CLogger::logger_write_to_file(TEN_LOGGER_CATEGORY enCat, LPCTSTR buf, size_t bufcount)
{
	PTST_LOGGER_SET p = &(this->astLogger[static_cast<unsigned int>(enCat)]);

	p->critSect.Lock();
	p->pcarp->Write(buf, (UINT)bufcount);
	p->u32SeqNo++;

	if (MAX_LINE_PER_FILE < (p->u32SeqNo)) {
		this->_logger_renew_logfile(enCat);
		p->u32SeqNo = 0;
	}
	p->critSect.Unlock();
}

void format_log_msg(char* buf_ptr, int buf_size, char* fmt, ...)
{
	va_list arg_list;

	va_start(arg_list, fmt);
	(void)vsnprintf(buf_ptr, buf_size, fmt, arg_list);
	va_end(arg_list);

	return;
}

다음은 모듈별 로그 매크로 예시입니다.

 

신규 모듈을 추가하게 되면 CLogger.h 에 SSID를 추가하고

자신의 헤더파일에 매크로를 등록하여 사용합니다.

모듈별로 SSID를 부여하고 매크로를 만들어 둡니다
레벨별로 로그파일이 분리 생성
향후 별도 로그분석을 위해 csv로 저장. 보기만 할거면 쉼표가 아닌 예쁜 문자 사용

 

콘솔에 출력된 로그. 콘솔을 마우스 클릭하면 프로그램이 멈추니 유의.

이상으로 GUI 환경에서 콘솔로 디버깅 메시지를 출력하는 방법과 파일에 로그 저장 시 각 모듈별, 레벨별로 분류하고 부하를 줄이는 방식에 대해 알아보았습니다.

반응형

'프로그래밍 > C | C++' 카테고리의 다른 글

[MFC] Build Openssl Statically Linked Against Windows  (0) 2022.08.27
[MFC] C++ MiniDumpWriteDump  (0) 2022.08.12
[MFC] simdjson library 설치  (0) 2022.08.01
[MFC] TA-LIB 설치  (0) 2022.08.01
엑셀을 활용한 C코드 생성  (0) 2020.11.12