Amarengo

Articles and news

9 Errores de registro en sus Aplicaciones Java

El registro de información de tiempo de ejecución en su aplicación Java es de vital utilidad para comprender el comportamiento de cualquier aplicación, especialmente en los casos en que se encuentra con escenarios inesperados, errores o simplemente necesita realizar un seguimiento de ciertos eventos de la aplicación.

En un entorno de producción del mundo real, por lo general no tiene el lujo de depurar. Y, por lo tanto, los archivos de registro pueden ser lo único que debe usar al intentar diagnosticar un problema que no es fácil de reproducir.

Si se hace correctamente, los archivos de registro también pueden ahorrarle mucho tiempo al proporcionar pistas sobre la causa del problema y el estado del sistema en el momento en que ocurrió. Además, el registro puede ser útil para fines de auditoría, recopilación de estadísticas, extracción de inteligencia empresarial y una variedad de otras tareas.

En general, el registro es sin duda una práctica fundamental que proporciona beneficios significativos durante la vida útil de la aplicación, por lo que puede ser tentador comenzar a registrar tantos datos de registro como pueda.

Sin embargo, el uso inadecuado del registro también puede tener inconvenientes significativos.

En las siguientes secciones, echaremos un vistazo a algunas de las prácticas más comunes y perjudiciales que puede encontrar al hacer uso de iniciar sesión en una aplicación.

Todos los ejemplos y configuraciones utilizan la popular biblioteca log4j 2. Logback es otra gran opción, también bien soportada por Stackify.

9 Problemas de registro de Java y Cómo Evitarlos

Registro de información confidencial

Para empezar, probablemente la práctica de registro más dañina provocada por el enfoque «registrar tanto como sea posible por si acaso» es mostrar información confidencial en los registros.

La mayoría de las aplicaciones manejan datos que deben permanecer privados, como credenciales de usuario o información financiera. El peligro de tener este tipo de información registrada en un archivo de texto plano es claro: es muy probable que los archivos de registro sean procesados por múltiples sistemas no seguros.

Además, el registro de algunas categorías de datos, como la información financiera, también está muy regulado y puede tener graves implicaciones legales.

La mejor manera de evitar esto es simplemente asegurarse de que nunca registre este tipo de información confidencial.

Hay alternativas, como cifrar los archivos de registro, pero eso generalmente hace que estos archivos sean mucho menos utilizables en general, lo cual no es ideal.

Antes de seguir adelante, aquí hay una lista más completa de los tipos de información que necesita para registrar con mucho cuidado.

Registro de entrada de usuario simple

Otro problema de seguridad común en las aplicaciones Java es la forja de registros JVM.

En pocas palabras, la falsificación de registros puede ocurrir cuando los datos de una fuente externa, como la entrada del usuario u otra fuente no confiable, se escriben directamente en los registros. Un atacante malicioso puede introducir una entrada que simula una entrada de registro como «\n \ nweb-2017-04-12 17: 47: 08,957 INFO Amount reversed successfully», lo que puede resultar en datos de registro dañados.

Hay varias maneras de manejar este tipo de vulnerabilidad:

  • no registrar ninguna entrada de usuario, no siempre es posible, ya que los datos de usuario pueden ser críticos para llegar a la causa raíz de algunos problemas
  • usar validación antes de registrar, esta solución puede afectar el rendimiento, así como prescindir del registro de información importante
  • iniciar sesión en una base de datos, más segura pero costosa en cuanto al rendimiento, y puede introducir otra vulnerabilidad: Inyección SQL
  • API de seguridad de OWASP

Usar ESAPI es definitivamente una buena manera de hacerlo; esta biblioteca de seguridad de código abierto de OWASP puede codificar datos antes de escribirlos en los registros:

message = message.replace( '\n' , '_' ).replace( '\r' , '_' ) .replace( '\t' , '_' );message = ESAPI.encoder().encodeForHTML( message );

Registro excesivo

Otra práctica que debe evitarse es registrar demasiada información. Esto puede ocurrir en un intento de capturar todos los datos potencialmente relevantes.

Un problema posible y muy real con este enfoque es la disminución del rendimiento. Sin embargo, con la evolución de las bibliotecas de registro, ahora tiene las herramientas para que esto sea menos preocupante.

