Feb 22

log4j y liferay

Andamos desarrollando portlets para liferay y claro, queremos poner nuestro log de debug, de info y demás en algún sitio. Como también desarrollamos algunos jar que se usarán en más sitios aparte del portlet y queremos que esos jar también tengan log, usamos log4j y no el logger que se aconseja usar con liferay

// Para portlets liferay
Log log = LogFactoryUtil.getLog(UnaClase.class);

// Con log4j
Logger log = Logger.getLogger(UnaClase.class);

 

siendo Log y LogFactoryUtil clases específicas de los jar de liferay y por tanto útiles para un portlet, pero no para una librería. Pos ese motivo, queremos usar log4j. Dicho y hecho, a ello.

Pero aparece un problema, en el log del tomcat donde está instalador nuestro liferay, en vez de aparecer el log de estas librerías usadas por nuestro portlet, aparece el siguiente error

og4j:ERROR A "org.apache.log4j.ConsoleAppender" object is not assignable to a "org.apache.log4j.Appender" variable.
log4j:ERROR The class "org.apache.log4j.Appender" was loaded by
log4j:ERROR [WebappClassLoader
delegate: false
repositories:
/WEB-INF/classes/
———-> Parent Classloader:
org.apache.catalina.loader.StandardClassLoader@1ac1fe4
] whereas object of type
log4j:ERROR "org.apache.log4j.ConsoleAppender" was loaded by [WebappClassLoader
delegate: false
repositories:
/WEB-INF/classes/
———-> Parent Classloader:
org.apache.catalina.loader.StandardClassLoader@1ac1fe4
].
log4j:ERROR Could not instantiate appender named "CONSOLE".

Vamos, que hay algún tipo de problema al intentar usar log4j en nuestra librería. Buscando por internet, se llega a la conclusión de que es un bug de liferay y que de alguna forma se lía con los classloader y los log4j.jar que pueden existir en nuestra aplicación portlet y los que ya tiene liferay. Buscando soluciones, la gente propone muchas y a algunos les funcionan y a otros no, otras parecen complejas y lo cierto es que casi todas las que he probado no me funcionan.

Y digo casi todas porque hay una que sí me ha funcionado y es poner la variable de entorno -Dlog4j.ignoreTCL=true. Esto debe hacer que log4j ignore esos errores y siga funcionando. He añadido esta variable al fichero /liferay_home/tomcat_xxxx/bin/setenv.sh y todo va de perlas.

El siguiente problema son los niveles de DEBUG. Por defecto deben salir de INFO para arriba. A mi me interesan los de DEBUG de nuestras clases. Tampoco he visto forma adecuada de configurarlo, pero lo he conseguido de una manera que no me gusta mucho. En webapps/ROOT/WEB-INF/classes del tomcat de liferay hay un log4j.properties. Ahí podemos configurar el nivel que queremos para nuestros logs y otras cosas propias de log4j. Basta añadir una línea con

log4j.logger.com.chuidiang.libreria=DEBUG

donde log4j.logger es fijo y com.chuidiang.libreria es el paquete por el que empiezan las clases de mi librería.

Por fin, el log4j funcionando de perlas.

 

Jun 30

JUnit y Log4j

 

Cuando hacemos nuestras clases de test de JUnit, puede ser bastante normal testear clases que usan log4j. Si en nuestros test queemos que se muestre la traza de esas clases, debemos configurar log4j. Lo normal es hacerlo en el método setUp() del test (el que se ejecuta justo antes de cada uno de los test) de una forma similar a esta

public void setUp() {
   BasicConfigurator.configure();
}

Quizás, en vez de en el setUp() lo hagamos en el constructor de la clase de test.

Pero esto tiene una pequeña cosa que debemos saber. Cada vez que llamamos al BasicConfigurator.configure(), se añade un ConsoleAppender al log4j. Por ello, cada vez que se ejecuta setUp() (una vez por cada test), se añade un appender nuevo… y la traza empieza a salir repetida, ya que cada appender creado saca el mensaje por consola. Si lo hacemos en el constructor y tenemos varias clases de test, tenemos el mismo problema.

Para evitar esto, lo mas sencillo es llamar a BasicConfigurator.configure() en el método setUp() (y no en el constructor), pero poner tambien un método tearDown(), que JUnit llama automáticamente al final de cada test, y ahí borrar los appenders añadidos

public void tearDown() {
   LogManager.shutdown();
}

De esta forma, por cada test, primero se añadirá el appender y después se borrará. Cada test tendrá un único appender y la salida de log será única.

Mar 02

Incordiando al personal

 

Una de las cosillas que conviene hacer en un programa serio es usar un sistema de logging en condiciones en vez de usar un System.out o un System.err. Sin embargo, aunque se lo dices a la gente, es bastante habitual que muchos se "despisten" o pasen olímpicamente del tema, llenando el código de System.out.println("Voy por aquí") y System.out.println("Entro en el bucle"). Por supuesto, dicen que es temporal, sólo para probar, pero la realidad es que luego eso nunca se quita y queda por los siglos de los siglos.

Aprovechando que la clase System tiene un método setOut() que permite cambiar el System.out por otra cosa, si veo que proliferan los System.out.println(), lo que hago es cambiarlo en el main() de la aplicación (o en algún sitio menos evidente). Una "colleja" amistosa se puede dar con un código como este

