Log 생성 하기
프로그램을 개발할 때에, 중요한 요소 중의 하나가 쉽게 디버깅하고, runtime시에 발생한 오류에 대해서도 오류상태 값을 표시하여 빠른게 오류를 fix해야 합니다. 이를 위하여 프로그램에서는 log를 파일로 생성합니다. 로그레벨에 따라서 개발시에는 debug용으로 runtime시에는 오류로그만 생성한다든지 하여 성능문제가 발생하지 않도록 해야합니다. 새로운 프로젝트나 프로그램을 개발할 때에 로그를 생성하는 함수들을 만드는 것 자체도 쉽지 않습니다.
그래서 printf(3)함수로 로그를 출력하다가 오류가 fix되면 지운다든 지, 아니면 #if _DEBUG ~ #endif 에 printf(3)를 잔뜩 표시해서 컴파일시 -D_DEBUG option을 주어 debug을 켰다가 껐다가 하는 등, 귀찮은 일입니다.
log 생성 요건
로그 파일을 생성하는 요건들에는 어떤 것들이 있을까요?
1. 로그레벨을 조정할 수 있어야 함. (trace, debug, info, warning, error, fatal)
2. 프로그램의 재컴파일없이 로그 레벨을 조정할 수 있어야 함.
3. 로그가 생성된 위치정보를 정확 표시되어야 함. (어느 소스, 어느 함수의 몇번째 라인)
4. 로그를 생성한 시간이 찍혀야 함. (구간별 성능 확인 등에 사용)
5. 어느 프로세스가 수행했는 지 확인 필요함. (pid를 로깅함)
6. 날짜가 변경되면 자동으로 로그 파일이 날짜에 연동하여 새로운 파일로 변경되어야 함.
(오랫동안 실행되는 daemon 프로그램 들에서 유효함)
위의 요건들을 충족하도록 source를 만들었습니다.
사용방법
1. 로그레벨에 맞는 함수를 사용하여 로그를 생성함.
상세한 디버그용 - 주로 시스템 공통 등 : LOG_TRACE( )
일반 디버그용: LOG_DEBUG( )
중요 정보 로깅용: LOG_INFO( )
경고 로그용: LOG_WARNING( )
오류 로그용: LOG_ERROR( )
치명적 오류로그용: LOG_FATAL( )
- 로그를 생성하는 이들 함수의 사용법은 printf(3)와 같으며 단지
new line만 자동으로 붙이므로 \n는 format이 넣지 않음.
또한 함수의 시작 종료를 찍기 위하여 LOG_CALL( )함수가 있습니다.
2. 환경변수 LOG_LEVEL의 값을 설정하고 실행하면 자동으로 로그레벨이 변경됨.
그렇지 않으면 default로 로그레벨은 INFO로 설정됨
모든 로그를 생성하려면: export LOG_LEVEL=TRACE
DEBUG 이하만 로그 생성하려면: export LOG_LEVEL=DEBUG
INFO 이하의 로그만 생성하려면: export LOG_LEVEL=INFO
WARNING 이하의 로그만 생성하려면: export LOG_LEVEL=WARNING
ERROR 이하의 로그만 생성하려면: export LOG_LEVEL=ERROR
FATAL 로그만 생성하려면: export LOG_LEVEL=FATAL
3. 프로그램의 로그 생성 위치와 로그의 prefix 설정
프로그램이 시작하는 main()함수의 첫번째라인에 LOGsetInfo()호출함.
LOGsetInfo(로그 생성할 디렉토리, 로그파일명); 함수로 설정함.
ex).
#include <libgen.h>
......
LOGsetInfo(".", basename(argv[0]));
현재 디렉토리에 실행파일명 이름을 규칙으로 log 생성 위치 및 이름을 지정함
Source 구현
log_util.h 구현
#ifndef __LOG_UTIL_H__
#define __LOG_UTIL_H__
#define LOG_LVL_TRACE 50
#define LOG_LVL_DEBUG 40
#define LOG_LVL_INFO 30
#define LOG_LVL_WARNING 20
#define LOG_LVL_ERROR 10
#define LOG_LVL_FATAL 0
/*
* 각각의 level에서 LOG를 생성가능한 상태인지를 체크하는 macro
*/
#define LOG_IS_TRACE (LOGgetLevel() >= LOG_LVL_TRACE)
#define LOG_IS_DEBUG (LOGgetLevel() >= LOG_LVL_DEBUG)
#define LOG_IS_INFO (LOGgetLevel() >= LOG_LVL_INFO)
#define LOG_IS_WARNING (LOGgetLevel() >= LOG_LVL_WARNING)
#define LOG_IS_ERROR (LOGgetLevel() >= LOG_LVL_ERROR)
#define LOG_IS_FATAL (LOGgetLevel() >= LOG_LVL_FATAL)
int LOGsetInfo(const char *dir, const char *prefix);
int LOGlogging(char log_type, const char *src_file, const char *func, int line_no, const char *fmt, ...);
int LOGsetLevel(int log_lvl);
int LOGgetLevel(void);
/*
* 함수의 실행 시작과 종료를 log로 생성하는 macro 함수
*/
#define LOG_CALL(func)\
LOG_TRACE("%s #### starting...", #func);\
func;\
LOG_TRACE("%s #### end.", #func)
/*
* Trace Log를 생성하는 macro 함수
*/
#define LOG_TRACE(...) \
do { \
if(LOG_IS_TRACE) { \
LOGlogging('T', __FILE__, __func__, __LINE__, __VA_ARGS__);\
} \
} while(0)
/*
* debug Log를 생성하는 macro 함수
*/
#define LOG_DEBUG(...) \
do { \
if(LOG_IS_DEBUG) { \
LOGlogging('D', __FILE__, __func__, __LINE__, __VA_ARGS__);\
} \
} while(0)
/*
* 중요 정보 Log를 생성하는 macro 함수
*/
#define LOG_INFO(...) \
do { \
if(LOG_IS_INFO) { \
LOGlogging('I', __FILE__, __func__, __LINE__, __VA_ARGS__);\
} \
} while(0)
/*
* warning Log를 생성하는 macro 함수
*/
#define LOG_WARNING(...) \
do { \
if(LOG_IS_WARNING) { \
LOGlogging('W', __FILE__, __func__, __LINE__, __VA_ARGS__);\
} \
} while(0)
/*
* error Log를 생성하는 macro 함수
*/
#define LOG_ERROR(...) \
do { \
if(LOG_IS_ERROR) { \
LOGlogging('E', __FILE__, __func__, __LINE__, __VA_ARGS__);\
} \
} while(0)
/*
* fatal error Log를 생성하는 macro 함수
*/
#define LOG_FATAL(...) \
do { \
if(LOG_IS_FATAL) { \
LOGlogging('F', __FILE__, __func__, __LINE__, __VA_ARGS__);\
} \
} while(0)
#endif // __LOG_UTIL_H__
log_util.c 소스 구현
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <time.h>
#include <sys/time.h>
#include <stdarg.h>
#include <stdlib.h>
#include <log_util.h>
static char log_file_prefix[64];
static char log_folder[1024] = ".";
static FILE *fp_log_file;
static int log_level = LOG_LVL_INFO;
/**
* log level을 조정합니다.
*/
int LOGsetLevel(int log_lvl)
{
int tmp = LOGgetLevel();
log_level = log_lvl;
return tmp;
}
/*
* log level을 최초로 요청할 때에는 환경변수의 설정을 읽습니다.
*/
int LOGgetLevel(void)
{
char *log_env;
static int is_env_check = 0;
if(is_env_check == 0) {
if((log_env = getenv("LOG_LEVEL")) == NULL) {
log_level = LOG_LVL_INFO;
} else {
if(strcmp(log_env, "TRACE") == 0) {
log_level = LOG_LVL_TRACE;
} else if(strcmp(log_env, "DEBUG") == 0) {
log_level = LOG_LVL_DEBUG;
} else if(strcmp(log_env, "INFO") == 0) {
log_level = LOG_LVL_INFO;
} else if(strcmp(log_env, "WARNING") == 0) {
log_level = LOG_LVL_WARNING;
} else if(strcmp(log_env, "ERROR") == 0) {
log_level = LOG_LVL_ERROR;
} else if(strcmp(log_env, "FATAL") == 0) {
log_level = LOG_LVL_FATAL;
} else {
log_level = LOG_LVL_INFO;
}
}
is_env_check = 1;
}
return log_level;
}
/*
* Log file을 생성할 위치와 로그파일의 prefix를 설정함
* LOGset_log_info("/tmp", "mypgm");으로 설정하면
* 로그파일은 /tmp/mypgm-20160101.log 의 형태로 파일을 생성함
*/
int LOGsetInfo(const char *dir, const char *prefix)
{
if(dir == NULL || dir[0] == 0x00) {
fprintf(stderr, "log folder set error.\n");
return -1;
}
if(prefix == NULL || prefix[0] == 0x00) {
fprintf(stderr, "log file prefix set error.\n");
return -1;
}
if(strcmp(dir, log_folder) == 0 && strcmp(prefix, log_file_prefix) == 0) {
return 0;
}
strncpy(log_file_prefix, prefix, 64);
strncpy(log_folder, dir, 1024);
if(fp_log_file != NULL) {
fclose(fp_log_file);
fp_log_file = NULL;
}
return 0;
}
/*
* LOGcreateFile 날짜가 변경되면 자동으로 새로운 파일이 생성됨
*/
static int LOGcreateFile(struct tm *tm1, const char *src_file)
{
char filename[1024];
char *ext;
if(log_folder[0] == 0x00) {
strcpy(log_folder, ".");
}
if(log_file_prefix[0] == 0x00) {
strncpy(log_file_prefix, src_file, sizeof(log_file_prefix));
if((ext = strchr(log_file_prefix, '.')) != NULL) {
*ext = 0x00;
}
}
snprintf(filename, 1024, "%s/%s-%04d%02d%02d.log",
log_folder, log_file_prefix, 1900 + tm1->tm_year, tm1->tm_mon + 1, tm1->tm_mday);
if(fp_log_file != NULL) {
fclose(fp_log_file);
fp_log_file = NULL;
}
if((fp_log_file = fopen(filename, "a")) == NULL) {
return -1;
}
setvbuf(fp_log_file, NULL, _IOLBF, 0);
return 0;
}
/*
* LOGlogging(...) 로그 파일을 생성함.
* log_type, 로그생성일시분초microseconds, process id, 소스파일, 함수명, 라인수, 오류 내용
* 의 format으로 로그를 생성함.
*/
int LOGlogging(char log_type, const char *src_file, const char *func, int line_no, const char *fmt, ...)
{
va_list ap;
int sz = 0;
struct timeval tv;
struct tm *tm1;
static int day = -1;
static pid_t pid = -1;
char src_info[128];
gettimeofday(&tv, NULL);
tm1 = localtime(&tv.tv_sec);
va_start(ap, fmt);
if(pid == -1) {
pid = getpid();
}
/* 날짜가 변경되었으면 또는 최초 실행시에 */
if(day != tm1->tm_mday) {
if(LOGcreateFile(tm1, src_file) != 0) {
return -1;
}
day = tm1->tm_mday;
}
sz += fprintf(fp_log_file, "(%c) ", log_type);
sz += fprintf(fp_log_file, "%04d%02d%02d:%02d%02d%02d%06ld:%05d",
1900 + tm1->tm_year, tm1->tm_mon + 1, tm1->tm_mday,
tm1->tm_hour, tm1->tm_min, tm1->tm_sec, tv.tv_usec, pid);
snprintf(src_info, 128, "%s:%s(%d)", src_file, func, line_no);
sz += fprintf(fp_log_file, ":%-50.50s: ", src_info);
sz += vfprintf(fp_log_file, fmt, ap);
sz += fprintf(fp_log_file, "\n");
va_end(ap);
return sz;
}
활용 예제
사용예). log_test.c
#include <stdio.h>
#include <string.h>
#include <libgen.h>
#include <log_util.h>
int my_test_test_func(int value)
{
LOG_TRACE("TRACE LOG...1");
LOG_DEBUG("TEST");
printf("%d\n", value);
LOG_TRACE("TRACE LOG...2");
LOG_DEBUG("%s", "sample test");
}
int main(int argc, char **argv)
{
int ret;
int value = 0;
/*
* 로그의 위치는 현재 디렉토리에 생성하고 로그 파일명은 실행파일명-yyyymmdd.log
* 로 설정합니다.
*/
LOGsetInfo(".", basename(argv[0]));
LOG_TRACE("TRACE LOG...1");
LOG_DEBUG("TEST");
LOG_TRACE("TRACE LOG...2");
LOG_CALL(ret = my_test_test_func(value));
LOG_DEBUG("%s", "sample test");
LOG_TRACE("TRACE LOG...3");
LOG_ERROR("%s 오류입니다.", "sample test");
LOG_TRACE("TRACE LOG...4");
return 0;
}
위의 소스를 컴파일합니다.
$ gcc -o log_test log_test.c log_util.c -I./
로그레벨을 TRACE로 조정합니다.
$ export LOG_LEVEL=TRACE
프로그램을 컴파일한 프로그램을 실행합니다.
$ ./logtest
실행 결과 log_test-20161204.log 파일이 생성됩니다.
log_test-20161204.log 파일의 내용입니다.
로그레벨 로그생성일:시분마이크로초:프로세스ID:소스파일:함수(라인수) : 출력내용의 format으로 출력됩니다.
example). log_test-20161204.log
(T) 20161204:114121788079:03043:log_test.c:main(22) : TRACE LOG...1
(D) 20161204:114121788211:03043:log_test.c:main(23) : TEST
(T) 20161204:114121788216:03043:log_test.c:main(24) : TRACE LOG...2
(T) 20161204:114121788218:03043:log_test.c:main(25) : ret = my_test_test_func(value) #### starting...
(T) 20161204:114121788221:03043:log_test.c:my_test_test_func(8) : TRACE LOG...1
(D) 20161204:114121788223:03043:log_test.c:my_test_test_func(9) : TEST
(T) 20161204:114121788230:03043:log_test.c:my_test_test_func(11) : TRACE LOG...2
(D) 20161204:114121788233:03043:log_test.c:my_test_test_func(12) : sample test
(T) 20161204:114121788235:03043:log_test.c:main(25) : ret = my_test_test_func(value) #### end.
(D) 20161204:114121788238:03043:log_test.c:main(26) : sample test
(T) 20161204:114121788240:03043:log_test.c:main(27) : TRACE LOG...3
(E) 20161204:114121788242:03043:log_test.c:main(28) : sample test 오류입니다.
(T) 20161204:114121788245:03043:log_test.c:main(29) : TRACE LOG...4
source download :
'C언어 응용 > 활용' 카테고리의 다른 글
strncat(3) - 대체 함수 구현 (0) | 2019.10.04 |
---|---|
strcat(3) - 대체 함수 구현 (0) | 2019.10.04 |
copy_file( ) - 원본 파일의 속성까지 복사하기 (0) | 2019.10.04 |
strncpy시에 한글 반잘림 오류 방지 (0) | 2019.10.04 |
rmdirs( ) - 디렉토리 아래의 모든 파일 및 디렉토리 삭제 (0) | 2019.10.04 |