Poziomy logowania dla efektywnego debugowania systemów wbudowanych
Debugowanie za pomocą printf często prowadzi do przepełnienia konsoli logami, zwłaszcza w złożonych systemach. Poziomy logowania rozwiązują ten problem, umożliwiając dynamiczne zarządzanie szczegółowością komunikatów (verbosity) za pośrednictwem interfejsu CLI w czasie rzeczywistym. To standardowa praktyka wywodząca się jeszcze z czasów Unix SysLog, gdzie dla każdego modułu definiuje się osobne poziomy: critical, error, info, debug, paranoid.
Polecenia takie jak ll usb debug aktywują debugowanie dla modułu USB, a ll pll info ustawia poziom informacyjny dla PLL. Podczas obliczania współczynników PLL na poziomie info wyświetlany jest wyłącznie wynik końcowy, natomiast na poziomie debug widoczne są poszczególne kroki algorytmu iteracyjnego.
Implementacja interfejsu CLI oraz makr logowania
Oprogramowanie budowane jest z modułów posiadających unikalne identyfikatory facility_t. Interfejs CLI (shell/TUI) obsługuje polecenia LogLevel (ll), umożliwiające konfigurację szczegółowości logów dla poszczególnych modułów.
W kodzie źródłowym umieszcza się makra takie jak LOG_ERROR, LOG_WARNING itp., które działają podobnie do printf, ale przyjmują dodatkowy parametr 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
}
Dyrektywa HAS_STREAM pozwala wyłączyć logi w wersji produkcyjnej bez modyfikacji kodu źródłowego – makra są wówczas kompilowane do pustych instrukcji.
Definiowanie identyfikatorów facility i mapowanie na ciągi znaków
Facility to typ wyliczeniowy (enum) z unikalnymi ID dla modułów (np. SYS=1, GPIO, SPI itd.), które można warunkowo kompilować:
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;
Tablica FacilityInfo_t konwertuje identyfikatory na czytelne nazwy używane w logach:
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
// ...
};
Serializatory do diagnostyki struktur
Do interpretacji stałych i struktur niezbędne są serializatory umieszczane w plikach xxx_diag.c. Konwertują one wartości binarne na czytelne dla człowieka ciągi znaków:
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);
// ... dodatkowe pola
}
return text;
}
Interfejs CLI wywołuje te funkcje podczas inicjalizacji oraz generowania logów, aby wyświetlać aktualne konfiguracje.
Standardowe poziomy log_level_t
Definicja poziomów jako typu enum z ujemnymi wartościami ułatwia ich sortowanie:
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): Maksymalna szczegółowość, ryzyko „lawiny” logów.
- DEBUG (-3): Informacje debugowe, dane tymczasowe.
- NOTICE (-1): Istotne zdarzenia wymagające uwagi.
- INFO (0): Standardowe działanie systemu.
- WARNING (1): Nieoczekiwane zdarzenia, zwiastuny błędów.
- ERROR (2): Błędy wymagające naprawy.
- CRITICAL (3): Krytyczne awarie, zatrzymanie systemu.
- COVERAGE (4): Śledzenie ścieżek wykonania kodu.
Kluczowe wnioski
- Poziomy logowania integruje się poprzez CLI, co umożliwia konfigurację w czasie rzeczywistym bez konieczności rekompilacji.
- Typ
facility_toraz serializatory zapewniają czytelność logów, wyświetlając nazwy modułów i parametrów. - Kompilacja warunkowa (
#ifdef HAS_STREAM) minimalizuje narzut wydajnościowy w wersji produkcyjnej. - Prawidłowe rozmieszczenie makr
LOG_XXXjest kluczowe: dla błędów używamyERROR, a do debugowania –DEBUG. - Serializatory automatyzują diagnostykę struktur, przyspieszając migrację starszego kodu (legacy) z wykorzystaniem sztucznej inteligencji.
— Editorial Team
Brak komentarzy.