Una de las partes más cruciales del proceso de desarrollo de software es disponer de un registro adecuado. Con un montón de diferentes marcos de registro Java disponibles, es importante elegir uno que sea fácil de usar. Al mismo tiempo, el marco que elija debe tener un alto rendimiento, características extensibles y permitir la personalización. Log4j2 es una biblioteca de registro Java gratuita que cumple todos los requisitos.

La integración de Log4j2 con cualquier aplicación desbloquea opciones como el filtrado avanzado, la compatibilidad con Java 8 lambda, la búsqueda de propiedades y los niveles de registro personalizados. Echemos un vistazo a cómo puede añadir Log4j2 a sus proyectos y qué características pueden ayudarle a mantenerse en la cima.

¿Qué es Log4j2?

A developer writing code on his laptop

El registro es el método de capturar información útil, conocida como logs, que puede ser referenciada y analizada posteriormente. Puede utilizar los registros para depurar rápidamente el código de la aplicación. Los registros de la aplicación ayudan a comprender el flujo del código y a abordar los problemas y errores de producción.

Aparte de los casos de uso de diagnóstico, los registros también se utilizan con fines de auditoría; por ejemplo, para rastrear si un mensaje de notificación se ha enviado correctamente al usuario.

Log4j2 es una de las bibliotecas de registro más populares de Java. Es sucesora de la muy influyente biblioteca Log4j. Desarrollada por la Fundación del Software Apache y parte de los Servicios de Registro Apache, Log4j2 es un Software Libre y de Código Abierto (FOSS) distribuido bajo la Licencia Apache, versión 2.0.

Log4j2 está construido sobre la sólida base del Log4j original. El uso de un Logger tiene ventajas sobre las simples sentencias de impresión de System.out.println(). Esto incluye tener control sobre qué mensajes mostrar mientras se evitan otros mensajes de registro. Tener registros adecuados es crucial en un entorno de producción donde los depuradores no están disponibles.

¿Cómo añadir Log4j2 a su proyecto?

Hay varias formas de añadir Log4j2 a su proyecto Java. Es aconsejable estar en Java 8 o superior para poder utilizar todas las características de Log4j2.

Vamos a discutir los distintos métodos por los que puede añadir Log4j2 en función de los requisitos que pueda tener.

Añadir Log4j2 a proyectos que utilicen Apache Maven

Si su proyecto utiliza Apache Maven como sistema de compilación, es necesario añadir las dependencias de Log4j2 al archivo pom.xml.

<dependencias&gt
  <dependencia&gt
    <groupId>org.apache.logging.log4j</groupId&gt
    <artifactId>log4j-api</artifactId&gt
    <version>2.20.0</version&gt
  </dependencia&gt
  <dependencia&gt
    <groupId>org.apache.logging.log4j</groupId&gt
    <artifactId>log4j-core</artifactId&gt
    <version>2.20.0</version&gt
  </dependencia&gt
</dependencias&gt

Para facilitar el mantenimiento de la misma versión en diferentes artefactos, Log4j2 dispone de un archivo pom.xml de lista de materiales (BOM). Si lo añade en su gestión de dependencias, no tendrá que añadir individualmente las versiones.

<!-- Añada el BOM al dependencyManagement -->

<gestión de dependencias&gt
  <dependencias&gt
    <dependencia&gt
      <groupId>org.apache.logging.log4j</groupId&gt
      <artifactId>log4j-bom</artifactId&gt
      <version>2.20.0</version&gt
      <ámbito>import</ámbito&gt
      <tipo>pom</tipo&gt
    </dependencia&gt
  </dependencias&gt
</dependencyManagement&gt

<!-- Una vez añadida la lista de materiales, las versiones no son necesarias -->

<dependencias&gt
  <dependencia&gt
    <groupId>org.apache.logging.log4j</groupId&gt
    <artifactId>log4j-api</artifactId&gt
  </dependencia&gt
  <dependencia&gt
    <groupId>org.apache.logging.log4j</groupId&gt
    <artifactId>log4j-core</artifactId&gt
  </dependencia&gt
</dependencias&gt

Añadir Log4j2 a proyectos que utilicen Apache Gradle

