Un esempio di utility di log - STB1019/SkullOfSummer GitHub Wiki

Introduzione

Una componente importante di ogni software è il log, utile per capire il comportamento del software in runtime. Linux dispone di un log di sistema, il "syslog". Tuttavia, come piccola utility, è utile provare a creare una propria utility di log minimale. Un esempio di log utility la potete trovare qui.

Logging

Costruiamo ora una utility di log semplicissima.

Idea di base

L'idea di base è che la utility di log dovrebbe essere:

  1. semplice da usare;
  2. non influenzante le performance del software;
  3. con diversi livelli di log, dal livello di debug fino ad un livello più generale.

Una utility di log deve provvedere a diversi livelli di messaggi di log. Per esempio solitamente si dispongono dei seguenti "livelli di log" (in ordine dal meno importante al più importante):

  • DEBUG;
  • INFO;
  • WARNING;
  • ERROR;
  • CRITICAL;

Disattivando un livello, si disattivano automaticamente tutti i livelli sottostanti. Per esempio disattivando il livello di WARNING, anche INFO e DEBUG saranno disattivati.

Dei buoni concetti per implementare il log sono le macro: esse possono essere scartate in fase di preprocessing, evitando quindi di impattare sulle performance. Inoltre le macro sono notevolmente più flessibili delle funzioni in quanto sono in grado di nascondere codice boiler plate.

Per esempio possiamo creare una macro per ogni livello di log:

#define info(format, ...) printf(format, ## __VA_ARGS__); printf("\n")

Possiamo inoltre usare i comandi del preprocessore condizionale per dire al preprocessore cosa fare quando trova una macro di logging:

#ifdef QUICK_LOG > 3
#    define info(format, ...) 0
#else
#    define info(format, ...) printf(format, ## __VA_ARGS__); printf("\n")
#endif

Potremo quindi attivare o meno il livello "info" semplicemente compilando il file con -DQUICK_LOG=4 ("info" non verrà nemmeno inserita nel codice sorgente) o -DQUICK_LOG=2 ("info" verrà eseguita).

Per poter usare il log basterà importare "log.h" nel tuo programma.

Identificazione del log

Una prima miglioria sarebbe quella di inserire automaticamente nel log il file ed il numero di riga del log: sicuramente è capitato anche a voi di avere un printf in un programma grosso che volete togliere e che non sapete più dove è! Con questa miglioria potrete individuare facilmente il log che vi interessa!

#ifdef QUICK_LOG > 3
#    define info(format, ...) 0
#else
#    define info(format, ...) printf("%s:%s[%d] ", __FILE__, __func__, __LINE__); printf(format, ## __VA_ARGS__); printf("\n")
#endif

Appenders

E se voleste scrivere non sullo standard output ma da un'altra parte? I tipi che riferiscono a funzione qui possono tornarvi utili! Potete creare una funzione che inglobi la serie di printf (dovrà essere una funzione con numero variabile di argomenti dato che maschera un printf (esempio: info("hello");, info("hello %s", "Paolo"); info("hello %s %s", "Paolo", "Rossi"); e che al suo interno usi una funzione "appender". Eventualmente è possibile poi settare questa funzione "appender" come si vuole. Per esempio:

//log.h
extern log_appender appenderFunction;

#ifdef QUICK_LOG > 3
#    define info(format, ...) 0
#else
#    define info(format, ...) __log(__FILE__, __func__, __LINE__, format, ## __VA_ARGS__)
#endif

//log.c

void printOnConsole(const char* fileName, const char* funcName, int lineno, const char* format, ...) {
    //print log on console
}

void logOnSomethingElse(const char* fileName, const char* funcName, int lineno, const char* format, ...) {
}

log_appender appenderFunction = printOnConsole;

Quando vuoi cambiare appender basterà eseguire:

appenderFunction = logOnSomethingElse;