mlog
mlog ¶작성할 프로그램이 시작한 후 금방 끝나거나 빠른 시간내에 여러번 실행하는 것이라면, 버그가 발생했을 때 디버깅하기 쉽습니다. 왜냐하면, 버그가 발생한 원인을 빨리 알 수 있기 때문입니다. 또는 원인을 모른다 하더라도 어떤 상황에 버그가 발생했는지 파악하기가 쉽습니다. 그러나 네트워킹 서버처럼 일단 실행한 다음 오래도록 실행하는 도중 버그가 발생한다면, 원인을 알아내기도 힘들 뿐더러, 어떤 상황에 버그가 발생했는지 알아내기가 매우 힘듭니다. 따라서 이러한 프로그램일 경우에는 프로그램 동작 과정을 파일이나 기타 방법을 통해 기록해 둘 필요가 있습니다. 이런 기록을 보통 log라고 하는데, 여기에서는 프로그램에서 쉽게 쓸 수 있는 로그 관련 기능을 만들어 보도록 하겠습니다.
먼저, 우리가 만들 로그 기능이 가져야 할 것들은 크게 다음과 같이 생각할 수 있습니다:
대개의 경우, 경험상, 항상 로그 메시지를 출력할 때보다는 어떤 조건에 따라서 로그 메시지를 출력할 것인지 아닌지 결정하고 상황에 따라서 로그 메시지를 출력하는 경우가 많았습니다. 따라서 우리가 사용자들에게 제공할 인터페이스는 어떤 조건 x가 참일 경우에 printf(3)와 같은 형식으로 메시지를 출력할 수 있는 것입니다. 결국 다음과 같은 인터페이스를 지니게 됩니다:
int message_logger(condition, const char *format, ...);
message_logger(3.2, "some log message");
#define MLOG(condition, ...) ((condition) ? mlog(__VA_ARGS__) : 0)
Stream Buffer ¶mlog() 함수는 조금 이따가 만들어 보기로 하고, 이 모듈이 제공해야 할 것들을 좀 더 생각해 보기로 합시다. 일단 사용자가 특별히 지정하지 않았다면 로그는 지정한 스트림으로 출력되게 합시다. 이렇게 하기 위하여, 다음과 같이 로그용 스트림을 가지고 있는 게 좋을 것 같습니다:
static FILE *mlog_stream;
FILE *
mlog_get_stream(void) { return mlog_stream; } int mlog_set_stream(FILE *fp) { fflush(fp); if (setvbuf(fp, 0, _IONBF, 0) != 0) return -1; mlog_stream = fp; return 0; } 여러분은 표준 입력 스트림, ‘stdin’과 표준 출력 스트림 ‘stdout’, 그리고 표준 에러 출력 스트림인 ‘stderr’를 이미 알고 있을 것입니다. 그런데 ‘stdout’과 ‘stderr’의 차이점을 모르는 분도 있기 때문에 잠깐 그 차이점에 대해 다루겠습니다. 스트림 타입, 정확히 말해 FILE * 타입으로 연결된 파일은 표준 C 라이브러리가 내부적으로 버퍼를 가지고 있습니다. 따라서 여러분이 fprintf(3), fputs(3), fputc(3), 또는 fwrite(3) 등으로 파일에 쓴다(write) 하더라도 바로 써지지 않을 수 있습니다. 대개의 경우, 어느 크기까지는 버퍼에 쓰게 되고, 이 버퍼가 다 차거나 또는 어떤 특수한 상황이 발생할 경우, 실제 파일에 쓰게 됩니다. 특별히 지정하지 않은 한, ‘stdout’과 ‘stderr’는 똑같이 터미널의 출력으로 지정되어 있지만, ‘stdout’의 경우, 버퍼링(buffering)을 하지만, ‘stderr’는 버퍼링을 하지 않고 바로 출력하게 됩니다. 아까 버퍼가 다 차지 않아도 어떤 특수한 상황이 발생하면 실제 파일에 쓴다고 했습니다. 보통은 newline 문자인 ‘\n’을 출력하면 버퍼의 내용을 출력하게 됩니다. 다음 프로그램을 실행하면 그 차이를 쉽게 알 수 있습니다:
#include <stdio.h>
int main(void) { fprintf(stdout, "hello"); fprintf(stderr, "hi"); fprintf(stdout, ", world!\n"); return 0; } $ gcc tmp.c $ ./a.out hihello, world! $ _출력은 “hello”를 먼저 했지만, ‘stdout’으로 출력되어 버퍼에 대기해 있는 상태입니다. 이 때 버퍼링되지 않는 ‘stderr’로 “hi”를 출력하면 바로 출력되고, 다음으로 ‘stdout’으로 “, world!\n”를 출력하면, 아까 대기하고 있던 “hello”와 함께 출력되어, 실제 터미널로는 “hihello, world!”가 출력됩니다. syslog(3) Interface ¶syslog1는 시스템 로그 메시지를 기록하는 인터페이스입니다. 일반적으로 시스템에는 syslogd(8) 데몬 프로세스가 존재하며, 모든 syslog 로그들을 모아, 지정한 곳에 출력 또는 저장하게 됩니다. 따라서 syslog(3)으로 출력한다는 것은 결국 syslogd(8)에 메시지를 보내고, syslogd(8) 프로세스가 정해진 규칙에 따라 로그를 처리합니다. syslogd(8)로 메시지를 보내기 위해, 대개의 시스템에서는 아래 함수들을 제공합니다:
#include <syslog.h>
void openlog(const char *ident, int option, int facility); void syslog(int priority, const char *format, ...); void closelog(void); #include <stdarg.h> void vsyslog(int priority, const char *format, va_list ap); mlog() ¶다시 본론으로 돌아와서, 이제 mlog()를 만들어 보겠습니다. 먼저, mlog()의 출력은 다음 꼴로 출력될 것입니다:
시간: 프로그램이름 [PID]: 메시지
시간을 출력하기 위해, 우리는 time(2), localtime(3), strftime(3)을 쓰려고 합니다. 프로그램 이름은 전역 변수 ‘program_name’에 저장되어 있다고 가정하겠습니다. 프로세스 id(pid)는 getpid(2)를 써서 얻으려고 합니다. 시간을 얻어내는 함수는 다음과 같이 만들겠습니다:
static const char *current_time(void);
static int using_stream(void);
void
mlog(const char *format, ...) { if (using_stream()) { /* 스트림으로 출력 */ } else { /* syslog로 출력 */ } } 이 절의 앞에서 우리는 이미 로그의 포맷을 정했습니다. syslogd(8)는 알아서 메시지를 포맷시켜 주므로, 우리는 스트림으로 출력할 경우에만 포맷해주면 됩니다. 따라서 위 뼈대에서 using_stream()이 참인 경우에 다음과 같은 코드를 실행합니다.
va_list ap;
fprintf(mlog_stream, "%s: %s[%d]: ", current_time(), program_name, (int)getpid()); va_start(ap, format); vfprintf(mlog_stream, format, ap); va_end(ap); fputc('\n', mlog_stream);
#define MLOG_BUFFER_MAX 1024
static char mlog_buffer[MLOG_BUFFER_MAX];
int ret;
va_list ap; #ifdef HAVE_VSYSLOG vsyslog(LOG_INFO | LOG_USER, format, ap); #else va_start(ap, format); ret = vsnprintf(mlog_buffer, MLOG_BUFFER_MAX, format, ap); va_end(ap); if (ret >= MLOG_BUFFER_MAX || ret < 0) mlog_buffer[MLOG_BUFFER_MAX - 1] = '\0'; syslog(LOG_INFO | LOG_USER, "%s", mlog_buffer); #endif /* HAVE_VSYSLOG */
#define MLOG_BUFFER_MAX 1024
static char mlog_buffer[MLOG_BUFFER_MAX]; void mlog(const char *format, ...) { va_list ap; int ret; if (using_stream()) { fprintf(mlog_stream, "%s: %s[%d]: ", current_time(), program_name, (int)getpid()); va_start(ap, format); vfprintf(mlog_stream, format, ap); va_end(ap); fputc('\n', mlog_stream); } else { #ifdef HAVE_VSYSLOG vsyslog(LOG_INFO | LOG_USER, format, ap); #else va_start(ap, format); ret = vsnprintf(mlog_buffer, MLOG_BUFFER_MAX, format, ap); va_end(ap); if (ret >= MLOG_BUFFER_MAX || ret < 0) mlog_buffer[MLOG_BUFFER_MAX - 1] = '\0'; syslog(LOG_INFO | LOG_USER, "%s", mlog_buffer); #endif /* HAVE_VSYSLOG */ } } mlog.h ¶
/* $Id: mlog,v 1.6 2008/04/10 09:26:22 kss Exp kss $ */
/* * stream/syslog logger module * Copyright (C) 2003, 2004 Seong-Kook Shin <cinsk.shin at samsung.com> */ #ifndef MLOG_H_ #define MLOG_H_ #include <stdio.h> #ifdef HAVE_CONFIG_H # include <config.h> #endif /* This indirect writing of extern "C" { ... } makes Emacs happy */ #ifndef BEGIN_C_DECLS # ifdef __cplusplus # define BEGIN_C_DECLS extern "C" { # define END_C_DECLS } # else # define BEGIN_C_DECLS # define END_C_DECLS # endif #endif /* BEGIN_C_DECLS */ BEGIN_C_DECLS #define MLOG_BUFFER_MAX 4096 /* You should define PROGRAM_NAME somewhere in your code. */ extern const char *program_name; /* Set the stream for logging. * Returns zero on success, otherwise returns -1 */ extern int mlog_set_stream(FILE *fp); /* Get the stream for logging. */ extern FILE *mlog_get_stream(void); extern void mlog(const char *format, ...); #define MLOG(expr, ...) do { if (expr) mlog(__VA_ARGS__); } while (0) END_C_DECLS mlog.c ¶
#include <stdarg.h>
#include <time.h> #include <syslog.h> #include <mlog.h> #define TIME_BUF_MAX 32 static FILE *mlog_stream = 0; static int syslog_opened = 0; #ifndef HAVE_VSYSLOG static char mlog_buffer[MLOG_BUFFER_MAX]; #endif static int using_stream(void) { if (mlog_stream) return 1; if (!syslog_opened) { openlog(program_name, LOG_CONS | LOG_NOWAIT | LOG_PID, LOG_USER); atexit(closelog); syslog_opened = 1; } return 0; } int mlog_set_stream(FILE *fp) { fflush(fp); if (setvbuf(fp, 0, _IONBF, 0) != 0) { /* Warning: cannot empty the stream buffer. */ return -1; } mlog_stream = fp; return 0; } FILE * mlog_get_stream(void) { return mlog_stream; } static const char * current_time() { struct tm *tmptr; time_t t; static char buf[TIME_BUF_MAX]; int ret; t = time(0); tmptr = localtime(&t); ret = strftime(buf, TIME_BUF_MAX, "%b %d %H:%M:%S", tmptr); if (ret == TIME_BUF_MAX || ret == 0) return 0; return buf; } void mlog(const char *format, ...) { va_list ap; int ret; if (using_stream()) { fprintf(mlog_stream, "%s: %s[%d]: ", current_time(), program_name, (int)getpid()); va_start(ap, format); vfprintf(mlog_stream, format, ap); va_end(ap); fputc('\n', mlog_stream); } else { #ifdef HAVE_VSYSLOG vsyslog(LOG_INFO | LOG_USER, format, ap); #else va_start(ap, format); ret = vsnprintf(mlog_buffer, MLOG_BUFFER_MAX, format, ap); va_end(ap); if (ret >= MLOG_BUFFER_MAX || ret < 0) mlog_buffer[MLOG_BUFFER_MAX - 1] = '\0'; syslog(LOG_INFO | LOG_USER, "%s", mlog_buffer); #endif /* HAVE_VSYSLOG */ } } #ifdef MLOG_TEST const char *program_name = "mlog"; int main(int argc, char *argv[]) { FILE *fp; char buf[10]; fp = fopen(argv[1], "a"); if (!fp) return -1; mlog_set_stream(fp); MLOG(1, "hello, %s", "world"); gets(buf); MLOG(1, "hello, %s", "world"); gets(buf); MLOG(1, "hello, %s", "world"); gets(buf); fclose(fp); return 0; } #endif /* MLOG_TEST */ Please see CCodeSnippets
|
Alimony and bribes will engage a large share of your wealth. |










