Serach

2013년 10월 10일 목요일

glog 사용법

How To Use Google Logging Library(glog)


Introduction

Google glog는 어플리케이션 수준의 로깅을 구현한 라이브러리이다. 이 라이브러리는 C++ 스타일의 스트림과 다양한 헬퍼 매크로들에 기반한 로깅 API 들을 제공한다. LOG(<특정 severity level>) 로 간단하게 스트리밍해서 메시지를 남길 수 있다. 예를 들면,
#include <glog/glogging.h>
int main(int argc, char* argv[]) {
    // Initialize Google's logging library.
    google::InitGoogleLogging(argv[0]);

    // ....
    LOG(INFO) << "Found " << num_cookies << " Cookies";
}
Google glog는 많은 공용 로깅 테스크들을 간소화시킨 일련의 매크로들을 정의한다. severiy level 에 의해서 메시지들을 남길 수 있으며, 커맨드 라인을 통해서 로깅 작용을 컨트롤 할 수 있고 상태에 따른 로그를 남길 수 있고 기대했던 상태가 나오지 않을 경우에는 프로그램을 종료시킬 수도 있으며 자신만의 로깅 레벨을 도입할 수도 있다. 이 문서는 glog에서 지원하는 기능들에 대해서 기술한다. 이 문서는 라이브러리의 모든 사항들에 대해서 기술되어 있지 않지만, 매우 유용할 것이다. 약간 일반적이지 않은 사항들을 찾고 싶다면 src/glog 디렉토리에 있는 헤더 파일들을 체크 해 보길 바란다.

Severity Level

다음 severity level 들 중에 하나를 명시할 수 있다(severity 증가 순서대로) : INFO, WARNING, ERROR, FATAL. FATAL 메시지를 남기면 프로그램을 종료시킨다(메시지가 출력된 뒤에). 주어진 severity 의 메시지들은 해당 severity 로그 파일에 남길 뿐만 아니라, 자신보다 낮은 severity 레벨의 파일들에도 전부 남긴다. 예를 들면, FATAL 단계의 메시지는 FATAL, ERROR, WARNING, INFO 단계의 파일들에 로그를 남기게 된다.
DFATAL 단계는 디버그 모드에서 FATAL 에러를 남긴다(즉, NDEBUG 매크로가 정의되지 않은 곳). 하지만 자동으로 ERROR 단계로 낮추어서 프로그램이 종료되는 것을 피할 수 있다.
특별히 명시된 것이 없다면, glog는 파일을 "/tmp/<program name>.<hostname>.<user name>.log.<severity level>.<date>.<time>.<pid>" 이름으로 남긴다(예를 들면, "/tmp/hello_world.example.com.hamaji.log.INFO.20080709-222411.10474"). 기본적으로, glog는 ERROR 이나 FATAL 단계의 로그 메시지들을 로그 파일 이외에도 standard error(stderr) 로 복사를 한다.

Setting Flags

몇몇 플래그들은 glog의 출력 작용에 영향을 끼친다. 당신의 머신에 Google gflags 라이브러리가 설치되어 있다면, 커맨드 라인에 플래그를 전달하도록 하는 configure 스크립트(이 스크립트에 대한 자세한 내용은 패키지에 있는 INSTALL 파일을 보라)가 자동으로 감지하고 사용한다. 예를 들면, --logtostderr 플래그를 켜길 원한다면, 다음의 커맨드 라인으로 어플리케이션을 실행하면 된다.
./your_application --logtostderr=1
Google gflags 라이브러리가 설치되어 있지 않다면, "GLOG_" 라는 접두어를 사용한 플래그를 환경 변수를 통해서 설정을 해주어야 한다. 예를 들면,
GLOG_logtostderr=1 ./your_application
다음의 플래그들은 매우 공통적으로 사용되는 것들이다:

logtostderr (bool, default=false)

로그 파일이 아닌 stderr 로 메시지를 남긴다.
NOTE: 플래그에 true 를 의미하는 1, true, yes 를 사용해서 설정할 수 있다(대소문자 구분 없음). 그리고 false 에 대해서는 0, false, no 를 사용해서 설정할 수 있다.

stderrthreshold(int, default=2, which is ERROR)

지정한 레벨과 그 위의 레벨들의 로그 메시지들을 로그 파일 외에 stderr 로 복사를 한다. severity 레벨들 INFO, WARNING, ERROR, FATAL 은 0, 1, 2, 3 에 대응된다.

minloglevel(int, default=0, which is INFO)

