본문으로 바로가기
homeimage
  1. Home
  2. 컴퓨터/프로그래밍
  3. C언어 로그파일 작성 방법

C언어 로그파일 작성 방법

· 댓글개 · 바다야크

프로그래머의 방패막이 로그 파일의 중요성

프로그램 실행 중에 생성되는 로그 파일은 디버깅에 매우 중요한 자료가 되고 억울함을 풀 수 있는 단초가 되어서 반드시 로그 생성 코드를 작성해야 합니다. 프로그래머라면 로그의 중요함을 잘 알 텐데요, 그럼에도 소홀하게 되고 기능 작성에만 치중하게 됩니다. 바쁘기도 하고 시간이 없을 수도 있지만, 로그 파일을 만드는 것은 습관과도 관계있지 않나 싶습니다. 부지런한 프로그래머라고 해서 로그 파일을 꼭 만들고, 게으른 프로그래머라서 무시하는 것은 아닌 것 같습니다.

번거롭기도 하고 귀찮기도 하지만, 디버깅하는 수고를 덜고 억울한 경험을 피하기 위해서도 로그 파일은 반드시 작성하는 것이 좋은데요, 개인적으로 사용하는 팁 몇 가지를 소개합니다.

  • 로그 생성은 쉬워야 합니다. 사용하기 복잡하면 부지런한 분도 귀찮게 생각되어서 사용하지 않게 됩니다.
  • 로그를 생성하는 애플리케이션을 따로 만들어서 실행합니다.
  • 로그 파일은 가급적 텍스트 파일로 만듭니다.
  • snprintf() 함수를 적극 사용합니다.
  • 통신 프로그램이라면 파싱된 결과만 저장하지 말고 수신된 바이트 그대로 16진 문자로 추가합니다.
  • 통신 프로그램은 프로토콜에 맞지 않는 수신 데이터도 16진 문자로 저장합니다.
  • 시스템이 부팅이 되었다면 반드시 로그에 저장합니다.
  • 프로그램 시작 정보를 반드시 로그에 남깁니다.
  • 불필요한 데이터로 너무 과도하게 생성하지 않아야 합니다.
  • 로그를 만드는 것에 목적을 두지 말고 실제로 도움이 되도록 작성합니다.

로그 작성 애플리케이션 별도 작성

여러 가지 얘기를 꺼냈지만, 몇 가지만 부연 설명을 하겠습니다. 첫 번째로 로그 작성 애플리케이션을 별도로 작성하고 다른 프로그램은 IPC로(프로세서간 통신, Inter-Process Communication) 로그 데이터를 전송하여 저장하는 것이 좋습니다.

로그 파일 대부분 텍스트로 저장해서 간단하게 보이지만, 파일로 저장하는 만큼 주의해야 할 것이 있습니다. 한 가지 예를 들면 전원이 갑자기 꺼지는 것을 반영하지 않아서 기능을 보강해야 하는데, 프로그램마다 로그 기록 루틴을 사용했다면 개별적으로 모두 수정해야 합니다. 로그 프로그램을 따로 작성했다면 그 프로그램만 업그레이드하면 됩니다.

이외에도 로그 작성 애플리케이션을 따로 실행하면 아래와 같은 장점이 있습니다.

  • 앞서 언급했듯이 업그레이드하기가 쉽습니다.
  • 여러 개의 애플리케이션이 실행되더라도 하나의 로그 파일로 생성할 수 있습니다.
  • 애플리케이션마다 로그 파일을 생성하지 않기 때문에 로그 파일 관리하기가 쉽습니다. 잔여 용량을 확보하고 기간에 따라 예전 파일을 제거하는 등의 일은 로그 애플리케이션에 맡기면 됩니다.

snprintf() 함수를 적극 사용

텍스트 로그 파일을 작성한다면 sprintf()만큼 편안한 함수가 없습니다. 그러나 sprintf() 함수는 버퍼 크기를 확인하지 않아서 위험할 수 있습니다. 버퍼 크기를 알려주면 버퍼 오버플로우(buffer overflow, buffer overrun) 걱정이 없는 snprintf() 함수를 사용하는 것이 안전합니다.

로그는 프로그램 실행에 방해되지 않도록 빨리 생성해야 하는데 sprintf()처럼 느린 함수를 사용하라니, 더욱이 더 느린 snprintf()를... 맞습니다. printf() 함수 계열은 사용자가 지정한 다양한 포맷에 맞추어 변수 값을 변환해서 버퍼에 차례로 넣어 주어야 해서 기능이 작지 않은 함수입니다. 무거운 함수이죠.

그러나 로그 데이터를 쉽게 작성할 수 있습니다. 로그 작성이 쉬워야 사용하게 됩니다.