Como ejemplo de rendimiento mejorado, el 2.la versión x de log4j utiliza el registro asíncrono, lo que significa ejecutar operaciones de E/S en un subproceso separado.

Demasiados mensajes de registro también pueden provocar dificultades para leer un archivo de registro e identificar la información relevante cuando se produce un problema.

Una forma de reducir el número de líneas de código de registro es registrando información importante a través de preocupaciones transversales en el sistema.

Por ejemplo, si desea registrar el inicio y el final de métodos particulares, puede agregar un Aspecto que lo haga para cada método que tenga una anotación personalizada especificada:

@Aspectpublic class MyLogger { private static final Logger logger = LogManager.getLogger(MyLogger.class); @Around("execution(* *(..)) && @annotation(LogMethod)") public Object around(ProceedingJoinPoint joinPoint) throws Throwable { logger.info("Starting method execution: " + joinPoint.getSignature().getName() + " in class:"+joinPoint.getSignature().getDeclaringTypeName()); Object result = joinPoint.proceed(); logger.info("Exiting method execution: " + joinPoint.getSignature().getName() + " in class:"+joinPoint.getSignature().getDeclaringTypeName()); return result; }}

Con la ayuda del aspecto personalizado, ahora podemos ser muy selectivos y elegir las áreas exactas de la aplicación donde realmente necesitamos esa información en los registros. Y, como resultado, podemos reducir significativamente la huella de registro general del sistema.

Mensajes de registro crípticos

Al analizar archivos de registro, encontrar una línea que no proporciona suficiente información puede ser frustrante. Un escollo común es la falta de especificidad o contexto en los mensajes de registro.

Para ilustrar el problema, echemos un vistazo a un mensaje de registro que carece de especificidad:

Operation failed.

En su lugar, puede agregar información más específica e identificable:

File upload picture.jpg failed.

Siempre tenga en cuenta que sus registros sin duda serán leídos por un desarrollador o administrador de sistemas diferente, y que necesitan comprender lo que ha sucedido en la aplicación.

Una buena manera de agregar contexto en los mensajes de registro es incluir la marca de tiempo, el nivel de registro, el nombre del hilo y el nombre de clase completo del evento. De esta manera, puede identificar más fácilmente cuándo y dónde ocurren eventos específicos en tiempo de ejecución.

Para agregar esta información al usar log4j 2, puede configurar un Diseño de patrón con las opciones %d para la fecha, %p para el nivel de registro, %t para el nombre del hilo y %c para el nombre de la clase:

<PatternLayout> <Pattern>%d %p %c - %m%n</Pattern></PatternLayout>

Un mensaje de registro usando el diseño anterior se verá así:

2017-05-11 22:51:43,223 INFO com.stackify.service.MyService - User info updated

Usar un solo archivo de registro

La desventaja de usar solo un archivo de registro para la aplicación es que, con el tiempo, se volverá bastante grande y difícil trabajar con él.

Una buena práctica para encontrar rápidamente información relevante es crear un nuevo archivo de registro cada día, con la fecha como parte del nombre del archivo.

Echemos un vistazo a un ejemplo de cómo crear un archivo de registro con el nombre igual a la fecha actual si se utiliza la biblioteca log4j2:

SimpleLayout layout = new SimpleLayout();FileAppender appender = new FileAppender(layout, LocalDate.now().toString(), false);logger.addAppender(appender);

La misma biblioteca también ofrece la opción de configurar un Appender de archivos móviles que creará nuevos archivos de registro en determinados intervalos de tiempo:

<RollingFile name="RollingFile" fileName="logs/app.log" filePattern="logs/$${date:yyyy-MM}/app-%d{MM-dd-yyyy}-%i.log.gz"> <PatternLayout> <Pattern>%d %p %c - %m%n</Pattern> </PatternLayout> <Policies> <TimeBasedTriggeringPolicy /> <SizeBasedTriggeringPolicy size="250 MB"/> </Policies> <DefaultRolloverStrategy max="20"/></RollingFile>

La configuración anterior dará como resultado uno o más archivos creados para cada día de hasta 250 MB por archivo con la fecha actual como nombre de archivo, colocados en carpetas con nombres del formulario año-mes.