지정한 레벨과 그 위의 레벨들의 메시지를 남긴다. severity 레벨들 INFO, WARNING, ERROR, FATAL 은 0, 1, 2, 3 에 대응된다.

log_dir(string, default="")

값이 명시되면, 로그파일들은 기본 디렉토리 대신에 이 디렉토리에 남겨진다.
제공되는 API는 SetLogDestination(LogSeverity severity, const char* base_filename) 이다. 다음과 같이 사용한다.
google::SetLogDestination(google::GLOG_INFO, "./Log.");

v(int, default=0)

VLOG(m) 메시지들을 보여줄 때, m에 대해서 이 플래그의 값보다 같거나 낮은 경우에 대해서만 보여준다. --vmodule 에 의해서 오버라이드 될 수 있다. 자세한 내용은 the section about verbose logging 을 참고.

vmodule(string, default="")

Per-module verbose level. 이 인자는 콤마로 구분되는 <module name>=<log level> 의 리스트를 포함하고 있어야 한다. <module name> 은 파일 이름 긱반으로 매치 시키는 glog 패턴이다(예를 들면, 'gfs' 로 시작되는 이름을 가진 모든 모듈들에 대해서는 gfs* 사용). <log level> 은 --v 에 의해서 주어지는 값을 오버라이드 한다. 자세한 내용은 the section about verbose logging 을 참고.

logging.cc 에는 몇몇 다른 플래그들도 정의되어 있다. 모든 플래그의 리스트를 보고 싶다면, "DEFINE_" 로 소스 코드를 검색해 보면 된다.

Conditional / Occasional Logging

때때로, 특정 상태에서만 메시지를 남기고 싶을 때가 있을 것이다. 특정 상태에 대해서 로그를 남기기 위해서 다음의 매크로를 사용할 수 있다.
LOG_IF(INFO, num_cookies > 10) << "Got lots of cookies";
"Got lots of cookies" 메시지는 num_cookies 변수가 10을 넘을 때만 남겨진다. 코드가 여러 번 실행된다면, 이 매크로는 특정 순간에만 메시지를 남기는데 유용할 것이다. 이런 종류는 로그는 정보 메시지들에 매우 유용하다..
LOG_EVERY_N(INFO, 10) << "Got the " << google::COUNTER << "th cookies";
위 라인은 첫 번째, 11번째, 21번째 ... 실행 시에만 메시지를 출력한다. google::COUNTER 값은 몇 번 반복이 일어났는지를 확인할 때 사용된다.
다음과 같이 상태에 따라서 그리고 임의로 남기도록 조합형태로 사용할 수 있다.
LOG_IF_EVERY_N(INFO, (size > 1024), 10) << "Got the " << google::COUNTER << "th big cookies";
매번 메시지를 남기는 것 대신에 처음 n 이 나타날 때로 출력을 제한할 수도 있다:
LOG_FIRST_N(INFO, 20) << "Got the " << google::COUNTER << "th cookie";
처음 20 번 실행되는 동안에 로그 메시지들을 출력한다.

Debug Mode Support

"debug mode" 로깅 매크로는 디버그 모드에서만 효과가 있고 디버그 모드가 아닐 때는 컴파일에서 제외가 된다. 출시가 되는 어플리케이션에서 지나친 로깅으로 느려지는 것을 피하려면 이 매크로를 사용하면 된다.
DLOG(INFO) << "Found cookies";
DLOG_IF(INFO, num_cookies > 10) << "Got lots of cookies";
DLOG_EVERY_N(INFO, 10) << "Got the " << google::COUNTER << "th cookies";

CHECK Macros