예를 들어 보겠습니다. 가상으로 rs232로부터 수신한 데이터를 로그에 저장하기 위해 문자열을 생성하는 코드입니다.

#include <stdio.h>
#include <string.h>
#include <unistd.h>

typedef struct
{
    int     id;
    int     value;
    char    text[20];
} __attribute__ ((packed)) data_t;

// rs232 가상 데이터 수신 함수
int get_rs_data( char *buff, int sz_buff){

    char rs_data[] =  { 0x63, 0x00, 0x00, 0x00, 0x0f, 0x27, 0x00, 0x00, 0x62, 
                        0x61, 0x64, 0x61, 0x79, 0x61, 0x6b, 0x2e, 0x63, 0x6f, 
                        0x6d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
                        0x00};   // rs232로부터 수신한 데이터

    memset( buff, 0, sz_buff);
    memcpy( buff, rs_data, sizeof( rs_data));
    return sizeof( rs_data);
}
int main( void){

    char    buff[1024];                 // rs232로부터 수신한 데이터
    char    log[1024];                  // 로그 출력 문자열
    int     sz_data;                    // rs232 수신 데이터 길이 

    sz_data = get_rs_data( buff, sizeof( buff));
    data_t  *p_data = (data_t *)buff;
    memset( log, 0, sizeof( log));
    snprintf( log, sizeof(log), "from rs232\n\
  ID    : %d\n\
  Value : %d\n\
  Text  : %s\n", p_data->id, p_data->value, p_data->text);

    printf( "%s\n", log);              // 로그 내용 확인
}

실행하면 아래와 같이 출력됩니다.

$ ./a.out
from rs232
  ID    : 99
  Value : 9999
  Text  : badayak.com

$

위의 코드를 snprintf()를 사용하지 않고 숫자를 문자 변환하면서 작성해 보면 snprintf()가 얼마나 고마운 함수인지 절실히 깨닫게 됩니다.

통신 프로그램이라면 수신 데이터도 추가

통신 프로그램의 경우 파싱된 데이터만 출력하기보다는 수신 데이터도 추가하는 것이 좋습니다. 아래 코드는 위의 예제에서 수신 데이터를 16진 코드로 변환해서 추가하도록 수정했습니다.

int main( void){

    char    buff[1024];                 // rs232로부터 수신한 데이터
    char    log[1024];                  // 로그 출력 문자열
    int     sz_data;                    // rs232 수신 데이터 길이 

    sz_data = get_rs_data( buff, sizeof( buff));

    data_t  *p_data = (data_t *)buff;
    memset( log, 0, sizeof( log));

    snprintf( log, sizeof(log), "from rs232\n\
  ID    : %d\n\
  Value : %d\n\
  Text  : %s\n\
  >> ", p_data->id, p_data->value, p_data->text);

    char *ptr = log + strlen( log);
    for ( int ndx = 0; ndx < sz_data; ndx++){
        sprintf( ptr, "%02x ", buff[ndx] & 0xff); // <-- 버퍼 오버플로우가 걸리지 않도록 주의
        ptr += 3;
    }
    sprintf( ptr, "\n");                              
    
    printf( "%s\n", log);              // 로그 내용 확인
}

실행한 모습은 아래와 같습니다.

$ ./a.out
from rs232
  ID    : 99
  Value : 9999
  Text  : badayak.com
  >> 63 00 00 00 0f 27 00 00 62 61 64 61 79 61 6b 2e 63 6f 6d 00 00 00 00 00 00 00 00 00 

$

이렇게 수신 데이터를 함께 추가하면 나중에 분석 결과를 따질 때 도움이 됩니다.

시스템 부팅과 프로그램 시작 로그

시스템이 부팅할 때마다 시간만이라도 꼭 로그에 넣어야 합니다. 1년 내내 365일 24시간 운용되는 시스템이라면 더욱 부팅 시간이 중요합니다. 아울러 로그 애플리케이션은 다른 애플리케이션으로부터 로그 입력이 없다면 주기적으로 로그에 시간 기록하는 것도 필요합니다. 이렇게 로그를 쌓는 이유는 시스템이 실제로 24시간 운영하고 있는지의 여부를 확인할 때 필요합니다.

시스템 부팅 로그가 많거나 일정 시간 동안 아무런 로그가 없다면 시간 정보를 참고하여 문제점을 찾아야 합니다. 애플리케이션도 너무 자주 재 실행된다면 그 원인을 찾아야겠습니다.

SNS 공유하기
💬 댓글 개
최근글
이모티콘창 닫기
울음
안녕
감사해요
당황
피폐

이모티콘을 클릭하면 댓글창에 입력됩니다.