En los títulos y los textos vais a encontrar unas cuantas citaciones cinematográficas (y si, soy un cinéfilo). Si no os interesan podéis fingir no verlas, ya que no son fundamentales para la comprensión de los post...

Este blog es la versión en Español de mi blog en Italiano L'arte della programmazione in C. Espero que mis traducciones sean comprensibles...

domingo, 23 de febrero de 2014

Quantum of Syslog
cómo escribir un Syslog en C - pt.2

Ok , a gran petición, publicamos una secuela de Casino Royale (¡oops! .. Syslog Royale ), donde el juego se hace muy difícil. Habíamos acabado con una pseudo-especificación de un sistema syslog-like, con, además, la anticipación de los datos que se quieren obtener. Ahora es el momento de la implementación real, y espero que alguien haya cogido mi invitación a escribir una versión suya esperando para este post (y  no podeis decir que no os he dejado el tiempo...).
Nuestra implementación es mejor que la tuya...
En el post anterior vimos el header mylog.h y el utilizador main.c, por tanto, con gran originalidad , el file de implementación lo llamaremos mylog.c. Vamos a verlo y analizarlo en secciones:
#include <time.h>
#include <sys/time.h>
#include <stdarg.h>
#include <stdio.h>
#include "mylog.h"

// prototipos locales
static char *getDateUsec(char *dest, size_t size);

// variables locales
static FILE *mylog_fp;
static int  mylog_level;
No voy a describir los include-files, que son , ni más ni menos, sólo lo que se necesitan. Parece obvio, pero es normal encontrar sources con muchos más include de lo necesario: no hacer daño, pero complican la interpretación visual del código. Acordaros de poner sólo lo que se necesitan.
La segunda sección que encontramos es la de los "prototipos locales", que describe las funciones estáticas de este archivo, las que no deben ser exportadas a los utilizadores de mylog. En este caso, hay getDateUsec(), que describiremos mas adelante.

Luego tenemos la sección "variables locales", que son, globales al file. Sé que las globales son una maldición (como se describe aquí ), pero en algunos casos (como éste ) es difícil prescindir. Y, además, son estáticas al file, entonces nos vamos a sentir menos culpables. Las variables locales son dos: uno es un FILE* que describe el file de log (mylog_fp) , mientras que la otra (mylog_level) es un int que se utiliza para gestionar el nivel de log (que, como se ha visto en el post introductorio se establece en cuatro valores ).

Después nos trasladamos a la implementación real, y nos encontramos con las dos primeras de las tres funciones de nuestra librería, myOpenLog() y myCloseLog():
/* myOpenLog()
 * Abre una conexión al logger myLog para un programa.
 */
void myOpenLog(const char *fname, int level)
{
    // abre el logfile y set level
    mylog_fp = fopen(fname, "a");
    mylog_level = level;
}

/* myCloseLog()
 * Cierra el descriptor usado para escribir al logger myLog.
 */
void myCloseLog()
{
    // cierra el logfile
    fclose(mylog_fp);
}
Como se puede ver son muy simples: myOpenLog() abre con fopen() el file que se pasa en el argumento (la cadena  fname), y establece el nivel de log con el valor que se pasa con el segundo argumento (int level). El file lo abriremos en modo append, ya que queremos un archivo de registro incremental, reservándonos de resetearlo cuando nos necesite.

La otra función, myCloseLog() , se limita simplemente a liberar con fclose() el descriptor obtenido myOpenLog().

Y ahora llegamos al corazón del sistema di log: la función mylog():
/* myLog()
 * Genera un mensaje de log para el logger myLog.
 */
void myLog(int level, const char *format, ...)
{
    // test nivel de log
    if (level <= mylog_level) {
        // compone logstr con argumentos múltiples
        va_list arglist;
        va_start(arglist, format);
        char logstr[128];
        vsnprintf(logstr, sizeof(logstr), format, arglist);
        va_end(arglist);

        // escribe logstr con fecha+hora y flush datos
        char date[128];
        fprintf(mylog_fp, "%s - %s\n", getDateUsec(date, sizeof(date)), logstr);
        fflush(mylog_fp);
    }
}
La primera operación que realiza mylog() es testear el  nivel de log: esto es crucial, ya que nos permite generar sólo los mensajes de nivel inferior o igual al  fijado por myOpenLog(), lo cual también incluye la no generación de mensajes si mylog_level es MYLOG_NOLOG. Y en este último caso, la única operación que se realiza es el test en el if, por lo que, si decidimos no utilizar el sistema de log, la carga de CPU y  sistema de I/O es prácticamente cero (¡estaba en las especificaciones del proyecto!) .

Luego, utilizando la función de la familia stdarg, podemos manejar el mensaje que se mostrará (ver después de el comentario "compone logstr con argumentos múltiples") que utiliza un formato printf () (tal como se utiliza en main.c) . No voy a explicar cómo funcionan printf() y stdarg() (no es el tema de este post), sin embargo, se podría volver a ellas en el futuro ... por el momento, os remito a la larguísima documentación encontrable con San Google.

En este punto, la función termina escribiendo en el file de log (véase después del comentario “escribe logstr con la fecha+hora y  flush datos") utilizando fprintf().

La última declaración es fflush(): ¿por qué? Ok , añade un overhead de I/O, pero un buen sistema de log debe garantizar la escritura inmediata del mensaje que acaba de componer, si no, en caso de crash del programa, o, más simplemente, en aplicaciones multi-proceso, es posible que, por extraño que parezca, lo mensajes tengan una orden temporal equivocada, que en fase de debug, pruebas o análisis de los resultados podría confundirnos.

¿Qué pensáis de la implementación? No está mal, ¿verdad? Bueno, obviamente, he hecho algunas simplificaciones, porque la idea básica es la de proponer ejemplos que tengan estilo y funcionalidad, y que, a continuación, se puedan mejorar añadiendo los controles que faltan (tipo verificar si el descriptor FILE* es válido antes de usarlo, etc.) . Lo importante es que la idea básica sea correcta y se puede utilizar como pista para productos reales. Probar en copiar y compilar todo, os aseguro que funciona (¡al igual que todos los ejemplos que propongo en el blog, eh!) .

En un futuro episodio hablaremos de la función local getDateUsec(), para terminar con broche de oro el tema. Como siempre, les invito a no contener la respiración esperando...

¡Hasta el próximo post!