En caso de que utilice Apache Gradle como herramienta de compilación, puede añadir las dependencias de Log4j2 a su archivo build.gradle.

dependencias {
  implementación 'org.apache.logging.log4j:log4j-api:2.20.0'
  implementación 'org.apache.logging.log4j:log4j-core:2.20.0'
}

Si está en Gradle versión 5.0 o superior, tiene la opción de importar la lista de materiales (BOM) de Log4j2 Maven para mantener versiones de dependencias consistentes. Esto se puede lograr añadiendo lo siguiente a su archivo build.gradle.

dependencias {
  plataforma de implementación('org.apache.logging.log4j:log4j-bom:2.20.0')

  implementación 'org.apache.logging.log4j:log4j-api'
  runtimeOnly 'org.apache.logging.log4j:log4j-core'
}

Para las versiones de Gradle 2.8-4.10, no hay opción para importar directamente la lista de materiales de Maven. Necesita añadir un plugin adicional para la funcionalidad de gestión de dependencias.

plugins {
  id 'io.spring.dependency-management' version '1.0.15.RELEASE'
}

dependencyManagement {
  importaciones {
    mavenBom 'org.apache.logging.log4j:log4j-bom:2.20.0'
  }
}

dependencias {
  implementación 'org.apache.logging.log4j:log4j-api'
  runtimeOnly 'org.apache.logging.log4j:log4j-core'
}

Adición de Log4j2 a aplicaciones independientes sin una herramienta de compilación

Si su proyecto no dispone de una herramienta de compilación, puede descargar la versión artefacto necesaria de Log4j2 desde la página oficial de descargas de Log4j2.

Una vez descargados, deberá asegurarse de que el classpath de su aplicación incluye los siguientes jars.

  • log4j-api-2.20.0.jar
  • log4j-core-2.20.0.jar

¿Cuáles son los componentes de Log4j2?

Para comprender las características de Log4j2 y aprovechar al máximo sus capacidades, es importante entender cómo funciona. Bajo la superficie, varios bloques de construcción componen Log4j2. Hablemos de ellos uno a uno.

Block diagram showing the different components that build up Log4j2

#1. LoggerContext

El LoggerContext es la unidad central del sistema de registro. Contiene todos los Loggers solicitados en la aplicación. También contiene una referencia a la Configuración.

#2. Configuración

La Configuración contiene toda la información requerida por el sistema de registro. Esto incluye los Loggers, Appenders, Filtros y más. En Log4j2, puede definir la Configuración utilizando varios formatos de archivo como XML, JSON y YAML, y también mediante programación a través de la API de Log4j2.

Se produce una recarga automática cada vez que cambia alguna propiedad en la Configuración. Por lo tanto, no es necesario reiniciar la aplicación.

#3. Registrador

El componente principal del sistema Log4j2 es el Logger. Los loggers se obtienen dentro del código de la aplicación mediante la sentencia LogManager.getLogger() y se utilizan para generar logs. Los mensajes de registro pueden generarse en varios niveles de gravedad, como debug, info, warn, error y fatal.

#4. LoggerConfig

El LoggerConfig es responsable del comportamiento de un Logger específico. Define el comportamiento y la configuración de los eventos de registro generados por ese registrador en particular. Permite la configuración de diferentes niveles de registro, el establecimiento de anexos y la aplicación de filtros.

#5. Filtro

Puede procesar selectivamente los eventos de registro en Log4j2 utilizando Filtros. Los filtros se aplican basándose en criterios específicos. Puede aplicar estos filtros a los registradores o a los appenders. Los filtros controlan qué eventos de registro se les permite pasar a través de la tubería de registro para su posterior procesamiento. Con la ayuda de filtros, el comportamiento de registro puede ser afinado, asegurando que sólo los registros pertinentes son procesados.

#6. Destinatario

El destino de cualquier mensaje de registro viene determinado por el Appender. Un único Logger puede tener múltiples Appenders. Un evento de registro será enviado a todos los Appenders para el Logger dado. Log4j2 tiene muchos Appenders preconfigurados. Por ejemplo, ConsoleAppender se utiliza para registrar mensajes en la consola, y FileAppender se utiliza para enviar mensajes a un archivo. Cada Appender necesita su propio Layout que determina el aspecto que tendrá el mensaje de registro final.