package chuidiang.ejemplos;

import java.io.PrintStream;

public class CambiaSystemOut {

    public static void main(String[] args) {
        System.setOut(new PrintStream(System.out) {
            @Override
            public void print(String text) {
                super.print("No seas capullo y usa log4j");
            }

        });

        System.out.println("Hola mundo");
    }
}
 

con lo que cuando alguien pone su System.out.println("salgo del if") y hace la prueba, verá un "No seas capullo y usa el log4j".

Y si quiero dar la "colleja" de forma más vistosa (y tenemos tiempo para permitirnoslo), se puede poner el mismo mensaje pero usando un JOptionPane.showMessage(), que "canta" más. O ya para una "colleja" nada amistosa, un System.setOut(null), que hará que salte una NullPointerException cada vez que hagan un System.out.println().

El que no se divierte, es porque no quiere y yo, a veces, me lo paso como los indios.

Feb 25

Un par de cosillas

 

Un par de cosillas que aunque no son muy de programación, ahí van.

La primera es una pequeña anécdota que me ha pasado hoy. Pregunté a mi compañero cómo se hacía "no sé qué" con log4j, que estaba seguro de que se podía hacer, pero no recordaba cómo. El tampoco sabía, así que miró en google, encontró cómo y me mando un corredo diciendo "me engañas, sí que sabes cómo se hace" y un enlace … ¡ a mi propia página de log4j en la Chuwiki !. Creo que me estoy haciendo mayor y no sólo la mano izquierda no sabe lo que hace la derecha, sino que además no se acuerda.

La otra es sobre un compañero de trabajo, que ha hecho un sitio web sobre  venta de pastores alemanes y cahorros. Aunque es fan de las herramientas de Microsoft, casi le tengo convencido para que la haga con PHP y CSS. Por cierto, si alguien visita la página y se decide a comprar un perro, que le diga que va de mi parte, que me llevo comisión  ; – )

 

 

May 12

log4j 1.2.15

 

Normalmente en mis proyectos maven añado el log4j, siempre la versión 1.2.12. No por ningún motivo en particular. Símplemente era la versión disponible la primera vez que lo añadí a un proyecto maven y luego ha prevalecido el copy-paste de esa dependencia de un proyecto a otro.

El otro día cree un proyecto en casa y no tenía ningún sitio de donde copiar esta dependencia, así que me fuí a internet y busqué cómo ponerla. Puse la 1.2.15, que es la última que encontré disponible para maven.

Me pongo a compilar con maven, a generar el proyecto para eclipse y … ¡¡ Sorpresa !!. Da fallo. Busco el motivo y resulta que entre los jar que no puede bajarse, está el javamail, el activation.jar y algún otro más de los de SUN. Efectivamente, SUN no permite distribuir sus jar, por lo que oficialmente no se puede hacer. En los repositorios maven que hay por el mundo, no están estos jar de SUN, así que maven no se los puede bajar. Hay que bajárselos a mano y ponerlos en tu repositorio local de maven.

Y digo yo… ¿necesito javamail para log4j? Y aunque sea así, ¿voy a usarlo?. Pues más bien no. No tengo ningún interes en recibir por correo el log de mi aplicación y tampoco tengo ningún enemigo al que odie lo bastante como para mandárselo. Mejor dicho, sí lo tengo, pero lo que no tengo son ganas de quedarme de patitas en la calle.

Creo que esta vez se han pasado un poco. Entiendo que un momento dado, ante un error crítico, alguna aplicación crítica quiera enviar un correo a alguien. Pero, ¿es con un log.error(…) la mejor forma de hacerlo?. Desde luego puede ser cómoda en vez de usar javamail directamente, pero ¿tienen que cargar todas las aplicaciones que quieran un uso normal de log4j cargar con javamail?. A mi, desde luego, no me gusta.

Y aquí es donde llegamos a un punto donde siempre he tenido mis dudas. Por un lado la lógica y la elegancia me dicen que debería hacerse algo como log4j-core.jar con lo básico de log4j, es decir, sacar los log por pantalla o por fichero y poco más. Luego, deberían hacerse otros jar de amplicación, como log4j-mail.jar, log4j-bd.jar, etc, de forma que cada uno cargue sólo con lo que necesita. Sin embargo, la pereza me pediría que hubiera un único log4j-con-todo.jar y despreocuparme de andar buscando los que debo. Si me sobra el 90% del jar, da igual, no pasa nada.

Y esas dudas son las que siempre me corroen en el trabajo. Ante un proyecto gigante… ¿hacemos muchos jar pequeñitos por temas, de forma que en otros proyectos podamos llevarnos aquellos mini-jar que necesitamos? o por el contrario ¿hacemos un mega jar con todo y si un proyecto no necesita parte de él no pasa nada?

En la primera opción, al empezar un proyecto, debemos empezar a elegir jars que necesitamos y a coger también los jar de los que dependen esos jar. Afortunadamente maven nos ayuda en el proceso. Sin embargo, la segunda opción es menos elegante pero infinitamente más cómoda. Cuando empiezo el proyecto, me copio el mega-jar-que-lo-tiene-todo y a trabajar, sin más complicaciones.

¿Tú qué eliges?. ¿Elegancia o pereza?