La librería Log4j para emitir trazas en aplicaciones Java

Escrito por el .
java planeta-codigo programacion
Enlace permanente Comentarios

Java

La librería Log4j es una librería para emitir las trazas de depuración e información indispensables cuando son requeridas en una aplicación Java. Es la librería sucesora de logback y aunque no es imprescindible suele usarse en combinación con slf4j ya que esta permite cambiar de librería de logging subyacente sin hacer ningún cambio en la aplicación.

Algunas de las nuevas características de log4j son:

  • Rendimiento mejorado usando funcionalidades asíncronas en los loggers.
  • Soporte para múltiples APIs como SL4J, Commons Logging y java.util.logging (JUL).
  • Sin encadenamientos a la librería al poder en cualquier momento usar cualquier librería compatible con SLF4J.
  • Recarga automática de la configuración sin perder ninguna traza.
  • Filtrado avanzado basado en datos de contexto, marcadores, expresiones regulares y otros componentes.
  • Arquitectura basada en plugins.
  • Soporte de propiedades definidas en archivos de configuración, propiedades del sistema, variables de entorno, el mapa ThreadContext y datos del evento.
  • Soporte de lambdas de Java 8. Las expresiones lambdas no se evalúan si no está activado el nivel de log consiguiendo el mismo efecto que con una sentencia if pero en menos código.
  • Niveles de log personalizados fácilmente definibles sin necesidad de realizar subclases.
  • Recolección de basura reducida lo que evita presión al recolector de basura y mejora el rendimiento de las aplicaciones.

Por defecto, la configuración de Log4j se describe en un archivo xml aunque también soporta definirlo en un formato menos verboso como yaml. La siguiente aplicación de Spring Boot al iniciarse emite en la consola varios mensajes usando log4j2.

Usando la clase LogManager se obtiene una referencia a la clase Logger con la que se emiten las trazas y que posee diferentes métodos para cada nivel de traza.

Una vez se ha iniciado la aplicación Spring Boot invoca el método run y se emiten las trazas propias de la aplicación después de las que también Spring Boot y otras librerías emiten., en este caso usando texto en forma de arte ascii.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package io.github.picodotdev.plugintapestry;

import io.github.picodotdev.plugintapestry.spring.AppConfiguration;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;

@SpringBootApplication
public class Main extends SpringBootServletInitializer implements CommandLineRunner {

	private static final Logger logger = LogManager.getLogger(Main.class);

	@Override
	public void run(String... args) {
		StringBuilder banner = new StringBuilder();
		banner.append("   ___  __          ____   ______                  __          \n");
		banner.append("  / _ \\/ /_ _____ _/  _/__/_  __/__ ____  ___ ___ / /_______ __\n");
		banner.append(" / ___/ / // / _ `// // _ \\/ / / _ `/ _ \\/ -_|_-</ __/ __/ // /\n");
		banner.append("/_/  /_/\\_,_/\\_, /___/_//_/_/  \\_,_/ .__/\\__/___/\\__/_/  \\_, / \n");
		banner.append("            /___/                 /_/                   /___/");
		logger.info("\n" + banner.toString());
		logger.info("Application running");
 	}
	
 	@Override
 	protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
 	 	return application.sources(AppConfiguration.class);
 	}

	public static void main(String[] args) throws Exception {
		SpringApplication application = new SpringApplication(Main.class);
		application.setApplicationContextClass(AnnotationConfigWebApplicationContext.class);
		SpringApplication.run(Main.class, args);
	}
}
Main.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
2018-07-15 01:25:14,645 INFO  pringframework.jmx.export.annotation.AnnotationMBeanExporter Registering beans for JMX exposure on startup
2018-07-15 01:25:14,691 INFO                    org.apache.coyote.http11.Http11NioProtocol Starting ProtocolHandler ["https-jsse-nio-8443"]
2018-07-15 01:25:15,052 INFO                    org.apache.tomcat.util.net.NioSelectorPool Using a shared selector for servlet write/read
2018-07-15 01:25:15,065 INFO                    org.apache.coyote.http11.Http11NioProtocol Starting ProtocolHandler ["http-nio-8080"]
2018-07-15 01:25:15,073 INFO  org.springframework.boot.web.embedded.tomcat.TomcatWebServer Tomcat started on port(s): 8443 (https) 8080 (http) with context path ''
2018-07-15 01:25:15,079 INFO                      io.github.picodotdev.plugintapestry.Main Started Main in 10.576 seconds (JVM running for 11.962)
2018-07-15 01:25:15,082 INFO                      io.github.picodotdev.plugintapestry.Main
   ___  __          ____   ______                  __
  / _ \/ /_ _____ _/  _/__/_  __/__ ____  ___ ___ / /_______ __
 / ___/ / // / _ `// // _ \/ / / _ `/ _ \/ -_|_-</ __/ __/ // /
