lunes, 29 de noviembre de 2021

Log4j ofrece control sobre el logging

La API log4j de código abierto para Java ofrece servicios de logs rápidos y eficientes

¡Advertencia! Log4j 1.x ha sido deprecada y reemplazada por Log4j 2.x. Sin embargo es buena practica iniciarnos con la versión predecesora.

Casi todas las aplicaciones grandes incluyen su propia API de logging. La experiencia indica que el logging representa un componente importante del ciclo de desarrollo. Como tal, el logging ofrece varias ventajas. Primero, puede proporcionar un contexto preciso sobre una ejecución de la aplicación. Una vez insertado en el código, la generación de la salida del log no requiere intervención humana. En segundo lugar, la salida del log se puede guardar en un medio persistente para estudiarla más adelante. Finalmente, además de su uso en el ciclo de desarrollo, también se puede emplear un paquete de logs suficientemente rico como herramienta de auditoría.

De conformidad con esa regla, a principios de 1996, el proyecto EU SEMPER (Mercado electrónico seguro para Europa) decidió escribir su propia API de logging. Después de innumerables mejoras, varias encarnaciones y mucho trabajo, esa API se ha convertido en log4j, un paquete de logging popular para Java. El paquete se distribuye bajo la Licencia Pública de IBM, certificada por la iniciativa de código abierto.

El logging tiene sus inconvenientes. Puede ralentizar una aplicación. Si es demasiado detallado, puede causar ceguera de desplazamiento. Para aliviar esas preocupaciones, log4j está diseñado para ser rápido y flexible. Dado que el log rara vez es el enfoque principal de una aplicación, la API de log4j se esfuerza por ser simple de entender y usar.

Este artículo comienza describiendo los componentes principales de la arquitectura log4j. Continúa con un ejemplo simple que describe el uso y la configuración básica. Concluye tocando los problemas de rendimiento y la próxima API de logging de Sun.

Categorías, Appenders y Layouts

Log4j tiene tres componentes principales:

  • Categorías
  • Appenders
  • Layouts

Los tres componentes trabajan juntos para permitir que los desarrolladores registren los eventos según el tipo de evento y la prioridad, y para controlar en tiempo de ejecución cómo se formatean estos mensajes y dónde se informan. Echemos un vistazo a cada uno de ellos.

Jerarquía de categorías

La primera y principal ventaja de cualquier API de logs sobre System.out.println simple reside en su capacidad para deshabilitar ciertas declaraciones de registro mientras permite que otras impriman sin obstáculos. Esa capacidad asume que el espacio del log, es decir, el espacio de todas las declaraciones de logs posibles, se categoriza de acuerdo con algunos criterios elegidos por el desarrollador.

De conformidad con esa observación, la clase org.log4j.Category figura en el núcleo del paquete. Las categorías son entidades con nombre. En un esquema de nomenclatura familiar para los desarrolladores de Java, se dice que una categoría es padre de otra categoría si su nombre, seguido de un punto, es un prefijo del nombre de la categoría secundaria. Por ejemplo, la categoría denominada com.foo es un padre de la categoría denominada com.foo.Bar. De manera similar, java es un padre de java.util y un antepasado de java.util.Vector.

La categoría raíz, que reside en la parte superior de la jerarquía de categorías, es excepcional de dos maneras:

  1. Siempre existe
  2. No se puede recuperar por nombre

En la clase Category, al invocar el método getRoot () estático se recupera la categoría raíz. El método estático getInstance () crea una instancia de todas las demás categorías. getInstance () toma el nombre de la categoría deseada como parámetro. Algunos de los métodos básicos de la clase Categoría se enumeran a continuación:

A las categorías se les pueden asignar prioridades del conjunto definido por la clase org.log4j.Priority. Aunque el conjunto de prioridades coincide con el del sistema Unix Syslog, log4j fomenta el uso de solo cuatro prioridades: ERROR, WARN, INFO y DEBUG, enumeradas en orden decreciente de prioridad. La lógica detrás de ese conjunto aparentemente restringido es promover una jerarquía de categorías más flexible en lugar de un conjunto de prioridades estático (aunque sea grande). Sin embargo, puede definir sus propias prioridades subclasificando la clase Prioridad. Si una categoría determinada no tiene una prioridad asignada, hereda una de su antepasado más cercano con una prioridad asignada. Como tal, para garantizar que todas las categorías puedan eventualmente heredar una prioridad, la categoría raíz siempre tiene una prioridad asignada.

