고절가주팁 #9 - 로깅 라이브러리 상세 설계 및 구현 쪼~금
서론이 너무 길었네요 이만 각설하고, 이번에는 LogSource 를 상세하게 설계해 보고 구현도 쪼금 해보도록 하겠습니다. 그나저나 저랑 같아 잘 가고 계시죠 ? 요즘 또 갈수록 저 혼자 가는 건 아닌가라는 생각이 들어서 그렇습니다. 혼자가면 너무 외로워요. 같이 가요~ 흑흑흑 (로깅 라이브러리 관련된 글을 처음 보시는 분들은 본문에 나와 있는 로깅 라이브러리를 클릭해 보시면 이전 글들을 모두 보실 수 있습니다).
LogSource 부터 시작해 보겠습니다. 지난 글까지 설계했던 내용을 정리하자면 LogSource 의 인터페이스는 다음과 같을 것입니다.
LogSource 인터페이스
2. 로깅 수준은 DEBUG, TRACE, INFO, WARNING, ERROR, FAULT 로 나눔이런 것이 있었습니다. 그러니 level 의 타입을 그냥 int 로 하면 안되겠지요. 다음과 같이 LogLevel 이라는 enumeration 을 따로 정의해야 할 것입니다.
LogLevel 정의
.
.
.
.
.
.
그렇습니다. 자신에게 subscribe한 LogListener 목록을 가지고 있어야 하겠죠. 별것도 아닌 걸 가지고 괜히 퀴즈 같은 걸 내고 그랬네요 ^^
이상 생각했던 것을 UML 로 표현해 본다면 다음과 같이 나타낼 수 있을 것입니다.
LogSource 클래스 정의
그리고, 요즘 javadoc 이나 doxygen처럼 소스코드 안에 documentation 을 넣는 방식도 많이 사용되고 있죠. 저는 이런 방식을 무척이나 좋아라합니다. 왜냐면 프로그래머가 굳이 documentation 을 위해 별도의 툴을 사용할 필요가 없거든요. 그냥 코딩하다가 documentation할 게 생각나면 그냥 소스코드 안에 그 내용을 집어 넣는 거죠. 솔직히 이런 방식이 형상 관리-S/W 프로젝트의 모든 산출물(소스 코드 포함 모든 문서들)을 관리-하는 측면에서도 보면 훨씬 효율적입니다. 산출물간의 consistency 를 유지하기가 무척 좋거든요. 여러분도 꼭 써보시기를 추천합니다.
그래서 LogSource 설계에 대해 UML 을 쓰는 건 이 정도에서 멈추고 코딩을 통해 나머지 설계를 계속해 보도록 하겠습니다. 한마디로 코딩과 설계를 mixup 하면서 작업하는 거죠. 잘 정의된 문서는 코딩 끝난 후에 하겠습니다. ㅋㅋㅋ(이런 식으로 하는 건 워낙 제 성격이 급해서 그런 걸 수도 있습니다. 이런 방식이 불편하신 분들은 꼭 따라 하실 필요는 없습니다. 제 경험에서 우러나온 것이니... 한계가 있을 수도 있죠)
자 그럼... LogPublisher 인터페이스부터 *구현*해 보죠.
// LogPublisher.h
#ifndef LOGPUBLISHER_H // include guard, 고절가주팁 #1
#define LOGPUBLISHER_H
namespace YSLog { // 로깅 라이브러리의 namespace
struct LogListener; // forward declaration
struct LogPublisher {
virtual bool subscribe(LogListener * pListener) = 0;
virtual bool unsubscribe(LogListener* pListener) = 0;
};
}
#endif // LOGPUBLISHER_H
간단한 LogPublisher 인터페이스를 구현하면서 몇 가지 제가 설계 과정에서 빼 먹었던 것을 *설계*했네요.
1. 클래스별로 별도로 헤더 파일을 둔다.
2. 로깅 라이브러리의 namespace 는 YSLog 로 한다.
이 두 가지 설계 사항은 코드 자체에서 워낙 명확하게 드러나니 별도로 documentation 을 남기진 않아도 될 것입니다.
자~ 그 다음에는 LogSource 를 구현해 볼까요 ?
#ifndef LOGSOURCE_H
#define LOGSOURCE_H
#include <list>
#include <string>
#include "LogPublisher.h"
#include "LogListener.h"
#include "LoggingEvent.h"
using namespace std;
namespace YSLog {
class LogSource : public LogPublisher {
private:
string _module;
bool _enable;
LogLevel _level;
list<LogListener*> _lListenerPtr;
public:
LogSource(string mod, bool enable = true,
LogLevel lvl = YSLog::WARN) :
_module(mod), _enable(enable), _level(lvl), _lListenerPtr()
{
}
virtual bool subscribe(LogListener* pListener);
virtual bool unsubscribe(LogListener* pListener);
bool isEnabled(const LogLevel lvl) const;
string module() const;
bool log(const char* file,
const char* func,
int line,
const char* msg) const;
bool log(LoggingEvent* pLogEv) const;
};
}
#endif // LOGSOURCE_H
UML 설계시에는 string 타입으로 표현했던 인자들을 대부분 const char* 로 바꾸었습니다. 왜냐면 프로그래머가 실제 사용할 프로그래밍 인터페이스는 LogSource 의 인터페이스보다는 YSLOG_XXX 와 같은 매크로 함수가 주로 사용될 것 같은데, 거기에서 사용되는 대부분의 인자가 문자열 리터럴일 것이기 때문입니다(C++ 에서는 Java 와 달리 문자열 리터럴이 string 으로 컴파일되는 것이 아니라 const char* 로 컴파일되는 건 다 아시죠 ?). 그리고, 생성자도 설계시에는 항상 세 가지 인자를 꼭 명시하도록 했으나 enable 인자와 lvl 인자는 default 값을 각각 true, YSLog::WARN 으로 설정해서 모듈명만 인자로 명시하더라도 LogSource 인스턴스를 생성할 수 있도록 했습니다. 프로그래머의 편의성을 위한 배려라고 생각하시면 될 것 같습니다.
그리고, isEnabled(), module(), log() 등은 모두 내부 상태를 바꾸지 않기 때문에 const 를 멤버 함수 선언 뒤에 붙여 주었습니다. 고쳐서는 안된다라는 설계 내용을 코딩을 통해 명확하게 나타낸 것이죠. 어디서 주워 들은 말이 생각 나네요 "코드 자체가 문서이다"
LogSource 멤버 함수들은 어떻게 구현해야할까요 ? 그건 다음 시간까지 여러분에게 숙제로 내기로 하겠습니다.
그럼 지금까지 제 글을 읽어 주셔서 감사드리고, 이 글이 맘에 드신다면 살짝 여기 저기 추천 부탁드립니다. 고절가주팁이 쭉~ 갈 수 있도록 많은 관심 부탁드립니다.
블로그를 구독하는 방법을 잘 모르시는 분은 2. RSS 활용을 클릭하세요.
RSS에 대해 잘 모르시는 분은 1. RSS란 무엇인가를 클릭하세요.