가능한 빠르게 에러를 발견해야 하는 프로그램에서 기대했던 상태를 체크하는 것은 좋은 기능이다. CHECK 매크로는 상태를 만나지 못했을 때, 어플리케이션을 중단하는 기능을 제공한다. 이 매크로는 Standard C 라이브러리에 정의되어 있는 assert 매크로와 유사하다.
CHECK 는 상태가 true 가 아닐 때 어플리케이션을 중단한다. assert 와는 다르게, NDEBUG 에 의해서 컨트롤되지 않기 때문에 컴파일 모드를 무시하고 CHECK 는 실행된다. 그러므로, 다음의 예에 있는 fp-Write(x)는 항상 실행된다:
CHECK(fp->Write(x) == 4) << "Write failed!";
equality/inequality 체크를 하는 다양한 헬퍼 매크로들이 있다 -  CHECK_EQ, CHECK_NE, CHECK_LE, CHECK_LT, CHECK_GE, CHECK_GT. 이 매크로들은 두 값들을 비교하고 기대했던 결과가 나오지 않을 때는 두 값을 포함한 FATAL 메시지를 남긴다. 값들은 정의된 operator<<(ostream, ...) 을 가진다.
다음과 같이 에러 메시지를 붙일 수 있다:
CHECK_NE(1, 2) << ": The world must be ending!";
우리는 각각의 인자는 정확히 한번만 계산이 되고, 함수 인자로서 전달이 될 수 있는 것들은 어떤 것이든 전달이 될 수 있도록 주의를 하고 있다. 특히, 인자들이 구문의 끝에서 파괴되어서 끝나는 임시 표현일 수도 있을 것이다. 예를 들면:
CHECK_EQ(string("abc")[1], 'b');
컴파일러는 인자들 중에 하나가 포인터이고 다른 하나가 NULL 이라면 에러를 남길 것이다. 이에 대한 차선책으로, 간단히 원하는 포인터의 타입에 NULL 로 static_cast 를 하는 것이다.
CHECK_EQ(some_ptr, static_cast<SomeType*>(NULL));
아직 좋지 안다, CHECK_NOTNULL 매크로를 사용하면:
CHECK_NOTNULL(some_ptr);
some_ptr->DoSomething();
이 매크로는 주어진 포인터를 리턴하기 때문에, 생성자 초기화 리스트에 사용하면 유용하다.
struct S {
    S(Something* ptr) : ptr_(CHECK_NOTNULL(ptr)) {}
    Something* ptr_;
};
이러한 것 때문에 C++ 스트림 처럼 이 매크로를 사용할 수 없을 것이다. 어플리케이션이 중단 되기 전에 커스텀 메시지를 남기고 싶다면 위에서 언급한 CHECK_EQ 를 사용해 주기 바란다.
C 스트링(char*)를 비교한다면, 대소문자를 구분하는 매크로가 있고 구분하지 않는 매크로도 있다 - CHECK_STREQ , CHECK_STRNE, CHECK_STRCASEEQ, CHECK_STRCASENE. CASE 버전이 대소문자 구분을 하는 것이다. 이 매크로에는 NULL 포인터도 안전하게 전달할 수 있다. 이 매크로들은 NULL 과 NULL 이 아닌 스트링을 다른 것으로 다룬다. 둘다 NULL 일 경우에는 동일한 것으로 간주된다.
양쪽 인자들이 현재의 전체 표현이 끝나는 지점에서 파괴되는 임시 스트링일 수도 있다(예를 들면, CHECK_STREQ(Foo().c_str(), Bar().c_str()) 에서 Foo와 Bar가 리턴하는 C++의 std::string)
CHECK_DOUBLE_EQ 매크로는 약간의 오차를 허용하면서 두 개의 실수형 값들의 등식을 체크한다. CHECK_NEAR 는 세 번째 실수형 인자를 받아들이고, 세 번째 인자는 허용되는 오차를 명시한다.

Verbose Logging

어려운 버그들을 추적하고 있을 때, 로그 메시지는 매우 유용하다. 하지만, 일반적인 개발에서는 너무 장황한 메시지들을 무시하고 싶기도 할 것이다. 장황한 로그 같은 경우에, glog는 VLOG 매크로를 제공하고 있는데 이 매크로는 사용자만의 수치적 로깅 레벨을 정의할 수 있게 해준다.
--v 커맨드 라인 옵션은 어떤 verbose message 들이 남겨질지를 컨트롤한다:
VLOG(1) << "I'm printed when you run the program with --v=1 or higher";
VLOG(2) << "I'm printed when you run the program with --v=2 or higher";
VLOG 를 사용하면, 낮은 단계의 verbose 레벨의 메시지들이 좀더 남겨질 수 있다. 예를 들면, if --v==1, VLOG(1) 는 로그를 남길 것이고 VLOG(2)는 로그를 남기지 못할 것이다. 이는 INFO가 0이고 ERROR이 2인 severity 레벨과는 반대이다. 1의 --minloglevel은 WARNING 과 그 이상의 메시지를 남길 것이다. VLOG 매크로와 --v 플래그에 대해서 어떤 정수라도 명시할 수 있으며, 그 값들에 대한 일반적인 값은 양의 정수들이다. 예를 들면, VLOG(0)을 사용했다면, 로그를 남기지 않으려면 --v=-1을 명시하거나 0보다 낮은 값을 사용해야 한다. 이는 대부분의 상황에서 기본적으로 장황한 로그를 남기고 싶어하지 않기 때문에 유용한 이슈이다. VLOG 매크로는 항상 INFO 로그 레벨에서 로그를 남긴다(모든 것에서 로그를 남길 때)
Verbose logging은 각각의 모듈 기반으로 커맨드 라인을 사용해서 컨트롤 된다.
--vmodule=mapreduce=2,file=1,gfs*=3 --v=0
은 다음과 같이 수행될 것이다
  • a. mapreduce.{h,cc} 에서 발생되는 낮은 단계의 메시지들과 VLOG(2)를 출력한다.
  • b. file.{h,cc} 에서 발생되는 낮은 단계의 메시지들과 VLOG(1)을 출력한다.
  • c. 접두어가 gfs 인 파일들로부터 발생되는 낮은 단계의 메시지들과 VLOG(3)을 출력한다.
  • d. 어디에서든 발생되는 낮은 단계의 메시지들과 VLOG(0)을 출력한다.