Si una categoría no tiene una prioridad asignada va a heredar la prioridad de su padre.

Para registrar un evento de la aplicación, invoque uno de los métodos de impresión de una instancia de categoría. Esos métodos de impresión son:

  • error()
  • warn()
  • info()
  • debug()
  • log()

Por definición, el método de impresión determina la prioridad del registro del evento. Por ejemplo, si c es una instancia de categoría, entonces la instrucción c.info ("..") es una solicitud de logging de prioridad INFO.

Se dice que una solicitud de registro está habilitada si su prioridad es mayor o igual que la prioridad de su categoría. De lo contrario, se dice que la solicitud está deshabilitada. Una categoría sin una prioridad asignada heredará una de la jerarquía.

A continuación, encontrará un ejemplo de esa regla:

Llamar al método getInstance () con el mismo nombre siempre devolverá una referencia al mismo objeto de categoría. Por lo tanto, es posible configurar una categoría y luego recuperar la misma instancia en algún otro lugar del código sin pasar referencias. Las categorías se pueden crear y configurar en cualquier orden. En particular, una categoría padre buscará y vinculará a sus hijos incluso si se crea una instancia después de ellos. El entorno log4j generalmente se configura en la inicialización de la aplicación, preferiblemente leyendo un archivo de configuración, un enfoque que discutiremos en breve.

Log4j facilita la tarea de nombrar categorías por componente de software. Eso se puede lograr instanciando estáticamente una categoría en cada clase, con el nombre de la categoría igual al nombre completo de la clase, un método útil y sencillo para definir categorías. Como la salida del registro lleva el nombre de la categoría generadora, dicha estrategia de denominación facilita la identificación del origen de un mensaje del registro. Sin embargo, esa es solo una estrategia posible, aunque común, para nombrar categorías. Log4j no restringe el posible conjunto de categorías. De hecho, el desarrollador es libre de nombrar las categorías como desee.

Appenders y Layouts

La capacidad de habilitar o deshabilitar selectivamente las solicitudes de registro según su categoría es solo una parte de la imagen. Log4j también permite que las solicitudes de registro se impriman en múltiples destinos de salida llamados appenders en log4j. Actualmente, existen appenders para la consola, archivos, componentes GUI, servidores de socket remotos, NT Event Loggers y demonios remotos de UNIX Syslog.

Una categoría puede referirse a múltiples appenders. Cada solicitud de registro habilitada para una categoría determinada se reenviará a todos los appenders de esa categoría, así como a los que se agregan más arriba en la jerarquía. En otras palabras, los appenders se heredan de forma aditiva de la jerarquía de categorías. Por ejemplo, si agrega un appender de consola a la categoría raíz, todas las solicitudes de registro habilitadas se imprimirán al menos en la consola. Si, además, se agrega un appender de archivos a una categoría, digamos C, las solicitudes de registro habilitadas para C y los hijos de C se imprimirán en un archivo y en la consola. Tenga en cuenta que puede anular ese comportamiento predeterminado para que la acumulación de appenders ya no sea aditiva.

Las reglas que gobiernan la aditividad del appender se resumen a continuación.

Aditividad del appender

La salida de una declaración de registro del registrador C irá a todos los appenders en C y sus ancestros. Este es el significado del término "aditividad del appender".

Sin embargo, si un ancestro del registrador C, digamos P, tiene el indicador de aditividad establecido en falso, entonces la salida de C se dirigirá a todos los anexos en C y sus ancestros hasta e incluyendo P, pero no a los agregadores en ninguno de los ancestros de P .

Los registradores tienen su indicador de aditividad establecido en verdadero de forma predeterminada.

La siguiente tabla muestra un ejemplo:

Logger
Name
Added
Appenders
Additivity
Flag
Output TargetsComment
rootA1not applicableA1The root logger is anonymous but can be accessed with the Logger.getRootLogger() method. There is no default appender attached to root.
xA-x1, A-x2trueA1, A-x1, A-x2Appenders of "x" and root.
x.ynonetrueA1, A-x1, A-x2Appenders of "x" and root.
x.y.zA-xyz1trueA1, A-x1, A-x2, A-xyz1Appenders in "x.y.z", "x" and root.
securityA-secfalseA-secNo appender accumulation since the additivity flag is set to false.
security.accessnonetrueA-secOnly appenders of "security" because the additivity flag in "security" is set to false.

Para configurar el flag agregar la siguiente linea al archivo de configuración.

log4j.additivity.nombre.logger=false

La mayoría de las veces, los usuarios desean personalizar no solo el destino de salida, sino también el formato de salida, una hazaña que se logra al asociar un layout con un appender. El layout formatea el mensaje del registro de acuerdo con los deseos del usuario, mientras que un appender se encarga de enviar la salida formateada a su destino. PatternLayout, parte de la distribución estándar de log4j, permite al usuario especificar el formato de salida de acuerdo con patrones de conversión similares a la función printf del lenguaje C.

Por ejemplo, PatternLayout con el patrón de conversión % r [% t]% - 5p% c -% m% n generará algo similar a:

En la salida de arriba:

  • El primer campo es igual al número de milisegundos transcurridos desde el inicio del programa.
  • El segundo campo indica el hilo que realiza la solicitud de registro
  • El tercer campo representa la prioridad de la declaración de registro.
  • El cuarto campo es igual al nombre de la categoría asociada con la solicitud de registro.
  • El texto después de - indica el mensaje de la declaración.
Mas sobre los Layouts
https://logging.apache.org/log4j/2.x/manual/layouts.html

Mas sobre los Appenders
https://logging.apache.org/log4j/2.x/manual/appenders.html

Mas sobre Filtros (disponible en la versión 2.x)
https://logging.apache.org/log4j/2.x/manual/filters.html

Configuración

Insertar solicitudes de registro en el código de la aplicación requiere una gran cantidad de planificación y esfuerzo. La observación muestra que el código dedicado al registro representa aproximadamente el cuatro por ciento del total de la aplicación. En consecuencia, incluso las aplicaciones de tamaño moderado tendrán miles de declaraciones de registro incrustadas en su código. Dado su número, es imperativo administrar esas declaraciones de registro sin la necesidad de modificarlas manualmente.

El entorno log4j se puede configurar completamente mediante programación. Sin embargo, es mucho más flexible configurar log4j utilizando archivos de configuración. Actualmente, los archivos de configuración se pueden escribir en XML o en formato de propiedades Java (clave = valor).

Démosle una idea de cómo se hace con la ayuda de una aplicación imaginaria, MyApp, que usa log4j:

Como se ve en el código anterior, MyApp comienza importando clases relacionadas con log4j. Luego define una variable de categoría estática con el nombre MyApp, que resulta ser el nombre completo de la clase.

MyApp usa la clase Bar definida en el paquete com.foo:

En MyApp, la invocación del método BasicConfigurator.configure () crea una configuración log4j bastante simple. Ese método está programado para agregar a la categoría raíz una impresión FileAppender en la consola. La salida se formateará utilizando un PatternLayout establecido en el patrón% -4r [% t]% -5p% c% x -% m% n.

Tenga en cuenta que, de forma predeterminada, la categoría raíz se asigna a Priority.DEBUG.

La salida de MyApp es:

La Figura 1 muestra el diagrama de objetos de MyApp inmediatamente después de que llama al método BasicConfigurator.configure ().

//agregar figura

La Figura 1 muestra el diagrama de objetos de MyApp inmediatamente después de que llama al método BasicConfigurator.configure ().

La clase MyApp configura log4j invocando el método BasicConfigurator.configure (). Otras clases solo necesitan importar la clase org.log4j.Category, recuperar las categorías que desean usar y cerrar sesión.

El ejemplo anterior siempre genera la misma información de registro. Afortunadamente, es fácil modificar MyApp para que la salida del registro se pueda controlar en tiempo de ejecución. A continuación, verá una versión ligeramente modificada:

Esta versión de MyApp indica a PropertyConfigurator que analice un archivo de configuración y configure el registro en consecuencia.

Veamos un archivo de configuración de muestra que da como resultado exactamente el mismo resultado que el ejemplo anterior basado en BasicConfigurator:

Suponga que ya no deseamos ver la salida de ningún componente que pertenezca al paquete com.foo. El siguiente archivo de configuración muestra una posible forma de lograrlo:

La salida de MyApp configurada con este archivo se muestra a continuación:

Como la categoría com.foo.Bar no tiene una prioridad asignada, hereda su prioridad de com.foo, que se estableció en WARN en el archivo de configuración. La declaración de registro del método Bar.doIt () tiene la prioridad DEBUG, menor que la prioridad de categoría WARN. En consecuencia, se suprime la solicitud de registro de doIt ().

A continuación, vemos otro archivo de configuración que usa múltiples appenders:

Llamar a la MyApp mejorada con ese archivo de configuración generará lo siguiente en la consola:

Además, como a la categoría raíz se le ha asignado un segundo appender, la salida también se dirigirá al archivo example.log. Ese archivo se renovará cuando alcance los 100 KB. Cuando se produce una renovación, la versión anterior de example.log se mueve automáticamente a example.log.1.

Tenga en cuenta que para obtener esos diferentes comportamientos de registro, no es necesario volver a compilar el código. Con la misma facilidad podríamos haber iniciado sesión en un demonio de Unix Syslog y redirigir toda la salida de com.foo a un registrador de eventos de NT. De manera similar, podríamos haber reenviado los eventos de registro a un servidor log4j remoto, que registraría de acuerdo con la política del servidor local, por ejemplo, iniciando sesión en un archivo local y reenviando el evento de registro a un segundo servidor log4j.

Contextos de diagnóstico anidados

La mayoría de los sistemas del mundo real deben tratar con varios clientes simultáneamente. En una implementación multiproceso típica de un sistema de este tipo, diferentes subprocesos manejarán diferentes clientes. Teniendo en cuenta eso, el registro es especialmente adecuado para rastrear y depurar aplicaciones distribuidas complejas. Un enfoque común para diferenciar la salida de registro de un cliente de otro es crear una instancia de una nueva categoría separada para cada cliente. Sin embargo, ese enfoque promueve la proliferación de categorías y aumenta la sobrecarga administrativa del registro.

En una técnica más ligera, puede sellar de forma única cada solicitud de registro iniciada desde la misma interacción con el cliente, un enfoque descrito por Neil Harrison (ver Recursos). Para sellar de forma única cada solicitud, el usuario inserta información contextual en el contexto de diagnóstico anidado (NDC). La clase NDC se muestra a continuación:

El NDC se gestiona por hilo como una pila de información contextual. Tenga en cuenta que todos los métodos de org.log4j.NDC son estáticos. Suponiendo que la impresión NDC está activada, cada vez que se realiza una solicitud de registro, el componente log4j apropiado incluirá la pila NDC completa para el hilo actual en la salida del registro. Eso se hace sin la intervención del usuario, que es responsable únicamente de colocar la información correcta en el NDC mediante el uso de los métodos push () y pop () en algunos puntos bien definidos del código. En contraste, el enfoque de categoría por cliente exige cambios extensos en el código.

Para ilustrar ese punto, tomemos el ejemplo de un servlet que entrega contenido a numerosos clientes. El servlet puede construir el NDC al comienzo de la solicitud antes de ejecutar otro código. La información contextual puede ser el nombre de host del cliente y otra información inherente a la solicitud, generalmente información contenida en cookies. Por lo tanto, incluso si el servlet está sirviendo a varios clientes simultáneamente, los registros iniciados por el mismo código (es decir, pertenecientes a la misma categoría) aún se pueden distinguir porque cada solicitud de cliente tendrá una pila de NDC diferente. Compare eso con la complejidad de pasar una categoría recién instanciada a todo el código ejercido durante la solicitud del cliente.