/_/  /_/\_,_/\_, /___/_//_/_/  \_,_/ .__/\__/___/\__/_/  \_, /
            /___/                 /_/                   /___/
2018-07-15 01:25:15,082 INFO                      io.github.picodotdev.plugintapestry.Main Application running
System.out

En el archivo de construcción de la aplicación usando Gradle hay que incluir las dependencias de las librerías.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
configuration:
  status: warn

  appenders:
    console:
      name: STDOUT
      patternLayout:
        Pattern: "%d{DEFAULT} %-5level %60.60logger %msg%n"

  loggers:
    root:
      level: info
      appenderRef:
        ref: STDOUT
log4j2.yaml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
...

def versions = [
    ...
    'spring':              '5.0.7.RELEASE',
    'spring_boot':         '2.0.3.RELEASE',
    ...
]

...

dependencies {
    ...

    // Spring Boot
    compile("org.springframework.boot:spring-boot-starter:$versions.spring_boot") { exclude(group: 'org.springframework.boot', module: 'spring-boot-starter-logging') }
    compile("org.springframework.boot:spring-boot-starter-web:$versions.spring_boot") { exclude(group: 'org.springframework.boot', module: 'spring-boot-starter-logging') }
    compile("org.springframework.boot:spring-boot-starter-actuator:$versions.spring_boot") { exclude(group: 'org.springframework.boot', module: 'spring-boot-starter-logging') }
    compile("org.springframework.boot:spring-boot-starter-log4j2:$versions.spring_boot")
    compile("org.springframework.boot:spring-boot-autoconfigure:$versions.spring_boot")
    
    ...
    
    runtime "com.fasterxml.jackson.core:jackson-databind:2.9.6"
    runtime "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.9.6"
}

...
build.gradle

Las trazas son muy importantes por la valiosa información que proporcionan de lo que está sucediendo en una aplicación a los que recurriendo es posible obtener la valiosa información que permite saber que ha sucedido en una determinada acción o que permite descubrir mucho más rápidamente la causa de un error.

En otro artículo ya he comentado como usar marcadores con sl4j y logback para relacionar trazas que son emitidas en diferentes clases o módulos de la aplicación, también se puede hacer con Log4j. En siguientes artículos comentaré como transformar un objeto a un mensaje sin necesidad de convertir ese objeto a un String en cada traza de la aplicación donde se use su información y otra forma de relacionar trazas muy útil en aplicaciones web asignando un identificador única en cada petición y emitiéndolo en todas sus trazas, esto permite saber todo lo que ha ocurrido en una petición entre las muchas que se ejecutan concurrentemente por todos los usuarios de la aplicación.

Si las trazas no son suficientes para descubrir la causa de un error otra forma de conseguirla es depurar la aplicación con un IDE, ver paso a paso su ejecución y con la posibilidad de inspeccionar los valores de las variables. Una vez descubierta la causa del error de cualquiera de las maneras se pueden hacer los cambios necesarios en el programa para corregirlos.

Terminal

El código fuente completo del ejemplo puedes descargarlo del repositorio de ejemplos de Blog Bitix alojado en GitHub y probarlo en tu equipo ejecutando siguiente comando:
./gradlew run


Comparte el artículo: