홈으로 돌아가기

임베디드에서의 로그 레벨: 워터폴 없이 디버깅

CLI 인터페이스를 사용한 임베디드 시스템의 로그 레벨 구현을 설명하는 기사. LOG_* 매크로, facility enum, 진단을 위한 시리얼라이저 다룸. printf 대비 장점: 재컴파일 없이 동적 상세도 설정.

펌웨어 디버깅: 로그 레벨 및 CLI
Advertisement 728x90

임베디드 시스템 디버깅을 위한 효율적인 로깅 레벨 활용

printf 기반 디버깅은 복잡한 시스템일수록 콘솔을 로그로 순식간에 채워버립니다. 로깅 레벨은 런타임 중 CLI를 통해 동적으로 출력 수준을 제어할 수 있어 이 문제를 해결합니다. 이는 유닉스 SysLog 시대부터 표준으로 자리 잡은 방식으로, 각 모듈에 critical, error, info, debug, paranoid 등의 레벨을 할당해 관리합니다.

명령어 ll usb debug는 USB 모듈의 디버깅을 활성화하고, ll pll info는 PLL 모듈을 정보 출력 모드로 설정합니다. PLL 계수를 계산할 때 info 레벨은 최종 결과만 출력하지만, debug 레벨은 반복 알고리즘의 각 단계를 상세히 보여줍니다.

CLI 인터페이스 및 로깅 매크로 구현

애플리케이션은 고유한 facility_t ID를 가진 모듈들로 구성됩니다. CLI(셸/TUI)는 LogLevel(ll) 명령어를 처리하여 모듈별 상세 출력 수준을 구성합니다.

Google AdInline article slot

코드에서는 LOG_ERROR, LOG_WARNING 등의 매크로를 배치합니다. 이 매크로들은 printf와 유사하게 작동하지만 facility 매개변수를 추가로 포함합니다.

void log_write(log_level_t level, facility_t facility, const char* format, ...) {
#ifdef HAS_STREAM
    bool res = log_write_prefix(level, facility);
    if(res) {
        va_list va;
        va_start(va, format);
        cli_vprintf(format, va);
        va_end(va);
        log_write_end();
    }
#endif
}

void LOG_WARNING(facility_t facility, const char* format, ...) {
#ifdef HAS_STREAM
    if(log_write_prefix(LOG_LEVEL_WARNING, facility)) {
        va_list va;
        va_start(va, format);
        cli_vprintf(format, va);
        va_end(va);
        log_write_end();
    }
#endif
}

void LOG_DEBUG(facility_t facility, const char* format, ...) {
#ifdef HAS_STREAM
    if(log_write_prefix(LOG_LEVEL_DEBUG, facility)) {
        va_list va;
        va_start(va, format);
        cli_vprintf(format, va);
        va_end(va);
        log_write_end();
    }
#endif
}

HAS_STREAM 지시어는 코드 수정 없이 릴리즈 빌드에서 로깅 코드를 완전히 제거합니다. 조건부 컴파일 덕분에 해당 매크로는 빈 코드로 치환됩니다.

Facility 정의 및 문자열 매핑

facility는 각 모듈에 고유 ID를 할당하는 열거형(enum)입니다(SYS=1, GPIO, SPI 등). 하드웨어 기능에 따라 조건부 컴파일됩니다.

Google AdInline article slot
typedef enum {
    SYS= 1 ,
    UNKNOWN_FACILITY = 2, 
#ifdef HAS_ACC
    LG_ACC,
#endif
#ifdef HAS_GPIO
    GPIO,
#endif
#ifdef HAS_SPI
    SPI,
#endif
    ALL_FACILITY, /*must be last in enum*/
} facility_t;

FacilityInfo_t 배열은 이러한 ID를 로그 출력 시 사람이 읽을 수 있는 문자열로 매핑합니다.

typedef struct{
    facility_t facility;
    char* name;
}FacilityInfo_t;

