Log Project
목표
- Operator << 을 통해서 메세지의 Type을 보다 쉽게 사용할 수 있는 Log 생성
- 해당 Log가 소멸자에서 메세지를 출력할 수 있도록
진행 관련
- 기간 : 2025/01/24 ~ 2025/01/26
- 스킬 : C++, CMake
구현 순서
- Operator << 를 이용해서, Type별로 내부 메세지에 보다 쉽게 저장할 수 있는 구현
- LogMessage가 소멸될 때, 해당 메세지를 처리하도록
- 메세지를 저장할 지, 출력할 지 Mode 및 Log Level을 설정할 수 있어야 함
- LogMessage에 해당 생성을 Macro 함수를 통해서 호출될 수 있도록
구현 객체
- LogMessage : Operator <<을 통해서 내부 메세지에 넣을 수 있는 객체
- LogHandler : LogMessage가 소멸될 때, 어떠한 방식으로 처리할 지 정하는 객체
- LogManager : LogMessage의 실제 내부 메세지를 저장하는 객체
Log Message
객체 설명
Operator << 을 통해서 내부 메세지에 데이터를 넣을 수 있는 객체
해당 객체가 소멸되면서, LogHandler를 통해서 어떠한 처리를 할 지 정할 수 있음객체 생명 주기
사용자가 Log 메세지를 입력
해당 함수 Scope 혹은 해당 줄에서 벗어날 시 소멸자 호출
해당 소멸자 내부에서 LogHandler를 통해서 실제 내부 메세지 처리
#ifndef __LOG_CONSTANTS_H__
#define __LOG_CONSTANTS_H__
#include <cstdint>
#include <string>
namespace Log
{
namespace Enum
{
enum eType : uint8_t
{
eType_None = 0b0000001,
eType_Info = 0b0000010,
eType_Error = 0b0000100,
eType_Warn = 0b0001000,
eType_Max = 0b0001111,
};
enum eMode : uint8_t
{
eMode_None = 0b00000000,
eMode_Print = 0b00000001,
eMode_Save = 0b00000010,
eMode_Max = 0b00000011,
};
enum eLevel : uint8_t
{
eLevel_None = 0b00000000,
eLevel_Type = 0b00000001,
eLevel_Time = 0b00000010,
eLevel_File = 0b00000100,
eLevel_Func = 0b00001000,
eLevel_Line = 0b00010000,
eLevel_Max = 0b00011111,
};
};
struct MetaData
{
Enum::eType eType;
std::string strFileName;
std::string strFuncName;
std::string strLine;
MetaData()
: eType(Enum::eType_None)
, strFileName()
, strFuncName()
, strLine()
{}
MetaData(const Enum::eType _eType, const char* _pFileName, const char* _pFuncName, const unsigned int _nLine)
: eType(_eType)
{
strFileName = (_pFileName != nullptr) ? std::string(_pFileName) : std::string("");
strFuncName = (_pFuncName != nullptr) ? std::string(_pFuncName) : std::string("");
strLine = std::to_string(_nLine);
}
MetaData(const MetaData& _rhs)
: eType(_rhs.eType)
, strFileName(_rhs.strFileName)
, strFuncName(_rhs.strFuncName)
, strLine(_rhs.strLine)
{}
};
...
};
#endif // __LOG_CONSTANTS_H__
...
#ifndef __LOG_MESSAGE_H__
#define __LOG_MESSAGE_H__
#include <string>
#include <cstdint>
#include <memory>
#include "LogConstants.h"
namespace Log
{
class Handler;
class Message
{
public :
Message();
Message(const Message& _rhs);
explicit Message(const MetaData& _MetaData, std::weak_ptr<Handler> _spHandler);
Message(Message&& _rhs) noexcept;
virtual ~Message();
Message& operator=(const Message& _rhs);
Message& operator=(Message&& _rhs) noexcept;
Message& operator<<(const Message& _message);
Message& operator<<(Message&& _message);
Message& operator<<(const std::string& _string);
Message& operator<<(const bool _boolean);
Message& operator<<(const uint8_t _uint8);
Message& operator<<(const uint16_t _uint16);
Message& operator<<(const uint32_t _uint32);
Message& operator<<(const uint64_t _uint64);
Message& operator<<(const int8_t _sint8);
Message& operator<<(const int16_t _sint16);
Message& operator<<(const int32_t _sint32);
Message& operator<<(const int64_t _sin64);
Message& operator<<(const float _float);
Message& operator<<(const double _double);
Message& operator<<(const void* _pVoid);
Message& operator<<(const char* _pChar);
public :
const std::string Get();
private :
MetaData m_tMetaData;
std::string m_strMessage;
std::weak_ptr<Handler> m_wpHandler;
};
};
#endif // __LOG_MESSAGE_H__
- 코드 설명
- m_tMetaData* Log Type, File, Function, Line 들에 대한 정보
- m_strMessage* 실제 내부 메세지를 지니는 객체
- m_wpHandler* LogMessage를 어떠한 방식으로 처리할 지에 대한 방식을 지닌 객체
LogHandler
객체 설명
LogMessage*를 어떠한 방식으로 다룰 지에 대한 정보를 지닌 객체
객체 생명 주기
Log* namespace가 초기화될 때, LogDefaultHandler : LogHandler가 생성됨
#ifndef __LOG_CONSTANTS_H__
#define __LOG_CONSTANTS_H__
#include <cstdint>
#include <string>
namespace Log
{
...
struct Entry
{
MetaData tMetaData;
std::string strMessage;
Entry()
: tMetaData()
, strMessage()
{}
Entry(const Entry& _rhs)
: tMetaData(_rhs.tMetaData)
, strMessage(_rhs.strMessage)
{}
Entry(const MetaData& _MetaData, const std::string& _strMessage)
: tMetaData(_MetaData)
, strMessage(_strMessage)
{}
};
};
#endif // __LOG_CONSTANTS_H__
...
#ifndef __LOG_HANDLER_H__
#define __LOG_HANDLER_H__
#include <memory>
namespace Log
{
struct Entry;
class Manager;
class Handler
{
public :
Handler() = delete;
Handler(std::weak_ptr<Manager> _wpManager);
virtual ~Handler();
public :
virtual void Send(const Entry& _rEntry) = 0;
void Init(const uint8_t _eMode, const uint8_t _eLevel);
const uint8_t GetMode();
const uint8_t GetLevel();
protected :
uint8_t m_eMode;
uint8_t m_eLevel;
std::weak_ptr<Manager> m_wpManager;
};
};
#endif // __LOG_EVENT_QUEUE_H__
- 코드 설명
- Send()* : Entry 정보를 처리하는 것에 대한 가상 함수
- Init()* : eMode(Print, Save), Level(None, Info, Warn, Error, Verbose)로 초기화하는 객체
겪은 문제점
LogMessage와 m_strMessage에 대한 생명주기 관리
1 LogMessage가 소멸될 때 처리할 지, 아니면 살아있을 때 해당 m_strMessage를 Get해서 사용할 지에 대한 정리가 완전하지 않았음
MetaData에 대한 개념 확립 필요
1 Type, Mode, Level에 대한 소유 주최는 누구인가?
1.1 누가 Log에 대한 사용 및 관리 자체를 다루는 지에 대한 역할이 명확히 안 정해졌음
LogMessage와 LogManager와의 연결성
1 모든 Log가 LogManager를 가서, 저장할 지 말지를 정해야 하는 지에 대한 의문이 안 풀림
해결 방법
LogMessage와 m_strMessage에 대한 생명주기 관리
1 LogMessage가 소멸될 때, m_wpHandler를 통해서 m_strMessage에 대한 처리를 전적으로 맡김
// LogMessage.cpp // ... Message::~Message() { if (m_strMessage.empty()) { return; } if (std::shared_ptr<Handler> spHandler = m_wpHandler.lock()) { spHandler->Send(Entry(m_tMetaData, Get())); } }
...
2. **MetaData**에 대한 개념 확립 필요
2.1 **Type** : **LogMessage**에 대한 종류 선별이 필요함
2.2 **Mode** : **LogHandler**에서 어떠한 방식으로 처리할 지에 대한 선별이 필요함
2.3 **Level** : **LogMessage** 자체에서는 전체 **Level**에 대한 정보가 있지만, 그것들 다루는 **LogHandler**와 **LogUtils**(Log MetaData에서 데이터를 추출하는) 특정 부분만 있으면 됨
2.4 **LogMessage**에선 **Type**과 전체 **Level**에 대한 정보들만 지니고 있게 수정
```C++
// LogConstants.h
...
struct MetaData
{
Enum::eType eType;
std::string strFileName;
std::string strFuncName;
std::string strLine;
MetaData()
: eType(Enum::eType_None)
, strFileName()
, strFuncName()
, strLine()
{}
MetaData(const Enum::eType _eType, const char* _pFileName, const char* _pFuncName, const unsigned int _nLine)
: eType(_eType)
{
strFileName = (_pFileName != nullptr) ? std::string(_pFileName) : std::string("");
strFuncName = (_pFuncName != nullptr) ? std::string(_pFuncName) : std::string("");
strLine = std::to_string(_nLine);
}
MetaData(const MetaData& _rhs)
: eType(_rhs.eType)
, strFileName(_rhs.strFileName)
, strFuncName(_rhs.strFuncName)
, strLine(_rhs.strLine)
{}
};
...
// LogUtils.cpp
...
std::string GetDetails(const uint8_t _eLevel, const MetaData& _rMetaData)
{
std::string strDetail;
if (Enum::eLevel_None != (_eLevel & Enum::eLevel_Type))
{
strDetail += "[Type] " + GetType(_rMetaData.eType);
strDetail += " ";
}
if (Enum::eLevel_None != (_eLevel & Enum::eLevel_Time))
{
strDetail += "[Time] " + GetTime();
strDetail += " ";
}
if (Enum::eLevel_None != (_eLevel & Enum::eLevel_File))
{
strDetail += "[File] " + _rMetaData.strFileName;
strDetail += " ";
}
if (Enum::eLevel_None != (_eLevel & Enum::eLevel_Func))
{
strDetail += "[Func] " + _rMetaData.strFuncName;
strDetail += " ";
}
if (Enum::eLevel_None != (_eLevel & Enum::eLevel_Line))
{
strDetail += "[Line] " + _rMetaData.strLine;
strDetail += " ";
}
return strDetail;
}
...
LogMessage와 LogManager와의 연결성
1 모든 Log들은 LogManager를 거쳐서 갈 필요가 없음
2 둘 사이에 LogMessage를 처리하는 객체가 필요, 그리고 그 객체가 LogManager를 호출해서 저장하는 방식으로 처리하는 것이 유형 중 하나
3 LogHandler를 만들어서, LogManager에 접근하도록 수정
4 서로 다른 방식의 Log 처리 방식이 가능하도록 열려놓은 상태로 구현
// LogDefaultHandler.cpp ... void DefaultHandler::Send(const Entry& _rEntry) { const uint8_t eMode = GetMode(); const uint8_t eLevel = GetLevel(); std::string strLog = Utils::GetDetails(eLevel, _rEntry.tMetaData); strLog += "[Log] " + _rEntry.strMessage; if (Enum::eMode_None != (eMode & Enum::eMode_Print)) { std::cout << strLog << std::endl; } if (Enum::eMode_None != (eMode & Enum::eMode_Save)) { if (std::shared_ptr<Manager> spManager = m_wpManager.lock()) { spManager->SetLog(_rEntry.tMetaData.eType, strLog); } } } ...
```