No obstante, algunas aplicaciones sofisticadas, como los servidores web de alojamiento virtual, deben registrarse de manera diferente, según el contexto del host virtual y el componente de software que emite la solicitud. La versión más reciente de log4j admite múltiples árboles de jerarquía, una mejora que permite que cada host virtual posea su propia copia de la jerarquía de categorías.

Rendimiento

Uno de los argumentos más citados en contra del logging proviene de su costo computacional. Esa es una preocupación legítima, ya que incluso las aplicaciones de tamaño moderado pueden generar miles de solicitudes de registro. Se dedicó mucho esfuerzo a medir y ajustar el rendimiento del registro. Log4j afirma ser rápido y flexible: primero la velocidad, luego la flexibilidad.

No obstante, el usuario debe conocer los siguientes problemas de rendimiento:

1.- Rendimiento del registro cuando el registro está desactivado. Cuando el registro está desactivado por completo o para un conjunto de prioridades, el costo de una solicitud de registro es una invocación de método más una comparación de enteros. La invocación del método implica el costo oculto de la construcción de parámetros. Para algunas categorías cat, escribir

incurre en el costo de construir el parámetro de mensaje que convierte tanto el entero i como la entrada [i] en una cadena y concatena cadenas intermedias, independientemente de si el mensaje se registrará o no. Si a uno le preocupa la velocidad, escriba:

Eso no incurrirá en el costo de la construcción de parámetros si la depuración está deshabilitada. Por otro lado, si la categoría está habilitada para la depuración, incurrirá dos veces en el costo de evaluar si la categoría está habilitada o no: una vez en debugEnabled () y una vez en debug (). Eso representa una sobrecarga insignificante porque la evaluación de una categoría lleva aproximadamente el 1 por ciento del tiempo que lleva registrar realmente. En log4j, las solicitudes de registro se realizan a instancias de la clase Categoría. Debido a que Categoría es una clase y no una interfaz, el costo de la invocación del método es posiblemente menor, pero a costa de cierta flexibilidad. En una máquina Pentium II de 233 MHz, el costo típico de registro cuando el registro está desactivado está en el rango de 5 a 50 nanosegundos.

2.- El rendimiento de decidir si registrar o no registrar un registro cuando el logging está activado. En ese escenario, la pregunta se centra en el costo de rendimiento de recorrer la jerarquía de categorías. Cuando el registro está activado, log4j aún necesita comparar la prioridad de la solicitud de registro con la prioridad de la categoría de solicitud. Sin embargo, es posible que las categorías no tengan una prioridad asignada; pueden heredarlos de la jerarquía de categorías. Por lo tanto, para descubrir su prioridad, la categoría puede necesitar buscar a sus antepasados. Se ha hecho un esfuerzo serio para que esa jerarquía camine lo más rápido posible. Por ejemplo, las categorías secundarias se vinculan solo a sus antepasados ​​existentes. En el ejemplo de BasicConfigurator mostrado anteriormente, la categoría denominada com.foo.Bar enlaza directamente con la categoría raíz, evitando así las categorías com o com.foo inexistentes, mejorando significativamente la velocidad de la caminata, especialmente en jerarquías dispersas. caminar por la jerarquía está en el rango de 5 a 15 microsegundos, nuevamente en una máquina Pentium II de 233 MHz.

3.- El rendimiento real del Loggin. Otro problema de rendimiento se deriva del costo de formatear la salida del registro y enviarla a su destino. Aquí nuevamente, se hizo un gran esfuerzo para que los Layouts (formateadores) funcionen lo más rápido posible. Lo mismo ocurre con los Appenders. El costo típico de registrar realmente varía entre 100 y 300 microsegundos.

Aunque log4j tiene muchas características, su primer objetivo de diseño fue la velocidad. Algunos componentes de log4j se han reescrito muchas veces para mejorar el rendimiento. Sin embargo, los contribuyentes suelen presentar nuevas optimizaciones.

La empresa SUN está construyendo su API de Logging