#ifdef HAS_MPU
#define MPU_FACILITY_DIAG { .facility = LG_MPU, .name = "Mpu" },
#else
#define MPU_FACILITY_DIAG
#endif

static const FacilityInfo_t FacilityInfo[] = {
    // ...
    EEPROM_FACILITY_DIAG
    MPU_FACILITY_DIAG
    // ...
};

구조체 진단을 위한 시리얼라이저

상수 및 데이터 구조체를 해석하려면 전용 xxx_diag.c 파일에 시리얼라이저를 구현해야 합니다. 이 함수들은 원시 이진 값을 읽기 쉬운 문자열로 변환합니다.

const char* DacLevel2Str(uint8_t code){
    const char *name="?";
    switch(code){
        case DAC_LEV_CTRL_INTERNALY: name="internally"; break;
        case DAC_LEV_CTRL_LOW:       name="low"; break;
        case DAC_LEV_CTRL_MEDIUM:    name="medium"; break;
        case DAC_LEV_CTRL_HIGH:      name="high"; break;
        default:      name="??"; break;
    }
    return name;
}

const char* SpiConfigToStr(const SpiConfig_t* const Config) {
    strcpy(text,"");
    if(Config) {
        sprintf(text, "SPI%u", Config->num);
        snprintf(text, sizeof(text), "%sRate:%u Hz,", text, Config->bit_rate_hz);
        // ... additional fields
    }
    return text;
}

CLI는 초기화 및 로깅 과정에서 이러한 함수를 호출해 구성 상태를 덤프합니다.

Google AdInline article slot

표준 log_level_t 레벨 정의

로그 레벨은 정렬 순서를 보장하기 위해 음수 값을 사용하는 열거형으로 정의됩니다.

typedef enum {
    LOG_LEVEL_UNKNOWN = -5,
    LOG_LEVEL_PARANOID = -4,
    LOG_LEVEL_DEBUG = -3,
    LOG_LEVEL_PROTECTED = -2,
    LOG_LEVEL_NOTICE = -1,
    LOG_LEVEL_INFO = 0,
    LOG_LEVEL_WARNING = 1,
    LOG_LEVEL_ERROR = 2,
    LOG_LEVEL_CRITICAL = 3,
    LOG_LEVEL_COVERAGE = 4,
    LOG_LEVEL_DISABLE = 5,
    LOG_LEVEL_LAST = LOG_LEVEL_DISABLE
} log_level_t;
  • PARANOID (-4): 최대 상세 출력. 로그 과부하 위험이 높음.
  • DEBUG (-3): 디버깅 세부 정보 및 임시 상태 데이터.
  • NOTICE (-1): 주의가 필요한 주요 이벤트.
  • INFO (0): 표준 시스템 운영 상태.
  • WARNING (1): 예기치 않은 동작 또는 잠재적 오류 전조.
  • ERROR (2): 즉각적인 수정이 필요한 결함.
  • CRITICAL (3): 시스템 정지를 초래할 수 있는 심각한 오류.
  • COVERAGE (4): 코드 커버리지 분석을 위한 실행 경로 추적.

핵심 요약

  • 로깅 레벨은 CLI와 완벽하게 연동되어 재컴파일 없이 런타임에서 출력 수준을 미세 조정할 수 있습니다.
  • facility_t ID와 시리얼라이저를 활용하면 로그에 명확한 모듈명과 파라미터 값이 표시되어 가독성이 크게 향상됩니다.
  • 조건부 컴파일(#ifdef HAS_STREAM)을 적용하면 프로덕션 빌드에서의 오버헤드를 최소화할 수 있습니다.
  • LOG_XXX 매크로의 전략적 배치가 중요합니다. 오류에는 ERROR를, 개발 단계 추적에는 DEBUG를 사용하세요.
  • 시리얼라이저는 구조체 진단을 자동화하며, AI 도구와 결합하면 레거시 코드 마이그레이션 속도를 획기적으로 높여줍니다.

— Editorial Team

Advertisement 728x90

다음 읽기