Log Project

목표

  • Operator << 을 통해서 메세지의 Type을 보다 쉽게 사용할 수 있는 Log 생성
  • 해당 Log가 소멸자에서 메세지를 출력할 수 있도록

진행 관련

  • 기간 : 2025/01/24 ~ 2025/01/26
  • 스킬 : C++, CMake

구현 순서

  1. Operator << 를 이용해서, Type별로 내부 메세지에 보다 쉽게 저장할 수 있는 구현
  2. LogMessage가 소멸될 때, 해당 메세지를 처리하도록
  3. 메세지를 저장할 지, 출력할 지 Mode 및 Log Level을 설정할 수 있어야 함
  4. 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)로 초기화하는 객체

겪은 문제점

  1. LogMessagem_strMessage에 대한 생명주기 관리

  2. 1 LogMessage가 소멸될 때 처리할 지, 아니면 살아있을 때 해당 m_strMessage를 Get해서 사용할 지에 대한 정리가 완전하지 않았음

  3. MetaData에 대한 개념 확립 필요

  4. 1 Type, Mode, Level에 대한 소유 주최는 누구인가?

  5. 1.1 누가 Log에 대한 사용 및 관리 자체를 다루는 지에 대한 역할이 명확히 안 정해졌음

  6. LogMessageLogManager와의 연결성

  7. 1 모든 Log가 LogManager를 가서, 저장할 지 말지를 정해야 하는 지에 대한 의문이 안 풀림

해결 방법

  1. LogMessagem_strMessage에 대한 생명주기 관리

  2. 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;
        }
...
  1. LogMessageLogManager와의 연결성

  2. 1 모든 Log들은 LogManager를 거쳐서 갈 필요가 없음

  3. 2 둘 사이에 LogMessage를 처리하는 객체가 필요, 그리고 그 객체가 LogManager를 호출해서 저장하는 방식으로 처리하는 것이 유형 중 하나

  4. 3 LogHandler를 만들어서, LogManager에 접근하도록 수정

  5. 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);
              }
          }
      }
    ...
    
    

```

#include <iostream>
#include <vector>
#include <string>

struct Node {
	int Value;
	Node* nextNode = nullptr;

	Node(const int _Value)
		: Value(_Value)
		, nextNode(nullptr)
	{};
};

class Queue
{
public:
	~Queue()
	{
		while (!IsEmpty())
		{
			dequeue();
		}
	}

	Node* front;
	Node* rear;

	bool IsEmpty()
	{
		return front == nullptr && rear == nullptr;
	}

	void enqueue(int _value)
	{
		Node* newNode = new Node(_value);

		front = front == nullptr ? newNode : front;
		rear = rear == nullptr ? front : rear;

		rear->nextNode = newNode;
		rear = newNode;
		rear->nextNode = front;
	}

	void dequeue()
	{
		if (nullptr != rear && nullptr != front)
		{
			std::cout << "Dequeue : " << front->Value << std::endl;

			Node* nextFront = front->nextNode;
			delete front;
			if (rear == front)
			{
				front = nullptr;
				rear = nullptr;
			}
			else
			{
				front = nextFront;
				rear->nextNode = nextFront;
			}
		}
	}

	void peek()
	{
		static Node* value = front;

		if (nullptr != value)
		{
			std::cout << value->Value << std::endl;
			value = value->nextNode;
		}
	}
};

int main()
{
	Queue queue;

	queue.enqueue(1);
	queue.enqueue(2);
	queue.enqueue(3);
	queue.enqueue(4);
	queue.enqueue(5);

	queue.dequeue();

	for (int index = 0; index < 6; index++)
	{
		queue.peek();
	}
	return 0;
}


Node를 활용한 원형 큐 구현

front, rear Node Pointer와 peek을 하기 위한 value Node pointer가 추가로 필요하였음

그리고, dequeue를 해서 제일 마지막에 요소가 한개만 남았을 때에,
해당 요소를 처리해주는 추가적인 로직이 필요했음 ( 관련 로직은 빠질 수 있지 않았을까? )

 

@echo off
doskey ls = dir $*
doskey cp = copy $*
doskey rm = del $*
doskey mv = move $*
doskey cwd = cd
doskey cd = pushd $*
doskey cmake_clean = del /s /f /q build
doskey cmake_allcl = rmdir /s /q build $T mkdir build
doskey cmake32 = cmake $* -G "Visual Studio 17 2022" -S ./ -B ./build -A Win32
doskey cmake64 = cmake $* -G "Visual Studio 17 2022" -S ./ -B ./build -A x64
doskey cmake_Release = cmake --build ./build --config Release
doskey cmake_Debug = cmake --build ./build --config Debug
doskey ccd = cd .. $T cd ..
doskey st = start build\Debug\$*
doskey gt = g++ -std=c++20 $*
doskey vim = nvim $*
doskey init = nvim %USERPROFILE%\AppData\Local\nvim\init.vim


doskey all = doskey /macros

'개인 프로젝트 > Vim' 카테고리의 다른 글

[Vim] vimrc 파일  (0) 2022.04.17

PDF 파일 열기

+ Recent posts