나만의 간단한 로그출력 함수 만들기

반응형

소프트웨어를 개발할 때, 소프트웨어의 동작이나 상태, 오류 등을 확인하기 위한 로그 출력 기능이 필요할 수 있다. 

예를 들어, 가장 많이 사용되는 로그 출력은 printf() 함수를 이용한 화면 출력이며, 필요에 따라 파일에 저장하는 방식도 사용된다.

 

또한 동작 중 발생하는 이벤트의 종류나 우선순위(중요도)에 따라 로그의 레벨을 다르게 설정하고, 원하는 우선순위 이상의 로그만을 출력하는 기능도 필요할 수 있다.

 

본 글에서는

설정된 로그레벨 이상의 우선순위를 갖는 로그를 출력하는 함수를 구현하는 방법을 소개한다.

또한 필요에 따라 로그 출력문에 부가적인 정보를 추가하되, 매크로를 활용함으로써 함수 호출 형식 자체는 복잡하지 않게 구현하는 방법을 소개한다.

 

본 글에서 소개하는 로그 출력 함수는 다음과 같은 기능을 지원한다.

  • g_log 라는 이름의 전역변수를 가지며, 해당 변수는 출력하고자 하는 로그레벨의 최대값으로 설정된다.
  • 로그 출력문에는 로그 메시지 외에도 다음과 같은 부가정보가 포함된다.
    • 로그 출력 시간
    • 해당 로그출력함수를 호출한 함수명 (__FUNCTION__)
    • 본 글의 예제에는 포함되어 있지 않지만, 필요에 따라 __FILE__, __LINE__ 등의 매크로를 추가하여 로그함수가 호출된 파일명이나 라인번호 정보 등도 함께 출력하도록 할 수 있다.
< log.h >

#include <stdio.h>

/**
 * @brief 로그레벨/우선순위 정의. 값이 낮을수록 우선순위가 높다.
 */
enum {
  kLogLevel_err,  ///< 에러 로그
  kLogLevel_warn, ///< 경고 로그
  kLogLevel_info, ///< 정보 로그
  kLogLevel_debug ///< 디버그 로그
} eLogLevel;

extern eLogLevel g_log;

/*
 * 로그출력 매크로
 *  - 에러에 해당되는 로그출력에 대해서는, 편의상(코드가독성을 위해) 
 *    별도의 Err 매크로를 정의한다.
 *  - 컴파일 옵션으로 "DEBUG_"가 정의되면("-DDEBUG_") 로그출력기능이 함께 빌드되며, 
 *    정의되지 않으면 NULL 동작으로 빌드된다.
 */
#ifdef DEBUG_
#define Log(l, f, a...) \
  do {  \
    if (g_log >= l) { \
      PrintLog(__FUNCTION__, f, ## a); \
    } \
  } while(0)
#define Err(f, a ...)  \
  do {  \
    if (g_log >= kLogLevel_err) { \
      PrintLog(__FUNCTION__, f, ## a); \
    } \
  } while(0)
#else
#define Log(l, f, a ...) do {} while(0)
#define Err(f, a ...) do {} while(0)
#endif
< log.c >

#include "log.h"

/**
 * 로그레벨 변수. 
 * - 에러로그는 기본적으로 출력되는 것이 좋기 때문에, 
 *   kLogLevel_err 값을 기본값으로 설정한다.
 */
eLogLevel g_log = kLogLevel_err; 

/**
 * @brief 로그메시지 출력함수 구현부. 로그 메시지를 출력한다.
 * @param func 로그 출력을 수행하는 함수 이름
 * @param format 출력 라인
 * @param ... 출력 라인
 *
 * 본 함수는 직접 호출되지 않으며, 항상 Log() 및 Err() 매크로를 통해 간접 호출된다.
 * 전달된 출력문 앞에 함수명 및 호출시간이 추가되어 표준에러(stderr)로 출력된다.
 */
void PrintLog(const char *func, const char *format, ...)
{
  va_list arg;
  struct timespec ts;
  struct tm tm_now;

  clock_gettime(CLOCK_REALTIME, &ts);
  localtime_r((time_t *)&ts.tv_sec, &tm_now);
  fprintf(stderr, "[%04u%02u%02u.%02u%02u%02u.%06ld]", 
    tm_now.tm_year+1900, 
    tm_now.tm_mon+1, tm_now.tm_mday,
    tm_now.tm_hour, tm_now.tm_min, 
    tm_now.tm_sec, ts.tv_nsec / 1000);

  fprintf(stderr, "[%s][%s] ", dev, func);
  va_start(arg, format);
  vfprintf(stderr, format, arg);
  va_end(arg);
}
< do_something.c >

#include "log.h"

void init(void)
{
  g_log = kLeveLevel_info;
}

void do_something(void)
{
  int ret = do_something();
  // 실패 시
  if (ret < 0) { 
    // g_log 값이 kLogLevel_err 이상일 경우에만 다음 로그가 출력된다.
    Err("Fail to do something - result: %d\n", ret);
  } 
  // 성공 시
  else {  
    // g_log 값이 kLogLevel_info 이상일 경우에만 다음 로그가 출력된다.
    Log(kLogLevel_info, "Success to do something - result: %d\n", ret);
  }
}

void main(void)
{
  init();
  do_something();
}

댓글

Designed by JB FACTORY