La nostra implementazione è meglio della tua... |
#include <time.h> #include <sys/time.h> #include <stdarg.h> #include <stdio.h> #include "mylog.h" // prototipi locali static char *getDateUsec(char *dest, size_t size); // variabili locali static FILE *mylog_fp; static int mylog_level;Non starò a descrivere gli include-files, che sono, ne più ne meno, solo quelli che servono. Sembra lapalissiano, ma è normale trovare sorgenti con molti più include del necessario: non fanno danni ma complicano l'interpretazione visiva del codice. Ricordatevi di mettere solo quelli che servono.
La seconda sezione che troviamo è quella dei "prototipi locali", che descrive le funzioni statiche di questo file, quelle che non devono essere esportate agli utilizzatori del mylog. In questo caso c'è getDateUsec(), che descriveremo più avanti.
Poi abbiamo la sezione "variabili locali", che sono, però, globali al file: so che le globali sono una maledizione (come già descritto qui), ma in alcuni casi (ad esempio in questo) è difficile farne a meno. E poi sono statiche al file, quindi ci sentiremo meno in colpa. Le variabili locali sono due: una è un FILE* che descrive il file di log (mylog_fp), mentre l'altra (mylog_level) è un int che serve per gestire il livello di log (che, come visto nel post introduttivo è impostato su quattro valori).
Dopodichè passiamo alla vera e propria implementazione, e incontriamo le prime due delle tre funzioni della nostra libreria, myOpenLog() e myCloseLog():
Come vedete sono molto semplici: myOpenLog() apre con fopen() il file che passiamo in argomento (la stringa fname), e setta il loglevel con il valore passato col secondo argomento (l'int level). Il file lo apriremo in modo append, visto che vogliamo un logfile incrementale, riservandoci di azzerarlo quando ci sarà necessario./* myOpenLog() * Apre una connessione al logger myLog per un programma. */ void myOpenLog(const char *fname, int level) { // apre il logfile e set level mylog_fp = fopen(fname, "a"); mylog_level = level; } /* myCloseLog() * Chiude il descrittore usato per scrivere al logger myLog. */ void myCloseLog() { // chiude il logfile fclose(mylog_fp); }
L'altra funzione, myCloseLog(), si limita, semplicemente, a rilasciare con fclose() il descrittore ottenuto con myOpenLog().
E adesso veniamo al cuore del sistema di log: la funzione myLog():
La prima operazione che esegue myLog() è il test del livello di log: questo è fondamentale, perché ci permette generare solo i messaggi di livello minore o uguale a quello settato con myOpenLog(), il che include anche la non-generazione di messaggi, se mylog_level è MYLOG_NOLOG. E in quest'ultimo caso l'unica operazione eseguita è il test nel if, per cui, se decidiamo di non usare il sistema di log, il carico per la CPU e il sistema di I/O è praticamente nullo (era nelle specifiche di progetto!)./* myLog() * Genera un messaggio di log per il logger myLog. */ void myLog(int level, const char *format, ...) { // test livello di log if (level <= mylog_level) { // compone logstr con argomenti multipli va_list arglist; va_start(arglist, format); char logstr[128]; vsnprintf(logstr, sizeof(logstr), format, arglist); va_end(arglist); // scrive logstr con data+ora e flush dati char date[128]; fprintf(mylog_fp, "%s - %s\n", getDateUsec(date, sizeof(date)), logstr); fflush(mylog_fp); } }
Dopodiché, usando le funzione della famiglia stdarg, possiamo gestire il messaggio da visualizzare (vedi dopo il commento "compone logstr con argomenti multipli") che usa una formattazione tipo printf() (come usato in main.c). Non sto a spiegarvi come funzionano stdarg e printf() (non è l'argomento di questo post) comunque potremmo tornarci su in futuro... per il momento vi rimando alla amplissima documentazione rintracciabile con San Google.
A questo punto, la funzione termina scrivendo nel file di log (vedi dopo il commento "scrive logstr con data+ora e flush dati") usando fprintf().
L'ultima istruzione è fflush(): perchè? Ok, aggiunge un overhead di I/O, ma un buon sistema di log deve garantire la scrittura immediata del messaggio appena composto, altrimenti in caso di crash del programma, o, più semplicemente, in applicazioni multi-processo, potremmo, stranamente, leggere messaggi con ordine temporale sbagliato, il che in fase di debug, sperimentazione o analisi di risultati potrebbe confonderci le idee.
Cosa ve ne sembra dell'implementazione? Non è male, no? Beh, ovviamente, ho fatto delle semplificazioni, perché l'idea base è sempre quella di proporre esempi che abbiano stile e funzionalità e che, poi, si possono perfezionare aggiungendo i controlli che mancano (tipo controllare se il descrittore FILE* è valido prima di usarlo, ecc.). L'importante è che l'idea di base sia corretta e si possa usare come traccia per prodotti reali. Provate a copiare e compilare il tutto, vi assicuro che funziona (come tutti gli esempi che propongo nel blog, eh!).
In una prossima puntata parleremo della funzione locale getDateUsec(), per terminare in bellezza l'argomento. Come sempre vi invito a non trattenere il respiro nell'attesa...
Ciao e al prossimo post!
Nessun commento:
Posta un commento