(c) 에서 보인 와일드 카드 기능은 '*' (0이나 문자들과 매치되는)와 '?'(단일 문자와 매치되는) 와일드 카드 둘 다 지원한다. command line flags 도 확인해 보기 바란다.
VLOG_IS_ON(n) "verbose level" 컨디션 매크로도 있다. 이 매크로는 --v 가 n 과 같거나 그 이상일 때 true를 리턴한다. 다음과 같이 사용된다.
if (VLOG_IS_ON(2)) {
    // do some logging preparation and logging
    // that can't be accomplished with just VLOG(2) << .... ;
}
Verbose 레벨 컨디션 매크로 VLOG_IF, VLOG_EVERY_N, VLOG_IF_EVERY_N 은 LOG_IF, LOG_EVERY_N, LOG_IF_EVERY 와 유사하게 행동하지만, 로깅 단계에 대한 레벨 넘버는 severity 레벨과 반대로 적용된다.
VLOG_IF(1, (size > 1024)) << "I'm printed when size is more than 1024 and when you run the program with --v=1 or more";
VLOG_EVERY_N(1, 10) << "I'm printed every 10th occurrence, and when you run the program with --v=1 or more. Present occurence is << google::COUNTER;
VOLOG_IF_EVERY_N(1, (size > 1024), 10)
<< "I'm printed on every 10th occurence of case when size is more than 1024, when you run the program with --v=1 or more. Present occurence is " << google::COUNTER;

Failure Signal Handler

라이브러리는 프로그램이 SIGSEGV 같은 특정 시그널에서 크래시 되었을 때 유용한 정보를 덤프하는 편리한 시그널 핸들러를 제공한다. 시그널 핸들러는 google::InstallFailureSignalHandler() 에 의해서 설치된다. 다음은 시그널 핸들러로부터의 출력 예제이다.
*** Aborted at 1225095260 (unix time) try "date -d @1225095260" if you are using GNU date *** *** SIGSEGV (@0x0) received by PID 17711 (TID 0x7f893090a6f0) from PID 0; stack trace: *** PC: @ 0x412eb1 TestWaitingLogSink::send() @ 0x7f892fb417d0 (unknown) @ 0x412eb1 TestWaitingLogSink::send() @ 0x7f89304f7f06 google::LogMessage::SendToLog() @ 0x7f89304f35af google::LogMessage::Flush() @ 0x7f89304f3739 google::LogMessage::~LogMessage() @ 0x408cf4 TestLogSinkWaitTillSent() @ 0x4115de main @ 0x7f892f7ef1c4 (unknown) @ 0x4046f9 (unknown)
기본적으로, 시그널 핸들러는 standard error로 failure dump를 작성한다. InstallFailureWriter() 를 이용해서 도착지를 커스터마이징할 수 있다.

Miscellaneous Notes

Performance of Messages

glog에 의해서 제공되는 상태에 대한 로깅 매크로들(예를 들면, CHECK, LOG_IF, VLOG, ...)은 주의 깊게 구현되었고 상태가 false 일 때 right hand side expression을 실행하지 않는다. 그래서 다음과 같은 체크는 어플리케이션의 성능을 희생하지 않을 것이다.
CHECK(obj.ok) << obj.CreatePrettyFormattedStringButVerySlow();

User-defined Failure Function

FATAL severity 레벨 메시지들이나 만족되지 않은 CHECK 컨디션은 프로그램을 종료시킨다. InstallFailureFunction 을 사용해서 종료 시키는 행동을 바꿀 수 있다.
void YourFailureFunction() {
    // Reports something....
    exit(1);
}