**Actualización: La API ya se encuentra disponible, se le conoce como JUL Java Util Logging**

Sun ha iniciado la petición JSR47 en Java Community Process (JCP) para definir una API de Logging para Java. La API resultante requerirá la versión 1.4 de JDK y pronto estará lista para su revisión pública.

La API JSR47 y Log4j son bastante similares a nivel arquitectónico. La API JSR47 admitirá un espacio de nombres jerárquico, una de las características centrales de log4j. Por otro lado, log4j posee muchas características útiles que faltan en JSR47. Dado el impulso detrás de log4j, en mi opinión partidista, es probable que JSR47 esté obsoleto para cuando se lance. Log4j está escrito por las personas que lo utilizan, no por un comité cerrado.

Por cierto, log4j ya proporciona soporte para la conexión en la API JSR47 cuando esta última esté disponible.

Permítanme también mencionar JLog, otra API de registro disponible en alphaWorks de IBM. JLog se llamaba anteriormente RAS Toolkit hasta que IBM lo renombró. En el sitio alphaWorks, JLog tiene la etiqueta "Logging Toolkit for Java", casi la misma etiqueta que tenía log4j en alphaWorks antes de migrar a http://log4j.org. JLog, aunque es un buen paquete de registro, no debe confundirse con log4j.

Conclusión

Log4j es un paquete de registro popular escrito en Java. Uno de sus rasgos distintivos incluye la noción de herencia en categorías. Usando una jerarquía de categorías, es posible controlar qué declaraciones de registro se generan con granularidad arbitraria, reduciendo así el volumen de salida registrada y minimizando el costo de registro.


Una de las ventajas de la API de log4j es su capacidad de administración. Una vez que las declaraciones de registro se han insertado en el código, se pueden controlar con archivos de configuración. Además, pueden habilitarse o deshabilitarse selectivamente y enviarse a diferentes y múltiples destinos de salida en formatos elegidos por el usuario. Además, el paquete log4j está diseñado para que las declaraciones de registro puedan permanecer en el código enviado sin incurrir en un alto costo de rendimiento.


Log4j es el resultado de un esfuerzo colectivo. Mi especial agradecimiento a todos los autores que han contribuido al proyecto. Sin excepción, las mejores características del paquete se han originado en la comunidad de usuarios.

Referencia: https://logging.apache.org/log4j/1.2/manual.html

Más información sobre este tema

"Patterns for Logging Diagnostic Messages," Neil Harrison in Pattern Languages of Program Design 3, edited by R. Martin, D. Riehle, and F. Buschmann (Addison-Wesley, 1997)
http://www.amazon.com/exec/obidos/ASIN/0201310112/qid%3D973215403/002-8420944-7444837/javaworld

Log4j project homepage
http://jakarta.apache.org/log4j/docs/

Sun's Java Logging API Specification
http://java.sun.com/aboutJava/communityprocess/jsr/jsr_047_log.html

Igor Poteryaev, an independent author, has ported log4j to the Python language, called log4p
http://log4p.sourceforge.net

IBM's JLog API
http://www.alphaworks.ibm.com/tech/loggingtoolkit4j
The EU's Secure Electronic Marketplace for Europe (SEMPER) project homepage

http://www.semper.org

OpenSource.org
http://www.opensource.org

Level
https://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/Level.html

Category
https://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/Category.html

PatternLayout
https://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/PatternLayout.html

miércoles, 24 de noviembre de 2021

Logging de una aplicación en Java

El logging de una aplicación consiste en registrar (logs) información relevante del comportamiento de la ejecución de nuestra aplicación. Contiene datos de variables, entidades, cambios de estado,  componentes de software involucradas en dicha ejecución y la llamada de los métodos. Su principal funcionalidad es facilitar el seguimiento o análisis de la ejecución de la aplicación:

  • Analizar el comportamiento de la aplicación durante la fase de desarrollo y depuración (pruebas de caja blanca)
  • Analizar los bugs o errores de ejecución detectados, sus causas y consecuencias
  • Servir de registro de auditoría cuando la información contenida y el modo en que se ha procesado cumpla los criterios requeridos
  • Medir el rendimiento o carga de los sistemas o aplicaciones
  • Revertir el estado del aplicativo siguiendo en orden inverso el log

