Trazas bien hechas

Una parte muy importante de cualquier aplicación es la de realizar trazas o logs. ¿Dónde estaríamos sin estas?
Para MonoCaffe he desarrollado hoy esta solución que viene en gran medida a cierta experiencia en este tema.
En primer lugar, hay que seleccionar una tecnologia para esto, y yo escogí Log4j de la Fundacion Apache por su facilidad y por los buenos resultados que me ha dado.
Muchos tutoriales y artículos en Internet se enfrascan en explicar la configuración de log4j que es un tema tratado hasta la saciedad y bastante sencillo en cuanto se entiende cada una de las partes.
Es en su uso donde dejan mucho que desear, y a lo que viene ésta entrada, mostrar cómo hacer bien un sistema de trazas el cual se puede utilizar con cualquier librería que utilicemos.
La idea es sencilla, un "singleton" será quien se encargue de realizar las trazas.
En muchos tutoriales os encontrareis con esto:

static Logger logger = Logger.getLogger(MyClass.class);
...
logger.debug("Mensaje de debug");

Lo cual está bien y funciona, pero lo hace difícil de mantener y propenso a errores, por ejemplo, olvidarnos de ponerlo. Otro ejemplo es si cambiamos la librería, en lugar de utilizar, tendríamos que cambiar los imports de TODAS las clases de la aplicación!

La solución a esto sería definir una clase especifica para esto que llamaremos MonoLogger.

package es.monocaffe.logging;
import org.apache.log4j;
public class MonoLogger(){
private final static Logger logger = Logger.getLogger("MonoCaffe");
}

Lo siguiente será añadir los métodos que escriban al Logger:

public static void writeError(Object message){
logger.error(message);
}

Lo mismo para "WARN", "DEBUG" e "INFO". Si paramos aquí, ya tendremos a MonoLogger listo para recibir nuestras trazas:

MonoLogger.writeError("Esto es un error");


Pero vayamos más allá y vamos a añadir un fichero de propiedades a todo esto, donde almacenaremos los mensajes que deseemos enviar al usuario o a otros programadores y una sorpresa especial. Gracias a java.util.PropertyResourceBundle podemos crear un fichero monologs.properties y definir una serie de mensajes utilizando un formato de clave=valor, como si de un Hash se tratase (al fin y al cabo, los objetos Properties, son hijos de de Hashmap).
Creamos el fichero dentro del mismo paquete o directorio que MonoLogger y añadimos lo siguiente:

#Esto es un comentario

client_not_found=No se ha encontrado el cliente que busca.
format_error=Error en el formato de entrada
null_assert=¿Pero qué haces?¿null?
this_is_an_error=Esto es un error
debug=true

Lo siguiente que necesitamos es "cargar" la información del fichero properties en la aplicación:

private static final PropertyResourceBundle monomessages = PropertyResourceBundle.getBundle("monologs.properties");

Y añadir un par de métodos que servirán para obtener los mensajes que están en las propiedades.
¿Qué error estamos cometiendo aquí? Más abajo la solución.

public static String getMessage(String key){
return monomessages.getString(key);
}

De esta manera, si queremos escribir un error como el anterior:

MonoLogger.writeError(MonoLogger.getMessage("this_is_an_error"));

Con esta práctica podremos cambiar todo el lenguaje de la aplicación, cambiando ficheros properties y sin la necesidad de compilar el código.
Finalmente, y lo que más me gusta de todo esto, es la posibilidad de activar/desactivar las trazas de debugging sin compilar y en un solo sitio con la propiedad debug que hemos metido en el fichero de propiedades:

private static boolean debug = doDebugging();
...
public static boolean doDebugging(){
String ret = getMessage("debug");
return Boolean.valueOf(ret);
}

De esta manera, podremos añadir libremente en nuestro código lineas como:

MonoLogger.writeDebug("Esto es un mensaje de Dios");

Y solo aparecerán cuando la propiedad debug sea true.
El error anterior está en que, si el fichero no existe, nos lanzará una excepción como una casa, por lo que es mejor meter esa lógica dentro de un método estático, capturar las excepción y devolver un PropertyResourceBundle.

2 comentarios:

  1. Una forma elegante de hacer las cosas.

    ¿Podrías poner un ejemplo del código de lo que dices en el último párrafo en cuanto a devolver un PropertyResourceBundle?

    Muchas Gracias!

    ResponderEliminar
  2. Si hombre, sería algo como esto:
    private static PropertyResourceBundle getBaseBundle() {
    try {
    return new PropertyResourceBundle(MonoLooger.class.getResourceAsStream("messages.properties"));
    } catch(IOException io) {
    sendError("Something wrong happened when retrieving \"messages.properties\" file.");
    sendError("Make sure the file is correctly located under the same directory as this class file and has the correct");
    sendError("permissions (the user and group should be the same as the one running the jboss process)");
    return null;
    }
    }

    ResponderEliminar