int main(int argc, char* argv[]) {
    google::InstallFailureFunction(&YourFailureFunction);
}
기본적으로, glog는 스택 트레이스를 덤프하고 프로그램을 1 상태 코드로 끝나게 만들려고 시도한다. 스택 트레이스는 glog 가 스택 트레이싱을 지원하는 아키텍쳐에서 실행했을 때만 만들어진다.

Raw Logging

<glog/raw_logging.h> 헤더 파일은 메모리 할당이나 메모리 락을 사용하지 않는 쓰레드 세이프 로깅에 대해서 사용될 수 있다. 그러므로, 이 헤더 파일에 정의된 매크로들은 low-level 메모리 할당과 동기화 코드에서 사용될 수 있다. 자세한 내용은 src/glog/raw_logging.h 를 확인해 보라.

Google Style perror()

PLOG() 와 PLOG_IF(), PCHECK() 는 errno 의 현재 상태를 기술한 내용에 출력 라인을 붙인 형태의 LOG*, CHECK 처럼 행동한다. 예를 들면,
PCHECK(write(1, NULL, 2) >= 0) << "Write NULL failed";
이 체크는 다음과 같은 에러 메시지를 남기고 실패한다.
F825 185142 test.cc:22] Check failed: write(1, NULL, 2) >= 0 Write NULL failed: Bad address [14]

Syslog

SYSLOG, SYSLOG_IF, SYSLOG_EVERY_N 매크로들도 사용할 수 있다. 이 매크로들은 일반 로그 메시지에 syslog를 더해서 출력을 한다. syslog를 출력하는 것은 과감하게 성능에 영향을 줄 수 있으며, 특히 syslog가 리모트 로깅이 설정되어 있다면 더욱더 그렇다. 이 매크로를 사용하기 전에 syslog에 의한 영향에 대해서 이해를 하고 있어야 한다. 일반적으로, 이 매크로들은 적게 사용하는 것이 현명하다.

Strip Logging Messages

로그 메시지에 사용되는 스트링들은 실행 파일의 사이즈를 증가시킬 수 있으며 프라이버시 문제를 줄 수 있다. 그러므로 GOOGLE_STRIP_LOG 매크로를 사용해서 특정 severity 레벨 하위에 있는 모든 스트링을 제거하도록 glog 에게 지시해야 한다.
어플리케이션이 다음과 같은 코드를 가지고 있다면:
#define GOOGLE_STRIP_LOG 1    // this must go before the #include!
#include <glog/logging.h>
컴파일러는 명시한 정수형 값보다 작은 단계의 로그 메시지를 제거할 것이다. VLOG 는 severity 레벨 INFO(숫자로 0)에서 로그를 남기기 때문에, GOOGLE_STRIP_LOG를 1로 설정하거나 그 이상으로 설정을 한다면 INFO 로그 메시지들과 함께 VLOG로 지정된 모든 로그 메시지들은 제거될 것이다.

Notes for Windows Users

Google 의 glog는 ERROR 라는 severity 레벨을 정의하고 있고 ERROR는 windows.h 에도 정의되어 있다. glog/logging.h 를 포함하기 전에GLOG_NO_ABBREVIATED_SEVERITIES를 정의해서 glog가 INFO, WARNING, ERROR, FATAL을 정의하지 않도록 만들 수도 있다. 이 매크로를 사용하면서, 여전히 iostream 을 사용할 수 있다.
#define GLOG_NO_ABBREVIATED_SEVERITIES
#include <windows.h>
#include <glog/logging.h>

// ...

LOG(ERROR) << "This should work";
LOG_IF(ERROR, x > y) << "This should be also OK";
하지만, glog/logging.h 에 정의되어 있는 INFO, WARNING, ERROR, FATAL  을 더이상 사용할 수 없다.
#define GLOG_NO_ABBREVIATED_SEVERITIES #include <windows.h> #include <glog/logging.h> // ... // This won't work. // google::FlushLogFiles(google::ERROR); // Use this instead. google::FlushLogFiles(google::GLOG_ERROR);
windows.h 에 정의되어 있는 ERROR 가 필요하지 않다면, 종종 작동하지 않는 몇 가지 대안이 있다.
  • #include <windows.h> 전에 #define WIN32_LEAN_AND_MEAN 이나 NOGDI
  • #include <windows.h> 다음에 #undef ERROR
좀더 자세한 내용은 여기로.

댓글 없음:

댓글 쓰기