Niveaux de journalisation pour un débogage efficace des systèmes embarqués
Le débogage par printf sature rapidement la console de journaux, en particulier dans les systèmes complexes. Les niveaux de journalisation résolvent ce problème en permettant un contrôle dynamique de la verbosité via une interface CLI à l'exécution. Cette pratique est devenue un standard depuis l'ère Unix SysLog, où chaque module se voit attribuer des niveaux : critique, erreur, info, debug, paranoïaque.
Des commandes telles que ll usb debug activent le débogage pour le module USB, tandis que ll pll info configure le module PLL en mode informatif. Lors du calcul des coefficients PLL, le niveau info n'affiche que le résultat final, alors que le niveau debug révèle chaque étape de l'algorithme itératif.
Implémentation de l'interface CLI et des macros de journalisation
L'application est structurée en modules, chacun disposant d'un identifiant unique facility_t. L'interface CLI (shell/TUI) traite les commandes LogLevel (ll) pour configurer la verbosité par module.
Dans le code, on utilise des macros telles que LOG_ERROR, LOG_WARNING, etc. Elles fonctionnent de manière similaire à printf, mais intègrent un paramètre de module :
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
}
La directive HAS_STREAM permet d'exclure la journalisation des versions de production sans modifier le code source : les macros se réduisent simplement à rien lors de la compilation.
Définition des modules et mappage des chaînes
Une « facility » (ou module) est une énumération qui attribue des identifiants uniques aux composants (SYS=1, GPIO, SPI, etc.), compilée de manière conditionnelle selon les fonctionnalités matérielles :
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;
Le tableau FacilityInfo_t associe ces identifiants à des chaînes lisibles pour l'affichage des journaux :
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
// ...
};
Sérialiseurs pour le diagnostic des structures
Pour interpréter les constantes et les structures de données, il est nécessaire d'implémenter des sérialiseurs dans des fichiers dédiés xxx_diag.c. Ils convertissent les valeurs binaires brutes en chaînes de caractères lisibles :
const char* DacLevel2Str(uint8_t code){
const char *name="?";
switch(code){
case DAC_LEV_CTRL_INTERNALLY: 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;
}
L'interface CLI appelle ces fonctions lors de l'initialisation et de la journalisation pour dumper les états de configuration.
Niveaux standards log_level_t
Les niveaux de journalisation sont définis via une énumération utilisant des valeurs négatives pour garantir un tri correct :
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) : Verbosité maximale, avec un risque élevé de saturation des journaux.
- DEBUG (-3) : Détails de débogage et données d'état temporaires.
- NOTICE (-1) : Événements notables nécessitant une attention particulière.
- INFO (0) : Fonctionnement standard du système.
- WARNING (1) : Comportements inattendus ou signes avant-coureurs de défaillances.
- ERROR (2) : Anomalies nécessitant une correction immédiate.
- CRITICAL (3) : Défaillances graves susceptibles de bloquer le système.
- COVERAGE (4) : Suivi des chemins d'exécution pour l'analyse de la couverture de code.
Points clés à retenir
- Les niveaux de journalisation s'intègrent parfaitement à l'interface CLI pour un ajustement à l'exécution, sans recompilation.
- Les identifiants
facility_tet les sérialiseurs garantissent la lisibilité des journaux grâce à des noms de modules et des valeurs de paramètres clairs. - La compilation conditionnelle (
#ifdef HAS_STREAM) minimise la surcharge dans les versions de production. - Le placement stratégique des macros
LOG_XXXest crucial : utilisezERRORpour les anomalies etDEBUGpour le suivi en phase de développement. - Les sérialiseurs automatisent le diagnostic des structures, accélérant considérablement la migration du code legacy lorsqu'ils sont couplés à des outils d'IA.
— Editorial Team
Aucun commentaire pour le moment.