#7. Layout

En Log4j2, el Layout se utiliza para definir el aspecto que tendrá el mensaje de registro final. Un Layout está asociado a un Appender. Mientras que un Appender determina el destino de salida, el Layout describe cómo se dará salida al mensaje.

Las 5 características principales de Log4j2

A person sitting and learning different techniques in Java

Log4j2 es rico en características, y eso es lo que lo diferencia de otros marcos de registro Java disponibles. Desde tener registradores asíncronos hasta soportar Java 8 lambdas, Log4j2 tiene una ventaja sobre los demás. Vamos a discutir algunas de las características notables de este marco.

#1. Ampliación de las funcionalidades mediante plugins

En Log4j 1.x, para crear extensiones era necesario modificar mucho código. Log4j2 resuelve el problema de la extensibilidad introduciendo el sistema de Plugins.

Puede declarar un nuevo Plugin utilizando la anotación @Plugin en su clase. Utilizando la potencia de los Plugins, puede crear sus propios componentes como Filtros y Appenders. También se pueden añadir fácilmente componentes de terceros a la biblioteca.

#2. Compatibilidad con Java 8 Lambda

Con el lanzamiento de la versión 2.4 de Log4j2, se introdujo la compatibilidad con las expresiones lambda de Java 8. Con las expresiones lambda, puede definir su lógica de registro en línea. Esto reduce la necesidad de controles multilínea o clases internas anónimas. Esto también garantiza que los métodos costosos no se ejecuten innecesariamente. Así, no sólo el código es más limpio y fácil de leer, sino que también se reduce la sobrecarga del sistema.

Consideremos un ejemplo en el que se registra el resultado de una operación costosa, pero sólo si el nivel de depuración está activado. Antes de la compatibilidad con las lambdas, esto se realizaría utilizando el código que se indica a continuación:

if (logger.isDebugEnabled()) {
    logger.debug("La salida de la operación dada es: {}", expensiveOperation());
}

Tener varios casos de uso de este tipo introduciría innecesariamente comprobaciones condicionales. Sin embargo, con Log42, la misma acción puede realizarse de la siguiente manera