Aunque depende de las circunstancias en las que nos encontremos, el segundo de los usos suele ser el más relevante. Un buen log desarrollado correctamente en el código y mantenido o configurado en explotación es una garantía de respuesta rápida para análisis de errores; que incluso podrían hacerse sin necesidad de parar el aplicativo, reconfigurarlo o aplicarle ningún cambio.

El log o registro de la aplicación suele formarse por un conjunto de eventos que se almacenan secuencialmente, por lo general en el orden en que suceden, de manera persistente o recuperable. Se pueden almacenar en ficheros, en BBDD, en componentes distribuidos a tal efecto. Se pueden habilitar mecanismos de rotación o históricos de estos logs, se pueden utilizar por monitores para lanzar alertas, se pueden integrar y fusionar para hacer análisis más exhaustivos. Lo relevante es que la información registrada y la forma en que se gestiona sea útil.

Existen numerosas soluciones y propuestas de software, tanto libres como propietarias, más o menos estandarizadas, más sencillas o más completas, de mil tipos y formas. Lo importante es buscar aquella se ajusta a nuestras necesidades y entornos, para olvidarnos de la implementación del mecanismo por completo; y ceñirnos a los dos aspectos más importantes:

  • El contenido de cada registro o evento, principal preocupación del desarrollador
  • El modo en que se procesa, persiste y gestiona, principal preocupación de la explotación del sistema o aplicativo

El coste de implementación del logging se encuentra en el ir, mientras se desarrolla, dejando registros (logs) en los diferentes puntos del código. Esta actividad debe hacerse durante el desarrollo, siguiendo patrones, criterios y procedimientos preestablecidos. De esta manera los desarrolladores tendrán criterios comunes y los logs serán coherentes entre las diferentes partes del código.

La información registrada en el log debe ser relevante y completa. Se pueden considerar los siguientes aspectos a la hora de decidir qué información incluir:

Qué:

Qué evento o acción ha ocurrido.

Qué entidades han estado involucradas.

Si hay un cambio de estado, ¿Cuál era el anterior? ¿Cuál es el nuevo estado?.

Dónde:

En qué punto del código ha ocurrido: componente, clase, fichero de código, método o bloque de ejecución, línea de código… Cuanto más detallada sea esta información mejor para localizar el lugar del posible error o por donde ha pasado la ejecución, por un lado; pero más puede afectar el logging al rendimiento.

Cuándo:

Registrando el momento temporal, bien absoluto o bien relativo al comienzo de ejecución o cualquier otro evento.

Generando los logs secuencial o causal, en la que los eventos que ocurren antes en el tiempo o que ocasionan otros, aparezcan antes.

En qué contexto:

Registrando estados o variables: propios de la ejecución (parámetros), de personalización o específicos de usuario, referentes a la sesión o transacción en ejecución…

Indicando hilos, transacciones o peticiones relacionadas cuando estemos en entornos concurrentes.

Para que la información de los logs sea más detallada en momento de análisis y más manejable durante la explotación de la misma, se establecen niveles de filtrado. De tal manera que solo se muestra o almacenan aquellos eventos con un nivel mayor o igual al del nivel de log establecido. Las librerías de logging permiten filtrar los eventos por otros criterios como son la clase o contexto del evento, también.

Los niveles más comunes son DEBUG, INFO, WARNING y ERROR. La clasificación de los diferentes eventos en cada nivel es parte del ejercicio de análisis, y deben orientarse a que la traza sea legible y útil en los diferentes contextos del aplicativo, desde el desarrollo hasta la explotación.

Este puede ser un ejemplo de semántica de niveles de logging:

DEBUG: para información de muy bajo nivel solo útil para el debug de la aplicación, tanto en el desarrollo como en el análisis de incidencias

Llamadas a funciones y procedimientos y otros componentes, con parámetros y respuestas

Flujos de ejecución

Desarrollo de algoritmos y procedimientos que permitan identificar y seguir su ejecución en desarrollo

