Nei titoli e nei testi troverete qualche rimando cinematografico (ebbene si, sono un cinefilo). Se non vi interessano fate finta di non vederli, già che non sono fondamentali per la comprensione dei post...

Di questo blog ho mandato avanti, fino a Settembre 2018, anche una versione in Spagnolo. Potete trovarla su El arte de la programación en C. Buona lettura.

domenica 19 gennaio 2014

Syslog Royale
come scrivere un Syslog in C - pt.1

Quando si scrive Software bisogna sempre cercare di essere professionali, qualunque sia l'uso del Software che stiamo scrivendo. Ad esempio non ci si può dimenticare di prevedere un buon sistema di log (ci sono, poi, alcuni professionisti della programmazione che non sanno neanche cosa è un buon sistema di log... ma questa è un altra storia).

Per aggiungere un log al nostro Software possiamo aiutarci col sistema operativo, ad esempio in UNIX e Linux abbiamo Syslog che è molto flessibile e ci semplifica la vita. Nel caso di non potere (o non volere) usare i mezzi forniti dal OS si può pensare di scriversi un proprio sistema di log, come vedremo nel seguito di questo post. Cercheremo di emulare, più o meno, sintassi e funzionamento di Syslog, cercando di realizzare un prodotto semplice ma di qualità, cioè un prodotto professionale, perché noi cerchiamo sempre di essere professionali, magari senza arrivare agli eccessi di professionalità di quello del titolo.
Mi chiamo Log, James Log
Sia perché non voglio fare un post troppo lungo (diventerebbe noioso), sia per seguire una specie di approccio specifica+implementazione (adattato a un C-Blog), ho diviso il post in due puntate: nella prima descriverò, a mo' di specifica funzionale, l'header file (mylog.h) e un esempio di uso (main.c). Nella seconda puntata descriverò la vera e propria implementazione.

Vediamo l'header file, mylog.h:
// livelli di mylog
#define MYLOG_NOLOG   -1 // nessun messaggio di log
#define MYLOG_ERROR   0  // condizione di errore
#define MYLOG_WARNING 1  // condizione di warning
#define MYLOG_DEBUG   2  // messaggi di debug

// prototipi globali
void myOpenLog(const char *fname, int level);
void myLog(int level, const char *format, ...);
void myCloseLog();
Semplice e auto-esplicativo, no? Il nostro prodotto è formato da tre funzioni canoniche (apri, usa, chiudi), e lavora a livelli di gravità (per il momento quattro). Quasi inutile spiegare che, settando un livello, nel logfile appariranno tutti i messaggi di uguale o maggiore gravità del livello selezionato. Il livello NOLOG, poi, è in pratica un flag di disabilitazione che ci permette di avviare l'applicazione senza logfile, per alleggerire l'esecuzione: è scontato che deve essere una disabilitazione vera, cioè la CPU e il sistema di I/O devono veramente lavorare meno senza log attivato.

Vediamo ora l'esempio d'uso, main.c:
#include <stdio.h>
#include <stdlib.h>
#include "mylog.h"

int main(int argc, char* argv[])
{
    // test argomenti
    if (argc != 3) {
        printf("mylog: wrong arguments counts\n");
        printf("usage: mylog logfile loglevel [e.g.: mylog log.txt 1]\n");
        return EXIT_FAILURE;
    }

    // open log: set logfname e loglevel (0=error/1=warning/2=debug; -1=nolog)
    myOpenLog(argv[1], atoi(argv[2]));

    // test mylog()
    myLog(MYLOG_ERROR, "questo è un msg di tipo %d (il livello impostato è %d)",
        MYLOG_ERROR, atoi(argv[2]));
    myLog(MYLOG_WARNING, "questo è un msg di tipo %d (il livello impostato è %d)",
        MYLOG_WARNING, atoi(argv[2]));
    myLog(MYLOG_DEBUG, "questo è un msg di tipo %d (il livello impostato è %d)",
       MYLOG_DEBUG, atoi(argv[2]));

    // close log
    myCloseLog();

    // exit
    return EXIT_SUCCESS;
}
Semplice, no? Eseguo un test argomenti con esempio di lancio (usage...), e poi apro, uso, chiudo, esco. Usando una linea di comando del tipo "mylog log.log 1" quale sarà il risultato? Verrà creato un file "log.log" che conterrà le seguenti due linee:
2014-01-18 18:39:33.890271 - questo è un msg di tipo 0 (il livello impostato è 1)
2014-01-18 18:39:33.890407 - questo è un msg di tipo 1 (il livello impostato è 1)
Aggiungiamo dettagli alla specifica: successivi lanci dell'applicazione devono appendere linee al logfile, perché una dote fondamentale di un log è mantenere informazioni dell'accaduto (e se ogni volta resettiamo il file le perdiamo). E se vogliamo ripartire da zero possiamo usare un altro filename oppure cancellare il vecchio logfile prima di eseguire. Un altro dettaglio che si nota è che ci servono, oltre alle nostre scritte di traccia, anche delle informazioni orarie, magari molto precise (nell'esempio ci sono i microsecondi: nella prossima puntata spiegherò il perché).

Per oggi abbiamo finito. I più volenterosi potranno, nell'attesa della seconda parte, scrivere una propria implementazione, e poi confrontarla con la mia, ma la mia sarà sicuramente meglio... (si allontana sghignazzando).

Ciao e al prossimo post!