logger.debug("La salida de la operación dada es: {}", () -> expensiveOperation()

El método exprensiveOperation( ) sólo se evalúa si el nivel de depuración está activado. No es necesaria ninguna comprobación explícita.

#3. Registradores asíncronos

Cada evento de registro es una operación de E/S, lo que aumenta la sobrecarga del sistema. Para mitigar esto, Log4j2 introduce Loggers Asíncronos que se ejecutan en un hilo separado del hilo de la aplicación. Cuando se utilizan registradores asíncronos, el subproceso que realiza la llamada recupera inmediatamente el control tras invocar el método logger.log().

Esto le permite continuar con la lógica de la aplicación en lugar de esperar a que se complete el evento de registro. Aprovechando este comportamiento asíncrono se consigue un mayor rendimiento del registro. Puede elegir hacer que todos los registradores sean asíncronos por defecto o tener una mezcla de ambos comportamientos, síncrono y asíncrono.

#4. Registro sin recolección de basura

En Java, la recolección de basura es el proceso por el cual los objetos no utilizados en la aplicación se eliminan automáticamente. Aunque no tiene que ocuparse manualmente de esta operación, la recogida de basura tiene su propia sobrecarga.

Si su aplicación crea demasiados objetos en un corto periodo de tiempo, el proceso de recogida de basura podría ocupar más recursos del sistema de los necesarios. Varias bibliotecas de registro, incluidas las versiones anteriores de Log4j, crean muchos objetos temporales durante el proceso de registro. En consecuencia, la mayor presión sobre el recolector de basura repercute en el rendimiento del sistema.

Desde la versión 2.6, Log4j2 se ejecuta en modo «libre de basura». Este es el comportamiento por defecto. Por tanto, los objetos se reutilizan y se reduce en gran medida la creación de otros temporales.

Las siguientes imágenes muestran cómo la versión 2.6 de Log4j2 mitiga el problema de los objetos innecesarios, en comparación con la versión 2.5 de Log4j2.

Java Flight Recording statistics showing objects created by log4j2-2.5
En la versión 2.5 de Log4j2, se crean muchos objetos temporales durante el proceso de registro; Fuente: apache.org
Java Flight Recording statistics showing almost no objects created by Log4j2 version 2.6
En Log4j2.6, no se crean objetos temporales durante el proceso de registro; Fuente: apache.org

#5. Búsquedas

En log4j2, puede añadir información contextual a sus registros utilizando Lookups. Utilizándolos, puede añadir datos de diversas fuentes, como propiedades del sistema, variables de entorno o valores definidos por el usuario. De este modo, puede incluir información relevante que se obtiene dinámicamente, haciendo que los registros sean más útiles.

Consideremos un ejemplo en el que desea registrar el id de sesión del usuario con todas las líneas de registro. Esto le permitiría buscar todos los registros correspondientes a un identificador de sesión.

La forma bruta de hacerlo sería añadir explícitamente el id de sesión de forma individual, lo que resulta difícil de mantener. Pronto podría olvidarse de añadirlo, perdiendo así información valiosa.

logger.info("Se han recuperado los datos de usuario correspondientes al id de sesión {}", sessionId);
...
logger.info("Se ha procesado la transacción para el id de sesión {}", sessionId);
...
logger.info("La solicitud se ha procesado correctamente para el id de sesión {}", sessionId);

Una forma mejor de hacerlo sería utilizar el mapa de contexto Lookup. El id de sesión puede añadirse al Thread Context en el código de la aplicación. El valor puede utilizarse entonces dentro de la configuración de Log4j2. Así se elimina la necesidad de mencionarlo explícitamente en los mensajes de registro.

ThreadContext.put("sessionId", sessionId);

Una vez añadido el valor, puede utilizarse el mismo en Lookup utilizando la palabra clave ctx.

<Archivo name="Aplicación" fileName="aplicación.log"&gt
  <PatternLayout&gt
    <patrón>%d %p %c{1.} [%t] $${ctx:sessionId} %m%n</pattern&gt
  </patternLayout&gt
</Archivo&gt

¿Cómo hacer niveles de registro personalizados en Log4j2?

Los niveles de registro en Log4j2 se utilizan para categorizar los eventos de registro en función de su gravedad o importancia. Puede controlar el nivel de registro cuando esté registrando un mensaje en el código de la aplicación.

Por ejemplo, logger .debug() añade el nivel DEBUG. Correspondientemente, logger. error () añade el nivel ERROR. Esto determina qué mensajes aparecen finalmente en la salida. Puede configurar el nivel de registro en el archivo de configuración.

A continuación se mencionan los niveles de registro preconfigurados en Log4j2 y sus valores correspondientes.

OFF0
FATAL100
ERROR200
WARN300
INFO400
DEBUG500
TRACE600
TODOSVALOR MÁX

Si el nivel de registro se fija en un nivel determinado, se emiten todas las líneas de registro para ese valor correspondiente y las que estén por encima (con un valor menor). Las demás se ignoran.

Por ejemplo, si establece el nivel de registro en WARN, entonces se mostrarán los mensajes WARN, ERROR y FATAL. Cualquier línea de registro con un nivel diferente será ignorada. Esto resulta especialmente útil cuando ejecuta el mismo código en distintos entornos.

Es posible que desee establecer el nivel de registro en INFO o DEBUG cuando ejecute el código en su entorno de desarrollo. Esto le permitirá ver más registros y le ayudará en el proceso de desarrollo. Sin embargo, cuando se ejecute en un entorno de producción, querrá establecerlo en ERROR. Así, podrá centrarse en encontrar el problema en caso de que se produzca alguna anomalía y no tendrá que revisar líneas de registro innecesarias.

Puede ocurrir que desee añadir su propio nivel de registro personalizado además de los preconfigurados. Log4j2 le permite hacerlo fácilmente. Veamos cómo puede añadir sus propios Niveles de Registro y utilizarlos en su aplicación.

#1. Añadir un nivel de registro personalizado mediante el archivo de configuración

Puede añadir niveles de registro personalizados declarándolos en el fichero de configuración.

En el ejemplo siguiente, se ha definido un nivel de registro personalizado llamado NOTICE con un valor de 450. Esto lo sitúa entre INFO (con un valor de 400) y DEBUG (con un valor de 500). Esto significa que si el nivel se establece en NOTICE, se registrarán los mensajes INFO, pero se omitirán los mensajes DEBUG.

<?xml version="1.0" encoding="UTF-8"?&gt
<Configuración&gt
  <Niveles personalizados&gt
    <Nivel personalizado name="AVISO" intLevel="450" /&gt
  </CustomLevels&gt
 
  <Applicadores&gt
    <Archivo name="MiArchivo" fileName="logs/app.log"&gt
      <patrónPatternLayout pattern="%d %-7level %logger{36} - %msg%n"/&gt
    </Archivo&gt
  </Appenders&gt
  <Loggers&gt
    <Root level="trace"&gt
      <AppenderRef ref="MiArchivo" level="AVISO" /&gt
    </Root&gt
  </Loggers&gt
</Configuración&gt

#2. Añadir un nivel de registro personalizado en el código

Además de declararlos en el archivo de configuración, puede definir sus propios niveles de registro personalizados en su código.

final Level VERBOSE = Level.forName("VERBOSE", 550);

Esto creará un nuevo nivel de registro llamado VERBOSE. Este nivel de registro se situará entre DEBUG (con un valor de 500), y TRACE (con un valor de 600). Si el registrador se establece en el nivel VERBOSE, se registrarán todos los mensajes de registro de VERBOSE y superiores, incluido DEBUG. Sin embargo, se omitirán los mensajes TRACE.

#3. Uso del nivel de registro personalizado en el código

Los niveles de registro personalizados deben ser declarados antes de ser utilizados. Puede declararlos en el archivo de configuración o en su código. Una vez declarados, puede utilizarlos libremente.

Este ejemplo de código muestra cómo puede declarar un nivel personalizado llamado NOTICE, y luego utilizarlo.

final Nivel NOTICE = Level.forName("NOTICE", 550);

final Logger logger = LogManager.getLogger();
logger.log(NOTICE, "un mensaje de nivel notice");

Aunque esto generará el mensaje requerido con el nivel recién creado, puede resultar engorroso pasar siempre el nivel explícitamente. Afortunadamente, puede generar código fuente de forma que obtenga métodos de ayuda para registrar sus niveles personalizados. Utilizando el mismo, podrá utilizar su propio método de logger .notice() de forma similar a como utilizaría logger.debug() o logger.error().

Log4j2 viene con una utilidad que le ayuda a crear sus propios loggers extendidos. El siguiente comando crea un archivo Java llamado CustomLogger.java. Este archivo contiene los métodos de registro existentes, junto con los nuevos métodos generados para el nivel NOTICE.

java -cp log4j-core-2.20.0.jar org.apache.logging.log4j.core.tools.ExtendedLoggerGenerator \
        com.example.CustomLogger NOTICE=450 > com/example/CustomLogger.java

Una vez generado el archivo, puede utilizar la clase en su código para crear nuevos loggers. Estos registradores contendrán métodos adicionales para su nivel de registro personalizado. De este modo, podrá ampliar la funcionalidad de sus loggers.

final Logger logger = CustomLogger.create(ValueFirstSmsSender.class);

//este nuevo método es similar al uso de logger.debug()
logger.notice("un mensaje de nivel de aviso");

Conclusión

Log4j2 es un marco de trabajo de registro de Java muy potente, que ofrece una amplia gama de características, configuraciones, mejoras de rendimiento y mucho más. Siendo los registros una parte muy importante del proceso de desarrollo de software, disponer de un marco de trabajo robusto como Log4j2 mejora las capacidades de la aplicación.

La flexibilidad y extensibilidad de Log4j2 permiten capturar adecuadamente los eventos que ocurren en su aplicación. Posteriormente, le permite pensar en los registros como una poderosa herramienta de depuración y auditoría. Con todas sus características y mejoras, Log4j2 destaca y se convierte en la opción preferida en una amplia gama de proyectos de software.

Puede que también le interesen estos IDEs y compiladores en línea de Java.