INFO: información de más alto nivel que permita hacer un seguimiento de la ejecución normal

Paradas y arranques de servicios y sistemas

Parámetros críticos o relevantes de configuración

Comienzo y fin de transacciones y operaciones completas

Cambios de estado de operaciones

WARN: información de situaciones, que aún sin ser de error, si son anómalas o no previstas, aunque el aplicativo tiene alternativas para solventarlas

Parámetros no definidos, y cuyo valor se toma por defecto

Situaciones anómalas, pero que son resueltas por el aplicativo, dejando la operación en un estado correcto

Funcionalidades no primordiales o imprescindibles, que no pueden resolverse, pero que dejan la operación en un estado correcto

ERROR: información de situaciones que son de error y que impiden la ejecución correcta de una operación o transacción, pero sin afectar a otras operaciones o transacciones (error aislado o contenido)

No se pudo realizar una operación o transacción, pero no afecta a otras

Peticiones o consultas erróneas (almacenando los parámetros de entrada)

Funcionalidades generales del aplicativo, que aún afectando al funcionamiento general del aplicativo, no se consideran primordiales o imprescindibles

FATAL: información de situaciones de error que afectan al funcionamiento general del aplicativo (errores no aislados o contenidos en alcance)

Parámetros no definidos o configuraciones erróneas

Falta de conexión o comunicación con otros componentes

Errores de ejecución que pueden afectar a operaciones o transacciones independientes, o que afectan al funcionamiento general de la aplicación

Tanto el contenido y forma de cada evento de log, como la semántica de los niveles son parte del diseño del aplicativo. Estos criterios deben definirse y ser comunicados al equipo de desarrollo para que se apliquen de manera homogénea y coherente en todo el desarrollo. Si estos criterios son consensuados con el equipo se puede sacar mucho provecho de la experiencia de los desarrolladores.

Otras recomendaciones a tener en cuenta son:

Registrar los eventos de manera atómica, que toda la información referente a un evento se almacene en un registro o línea.

Orientar el formato de la información mostrada a poder ser usado de manera informatizada o automática con herramientas específicas.

En lo referente a excepciones:

Mostrar siempre la traza (trace o llamada secuencial de los métodos) completa de la excepción con su mensaje y todo el stacktrace.

Si la excepción es capturada, tratada y luego lanzada de nuevo (bien la misma u otra) no dejar traza de la excepción hasta que se capture y se trate en última instancia. De esta manera cada excepción solo se mostrará una vez. Si no lo hacemos así y registramos cada captura y relanzamiento aparecerá en la traza varias veces y no sabremos si es la misma o son varias. (Antipatrón catch-log-throw).

Hacer uso de la propiedad de excepción causante o interna cuando capturemos y relancemos la excepción. En Java es el método getCause(), en .NET la propiedad InnerException.

Implementar el método toString() de todas las clases de negocio o POJOs para facilitar su traceado.

Hay que tener en cuenta las diferencias y zonas horarias a la hora de trabajar con logs de varias máquinas o componentes.

El logging no puede tener efectos laterales, no puede modificar el estado de ningún parámetro, variable o procedimiento. Solo muestra información. Debe ser ligero, el propio logging no puede requerir de procesamientos largos o costosos.

Las llamadas a métodos, componentes y componentes distribuidos externos, deberían registrarse tras la llamada incluyendo los parámetros de entrada y la respuesta, así como clase/componente y método, a nivel de DEBUG. Y a nivel de ERROR y FATAL cuando ocurra un error en la llamada (si fuese una excepción se debe tracear parámetros de entrada y excepción)

En Java tenemos varias API's o librerías para llevar a cabo el logging de nuestra aplicación, siendo las mas comunes:

  • Log4j2 antes Log4j
  • Loogback
  • JUL (Java Util Logging)
Tenemos dos fachadas las cuales permitirán que nuestra aplicación funcione con cualquiera las tres API's mencionadas, estas fachadas son:
  • Commons Logging
  • SLF4J
Continua con una guía practica para la API Log4j  Log4j ofrece control sobre el logging