Elegir niveles de registro incorrectos

Elegir un nivel de registro inadecuado llevará a que se pierdan eventos significativos o se inunde con muchos datos menos importantes.

En pocas palabras, elegir el nivel de registro correcto para los diferentes registros de su sistema es una de las cosas principales que necesita hacer bien para tener una buena experiencia comprendiendo sus registros.

La mayoría de los marcos de registro tienen un conjunto de niveles similares a FATAL, ERROR, WARN, INFO, DEBUG, TRACE, ordenados de mayor a menor.

Niveles de registro disponibles

Echemos un vistazo a cada uno de estos y al tipo de mensajes de registro que deben contener en función de la gravedad:

  • FATAL debe reservarse para errores que causen que la aplicación se bloquee o no se inicie (por ejemplo: ERROR debe contener problemas técnicos que deben resolverse para el funcionamiento adecuado del sistema (por ejemplo: no se pudo conectar a la base de datos)
  • WARN se utiliza mejor para problemas temporales o comportamientos inesperados que no obstaculizan significativamente el funcionamiento de la aplicación (por ejemplo: inicio de sesión de usuario fallido)
  • INFO debe contener mensajes que describan lo que está sucediendo en la aplicación (por ejemplo: usuario registrado, pedido realizado)
  • DEBUG está diseñado para mensajes que podrían ser útiles para depurar un problema (por ejemplo: ejecución del método iniciada)
  • EL SEGUIMIENTO es similar a la DEPURACIÓN, pero contiene eventos más detallados (por ejemplo: modelo de datos actualizado)

Control de niveles de registro

El nivel de registro de un mensaje se establece cuando se escribe:

logger.info("Order ID:" + order.getId() + " placed.");

Las API de registro generalmente le permiten configurar el nivel al que desea ver los mensajes. Lo que esto significa es que, si establece el nivel de registro para la aplicación o ciertas clases en INFO, por ejemplo, solo verá mensajes en los niveles FATAL, ERROR, WARN e INFO, mientras que los mensajes de DEPURACIÓN y RASTREO no se incluirán.

Esto es útil, ya que normalmente mostraría mensajes de depuración o de reducción en desarrollo, pero no en producción.

Aquí se muestra cómo puede establecer el nivel de registro para una clase, paquete o aplicación completa en log4j 2, utilizando un log4j2.archivo de propiedades:

loggers=classLogger,packageLoggerlogger.classLogger.name=com.stackify.service.MyServicelogger.classLogger.level=infologger.packageLogger.name=com.stackify.configlogger.packageLogger.level=debug
rootLogger.level=debug

Para mostrar el nivel de registro en el mensaje, puede agregar la opción %p en log4j2PatternLayout.

Antes de continuar, tenga en cuenta que, si ninguno de los niveles existentes es adecuado para las necesidades de su aplicación, también tiene la posibilidad de definir un nivel de registro personalizado.

Seguimiento de Una Sola Operación en Varios Sistemas y Registros

En sistemas distribuidos con múltiples servicios implementados de forma independiente que trabajan juntos para procesar solicitudes entrantes, el seguimiento de una sola solicitud en todos estos sistemas puede ser difícil.

Es muy probable que una sola solicitud llegue a varios de estos servicios, y si ocurre un problema, necesitaremos corroborar todos los registros individuales de estos sistemas para obtener una imagen completa de lo que sucedió.

Para abordar este tipo de arquitectura, ahora tenemos una nueva generación de herramientas auxiliares de registro en el ecosistema, como Zipkin y Spring Cloud Sleuth.

Zipkin rastrea solicitudes en arquitecturas de microservicios para ayudarlo a identificar qué aplicación está causando el problema. También viene con una interfaz de usuario útil donde puede filtrar trazas en función de la aplicación, la longitud de la traza o la marca de tiempo.

Y el proyecto Spring Cloud Sleuth funciona agregando un ID único de 64 bits a cada seguimiento; una solicitud web, por ejemplo, puede constituir un seguimiento. De esta manera, la solicitud se puede identificar en varios servicios.

Estas herramientas abordan las limitaciones de las bibliotecas principales y navegan por las nuevas realidades del estilo más distribuido de las arquitecturas.

No iniciar sesión con JSON

Si bien es muy común iniciar sesión en formato de texto plano, la llegada del almacenamiento de registros y los sistemas de análisis de datos lo han desplazado hacia JSON.

JSON como formato de registro de la aplicación principal tiene la ventaja de ser tan legible como el texto sin formato, a la vez que es mucho más fácil de analizar mediante herramientas de procesamiento automatizadas.

Por ejemplo, Log4j 2 ofrece el JSONLayout para este propósito exacto:

<JSONLayout complete="true" compact="false"/>

Esto producirá un documento JSON bien formado:

Como JSON, los datos de registro serán semánticamente más ricos cuando se procesen mediante un sistema de gestión de registros, como el de rastreo, que habilitará inmediatamente sus potentes capacidades de registro semántico/estructurado.

Impacto del registro en el rendimiento

Finalmente, consideremos un problema que es inevitable al agregar registros a una aplicación: el impacto en el rendimiento.

Es de esperar una pequeña caída en el rendimiento. Sin embargo, es importante hacer un seguimiento de esto para que pueda minimizarlo y no ralentizar el sistema.

Algunos aspectos relacionados con el rendimiento a tener en cuenta al elegir una API de registro son:

  • Operaciones de E/S de archivos utilizando un búfer – esto es crítico, ya que la E/S de archivos es una operación costosa
  • Registro asíncrono – esto debe considerarse para que el registro no bloquee otros procesos de aplicación
  • Tiempo de respuesta de registro: el tiempo que toma escribir una entrada de registro y devolver
  • Número de subprocesos utilizados para el registro
  • Filtrado de nivel de registro: esto se hace para verificar si el nivel de registro correspondiente a un mensaje está habilitado, y se puede hacer recorriendo la jerarquía o haciendo que el Registrador apunte directamente a la configuración del registrador; este último enfoque es preferible con respecto al rendimiento

Por supuesto, si necesita mantener la opción abierta y el sistema flexible, siempre puede usar una abstracción de nivel superior, como slf4j.

Antes de mover uno, estos son solo algunos pasos que puede tomar para mejorar el rendimiento de registro de su sistema:

  • ajustar el nivel de registro de la aplicación para paquetes detallados
  • evitar registrar la información de ubicación de origen en tiempo de ejecución, ya que buscar el subproceso actual, el archivo, un método es una operación costosa
  • evitar errores de registro con trazas largas de pila
  • comprobar si está habilitado un nivel de registro específico antes de escribir un mensaje con ese nivel – de esta manera el mensaje no se construirá si no es necesario
  • revise los registros antes de pasar a producción para verificar si se puede eliminar algún registro

Menciones de honor

Antes de terminar, vamos a eche un vistazo a una práctica final que debe evitar, y que es usar salida estándar en lugar de registrar.

Mientras que el sistema.out() puede ser una forma rápida de comenzar muy temprano en el ciclo de desarrollo, definitivamente no es una buena práctica a seguir después de ese punto.

Además del hecho de que pierde todas las potentes funciones de una API de registro dedicada, este inconveniente principal aquí es simplemente el hecho de que los datos de registro no se conservarán en ningún lugar.

Finalmente, otra mención de honor es una práctica que puede hacer que leer y analizar datos de registro sea mucho más fácil: mensajes de registro estandarizados. En pocas palabras, los eventos similares deben tener mensajes similares en el registro.

Si necesita buscar todas las instancias de ese evento en particular o extraer información significativa de sus datos de registro, los mensajes de registro estándar son muy importantes.

Por ejemplo, si una operación de carga falla, tener estos mensajes diferentes en el registro sería confuso:

Could not upload file picture.jpg
File upload picture.jpg failed.

En su lugar, siempre que se produzca un error en la carga del archivo, debe usar uno de estos mensajes para registrar el error.

Conclusión

El uso del registro se ha vuelto omnipresente en el desarrollo de aplicaciones, debido a la información muy útil y procesable que aporta al tiempo de ejecución del sistema.

Sin embargo, para aprovechar al máximo sus datos de registro, es importante ir más allá de lo básico, desarrollar una cultura de registro y comprender los puntos más finos de operación con estos datos a escala y en producción.

Deja una respuesta

Tu dirección de correo electrónico no será publicada.