Registro de Actividad en Sistemas de Transporte Críticos con Java

1. La Imperiosa Necesidad de Registrar Operaciones en Sistemas de Transporte

Las infraestructuras de transporte modernas, como los metros, redes de autobuses o sistemas ferroviarios de alta velocidad, son entornos altamente exigentes que demandan una concurrencia elevada y una fiabilidad inquebrantable. Cualquier fallo en estos sistemas no solo deteriora la experiencia del usuario, sino que puede acarrear graves consecuencias para la seguridad pública. En este contexto, el registro de operaciones actúa como la última barrera de defensa para la integridad del sistema.

Las razones clave para implementar un logging robusto incluyen:

  • Diagnóstico Rápido de Fallos: Permite identificar la causa raíz de los problemas con celeridad.
  • Auditoría de Seguridad: Proporciona un rastro inmutable de todas las acciones, previniendo errores o manipulaciones malintencionadas.
  • Cumplimiento Normativo: Satisface los requisitos de seguridad y regulación específicos del sector.
  • Trazabilidad de Usuarios: Registra las interacciones de los usuarios, facilitando la resolución de quejas o disputas.

En un sistema de transporte, el registro de operaciones no es un lujo, sino una necesidad fundamental para garantizar la seguridad y la continuidad del servicio.

2. Principios Esenciales para un Registro de Operaciones Eficaz

Principio 1: Captura de Eventos Cruciales

Es vital registrar aquellas acciones que impactan el estado del sistema o su operación. Esto incluye:

  • Alteraciones del estado del sistema (ej., inicio, apagado, reinicio).
  • Interacciones de los usuarios (ej., autenticación, modificación de configuraciones).
  • Situaciones anómalas del sistema (ej., errores críticos, advertencias).
Principio 2: Uniformidad en el Formato de Registros

Un formato estandarizado asegura que los logs sean legibles, procesables y analizables. Elementos clave incluyen:

  • Marca de tiempo precisa.
  • Categoría de la operación.
  • Entidad responsable de la acción.
  • Detalles de la acción realizada.
  • Resultado de la operación.
Principio 3: Gestión Adecuada de Niveles de Logging

La asignación inteligente de niveles de log (INFO, WARN, ERROR) previene la sobrecarga de información y garantiza la relevancia:

  • INFO: Para eventos operativos significativos.
  • WARN: Para problemas potenciales que no son críticos de inmediato.
  • ERROR: Para fallos del sistema o excepciones irrecuperables.

Un equilibrio es clave: un exceso de logs puede ocultar información relevante, mientras que una escasez puede dificultar la depuración.

3. Selección de Frameworks de Logging en Java

3.1 Log4j 2: Potente y Configurable

Log4j 2 es una opción robusta para el logging en Java, destacando por su flexibilidad. Sin embargo, su configuración puede ser compleja.


<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="INFO">
   <Appenders>
       <Console name="ConsolaAppender" target="SYSTEM_OUT">
           <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %message%n"/>
       </Console>
       <File name="ArchivoOperaciones" fileName="logs/sistema-transporte.log">
           <PatternLayout pattern="%d{ISO8601} [%thread] %-5level %logger{36} - %message%n"/>
       </File>
   </Appenders>
   <Loggers>
       <Logger name="com.sistema.transporte.operaciones" level="debug" additivity="false">
           <AppenderRef ref="ConsolaAppender"/>
           <AppenderRef ref="ArchivoOperaciones"/>
       </Logger>
       <Root level="warn">
           <AppenderRef ref="ConsolaAppender"/>
       </Root>
   </Loggers>
</Configuration>
   

El archivo log4j2.xml define la configuración. La etiqueta <Logger> configura el nivel de logging y los destinos para el paquete com.sistema.transporte.operaciones. Asegúrese de usar additivity="false" para evitar la duplicación de logs.

3.2 SLF4J + Logback: Ligero y Altamente Recomendado

La combinación de SLF4J como fachada de logging y Logback como implementación es una elección popular por su ligereza y eficiencia.


<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="30 seconds">
   <appender name="CONSOLA_SALIDA" class="ch.qos.logback.core.ConsoleAppender">
       <encoder>
           <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{20} - %msg%n</pattern>
       </encoder>
   </appender>
   
   <appender name="ARCHIVO_ROTABLE" class="ch.qos.logback.core.rolling.RollingFileAppender">
       <file>logs/registro-eventos.log</file>
       <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
           <fileNamePattern>logs/registro-eventos.%d{yyyy-MM-dd}.gz</fileNamePattern>
           <maxHistory>60</maxHistory>
           <totalSizeCap>1GB</totalSizeCap>
       </rollingPolicy>
       <encoder>
           <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
       </encoder>
   </appender>
   
   <root level="debug">
       <appender-ref ref="CONSOLA_SALIDA"/>
       <appender-ref ref="ARCHIVO_ROTABLE"/>
   </root>
   
   <logger name="com.sistema.transporte.actividad" level="info" additivity="false">
       <appender-ref ref="ARCHIVO_ROTABLE"/>
   </logger>
</configuration>
   

El archivo logback.xml centraliza la configuración. Similar a Log4j 2, la etiqueta <logger> gestiona el comportamiento del logging para paquetes específicos, y additivity="false" es crucial para prevenir entradas duplicadas.

3.3 JUL (Java Util Logging): La Opción Integrada pero Limitada

JUL es el sistema de logging nativo de Java. Aunque está integrado, sus funcionalidades son más básicas en comparación con Log4j 2 o Logback, lo que lo hace menos ideal para sistemas de alta complejidad como los de transporte.


handlers = java.util.logging.FileHandler, java.util.logging.ConsoleHandler
.level = WARNING

com.infraestructura.vial.monitoreo.level = FINE

java.util.logging.FileHandler.pattern = logs/monitoreo-vial-%u-%g.log
java.util.logging.FileHandler.limit = 1000000
java.util.logging.FileHandler.count = 5
java.util.logging.FileHandler.formatter = java.util.logging.SimpleFormatter
java.util.logging.ConsoleHandler.level = INFO
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
   

En logging.properties, se configuran los manejadores y niveles de logging. JUL es una opción funcional para aplicaciones simples, pero su limitada flexibilidad en formato y rotación lo hace poco recomendable para sistemas críticos.

4. Implementación de un Gestor de Registro de Actividad en Java

4.1 Diseño del Gestor de Registro

Un componente dedicado simplifica la generación de logs, asegurando coherencia y un formato uniforme.


package com.sistema.transporte.actividad;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class GestorRegistroActividad {
   private static final Logger REGISTRADOR = LoggerFactory.getLogger(GestorRegistroActividad.class);

   // Tipos de operaciones predefinidos
   public static final String TIPO_OPERACION_SISTEMA = "SISTEMA";
   public static final String TIPO_OPERACION_USUARIO = "USUARIO";
   public static final String TIPO_OPERACION_ERROR = "ERROR_APLICACION";

   // Estados de operación
   public static final String ESTADO_OPERACION_EXITO = "EXITOSO";
   public static final String ESTADO_OPERACION_FALLO = "FALLIDO";

   /**
    * Registra un evento originado por el sistema.
    * @param accion Descripción breve de lo que hizo el sistema.
    * @param estado Resultado de la operación (EXITOSO/FALLIDO).
    * @param descripcion Detalles adicionales sobre el evento.
    */
   public static void registrarEventoDelSistema(String accion, String estado, String descripcion) {
       registrarActividad(TIPO_OPERACION_SISTEMA, accion, estado, descripcion);
   }

   /**
    * Registra un evento realizado por un usuario.
    * @param idUsuario Identificador del usuario.
    * @param accion Descripción de la acción del usuario.
    * @param estado Resultado de la operación (EXITOSO/FALLIDO).
    * @param descripcion Detalles adicionales.
    */
   public static void registrarEventoDelUsuario(String idUsuario, String accion, String estado, String descripcion) {
       String infoUsuario = "ID_Usuario: " + enmascararIdentificador(idUsuario) + ", " + descripcion;
       registrarActividad(TIPO_OPERACION_USUARIO, accion, estado, infoUsuario);
   }

   /**
    * Registra una excepción o error crítico.
    * @param accion Acción que precedió al error.
    * @param ex La excepción lanzada.
    * @param contexto Información contextual del error.
    */
   public static void registrarErrorCritico(String accion, Throwable ex, String contexto) {
       String detallesError = String.format("Excepción: %s, Mensaje: %s, Contexto: %s",
                                           ex.getClass().getName(), ex.getMessage(), contexto);
       registrarActividad(TIPO_OPERACION_ERROR, accion, ESTADO_OPERACION_FALLO, detallesError);
       REGISTRADOR.error("Error crítico durante la operación '{}': {}", accion, ex.getMessage(), ex); // Log full stack trace
   }
   
   /**
    * Método central para formatear y emitir el log.
    * @param tipo Tipo de la operación (SISTEMA, USUARIO, ERROR_APLICACION).
    * @param accion Descripción de la acción.
    * @param estado Estado de la operación (EXITOSO, FALLIDO).
    * @param detalles Información adicional.
    */
   private static void registrarActividad(String tipo, String accion, String estado, String detalles) {
       String mensajeFormateado = String.format(
           "TIPO=%s | ACCION=%s | ESTADO=%s | DETALLES=%s",
           tipo, accion, estado, detalles
       );

       if (TIPO_OPERACION_ERROR.equals(tipo)) {
           REGISTRADOR.error(mensajeFormateado);
       } else if (ESTADO_OPERACION_FALLO.equals(estado)) {
           REGISTRADOR.warn(mensajeFormateado);
       } else {
           REGISTRADOR.info(mensajeFormateado);
       }
   }

   /**
    * Enmascara el ID de usuario para proteger la privacidad.
    * @param id El ID original del usuario.
    * @return Un ID de usuario enmascarado.
    */
   private static String enmascararIdentificador(String id) {
       if (id == null || id.isEmpty()) {
           return "N/A";
       }
       if (id.length() > 6) {
           return id.substring(0, 3) + "***" + id.substring(id.length() - 3);
       }
       return id;
   }
}
   

La clase GestorRegistroActividad centraliza la lógica de logging. Sus métodos estáticos registrarEventoDelSistema, registrarEventoDelUsuario y registrarErrorCritico facilitan la captura de logs, mientras que registrarActividad aplica el formato y nivel adecuados.

4.2 Ejemplo de Uso en un Sistema de Control de Tráfico

package com.sistema.transporte;

import com.sistema.transporte.actividad.GestorRegistroActividad;

public class SistemaControlTrafico {
   private boolean serviciosActivos = false;
   private String idUsuarioActual;

   public void iniciarServicios() {
       System.out.println("Iniciando componentes del sistema de tráfico...");
       try {
           Thread.sleep(500); // Simulación de operaciones de inicio
           serviciosActivos = true;
           GestorRegistroActividad.registrarEventoDelSistema(
               "INICIO_SERVICIOS", GestorRegistroActividad.ESTADO_OPERACION_EXITO, "Componentes principales iniciados.");
           System.out.println("Sistema de tráfico en línea.");
       } catch (InterruptedException e) {
           Thread.currentThread().interrupt();
           GestorRegistroActividad.registrarErrorCritico(
               "INICIO_SERVICIOS", e, "Interrupción durante el inicio del sistema.");
           throw new RuntimeException("Error al iniciar el sistema.", e);
       } catch (Exception e) {
           GestorRegistroActividad.registrarErrorCritico(
               "INICIO_SERVICIOS", e, "Fallo inesperado al iniciar los componentes.");
           throw new RuntimeException("Fallo al iniciar el sistema.", e);
       }
   }

   public void detenerServicios() {
       System.out.println("Deteniendo servicios del sistema de tráfico...");
       try {
           if (serviciosActivos) {
               Thread.sleep(300); // Simulación de operaciones de detención
               serviciosActivos = false;
               GestorRegistroActividad.registrarEventoDelSistema(
                   "DETENCION_SERVICIOS", GestorRegistroActividad.ESTADO_OPERACION_EXITO, "Servicios del sistema apagados.");
               System.out.println("Sistema de tráfico fuera de línea.");
           } else {
                GestorRegistroActividad.registrarEventoDelSistema(
                   "DETENCION_SERVICIOS", GestorRegistroActividad.ESTADO_OPERACION_FALLO, "Intento de detención de un sistema ya inactivo.");
               System.out.println("El sistema ya estaba inactivo.");
           }
       } catch (InterruptedException e) {
           Thread.currentThread().interrupt();
           GestorRegistroActividad.registrarErrorCritico(
               "DETENCION_SERVICIOS", e, "Interrupción durante la detención del sistema.");
           throw new RuntimeException("Error al detener el sistema.", e);
       } catch (Exception e) {
           GestorRegistroActividad.registrarErrorCritico(
               "DETENCION_SERVICIOS", e, "Fallo inesperado al detener los servicios.");
           throw new RuntimeException("Fallo al detener el sistema.", e);
       }
   }

   public void autenticarUsuario(String id) {
       System.out.println("Usuario " + id + " intentando autenticación...");
       try {
           Thread.sleep(200); // Simulación de autenticación exitosa
           this.idUsuarioActual = id;
           GestorRegistroActividad.registrarEventoDelUsuario(
               id, "AUTENTICACION", GestorRegistroActividad.ESTADO_OPERACION_EXITO, "Acceso concedido.");
           System.out.println("Usuario " + id + " autenticado con éxito.");
       } catch (InterruptedException e) {
           Thread.currentThread().interrupt();
           GestorRegistroActividad.registrarErrorCritico(
               "AUTENTICACION_USUARIO", e, "Interrupción durante la autenticación de " + id + ".");
           throw new RuntimeException("Error de autenticación.", e);
       } catch (Exception e) {
            GestorRegistroActividad.registrarErrorCritico(
               "AUTENTICACION_USUARIO", e, "Fallo en la autenticación para " + id + ".");
           throw new RuntimeException("Fallo en la autenticación.", e);
       }
   }

   public void cerrarSesion(String id) {
       System.out.println("Usuario " + id + " cerrando sesión...");
       if (idUsuarioActual != null && idUsuarioActual.equals(id)) {
           idUsuarioActual = null;
           GestorRegistroActividad.registrarEventoDelUsuario(
               id, "CERRAR_SESION", GestorRegistroActividad.ESTADO_OPERACION_EXITO, "Sesión finalizada correctamente.");
           System.out.println("Sesión del usuario " + id + " cerrada.");
       } else {
            GestorRegistroActividad.registrarEventoDelUsuario(
               id, "CERRAR_SESION", GestorRegistroActividad.ESTADO_OPERACION_FALLO, "Intento de cerrar sesión de usuario no autenticado o incorrecto.");
           System.out.println("No se pudo cerrar sesión para el usuario " + id + ".");
       }
   }

   public void simularIncidenteVial() {
       System.out.println("Simulando un incidente de tráfico mayor...");
       try {
           if (!serviciosActivos) {
                GestorRegistroActividad.registrarEventoDelSistema(
                   "SIMULACION_INCIDENTE", GestorRegistroActividad.ESTADO_OPERACION_FALLO, "No se puede simular incidente con sistema inactivo.");
               System.out.println("Sistema inactivo. No se puede simular.");
               return;
           }
           Thread.sleep(1500); // Simulación de un evento que podría generar una alerta
           GestorRegistroActividad.registrarEventoDelSistema(
               "ALERTA_TRAFICO", GestorRegistroActividad.ESTADO_OPERACION_EXITO, "Congestión detectada en sector Y.");
           System.out.println("Incidente vial simulado y registrado.");
       } catch (InterruptedException e) {
           Thread.currentThread().interrupt();
           GestorRegistroActividad.registrarErrorCritico(
               "SIMULACION_INCIDENTE", e, "Interrupción durante la simulación de incidente.");
           throw new RuntimeException("Error en simulación.", e);
       } catch (Exception e) {
           GestorRegistroActividad.registrarErrorCritico(
               "SIMULACION_INCIDENTE", e, "Fallo inesperado durante la simulación.");
           throw new RuntimeException("Fallo en simulación.", e);
       }
   }
}
   

La clase SistemaControlTrafico simula las operaciones principales, utilizando el GestorRegistroActividad para documentar cada evento. Esto garantiza que las operaciones críticas del sistema, las acciones de usuario y los errores sean adecuadamente registrados.

4.3 Verificación del Registro de Actividad

package com.sistema.transporte;

import com.sistema.transporte.SistemaControlTrafico;

public class PruebaSistemaTrafico {
   public static void main(String[] args) {
       SistemaControlTrafico miSistema = new SistemaControlTrafico();
       String idPrueba = "admin_001";

       System.out.println("\n--- Iniciando secuencia de prueba ---");

       // 1. Iniciar sistema
       miSistema.iniciarServicios();

       // 2. Autenticar usuario
       miSistema.autenticarUsuario(idPrueba);

       // 3. Simular un incidente
       miSistema.simularIncidenteVial();

       // 4. Cerrar sesión
       miSistema.cerrarSesion(idPrueba);

       // 5. Detener sistema
       miSistema.detenerServicios();
       
       System.out.println("\n--- Secuencia de prueba finalizada ---");
   }
}
   

Esta clase PruebaSistemaTrafico ejecuta una secuencia de operaciones, generando registros que pueden ser verificados en la consola o en los archivos de log configurados.

5. Prácticas Recomendadas para el Logging en Sistemas de Transporte

5.1 Estandarización del Formato de Logs

Un formato consistente es fundamental para la parseabilidad y el análisis. Un ejemplo de formato ideal sería:


[2023-10-10 14:30:45.123] [INFO ] TIPO=SISTEMA | ACCION=INICIO_SERVICIOS | ESTADO=EXITOSO | DETALLES=Componentes principales iniciados.
[2023-10-10 14:31:20.456] [INFO ] TIPO=USUARIO | ACCION=AUTENTICACION | ESTADO=EXITOSO | DETALLES=ID_Usuario: adm***001, Acceso concedido.
[2023-10-10 14:32:15.789] [WARN ] TIPO=SISTEMA | ACCION=ALERTA_TRAFICO | ESTADO=EXITOSO | DETALLES=Congestión detectada en sector Y.
[2023-10-10 14:33:00.012] [ERROR] TIPO=ERROR_APLICACION | ACCION=DETENCION_SERVICIOS | ESTADO=FALLIDO | DETALLES=Excepción: java.lang.RuntimeException, Mensaje: Error al detener el sistema., Contexto: Fallo inesperado al detener los servicios.
   

Cada entrada de log debe contener una marca de tiempo, nivel, tipo de operación, acción, estado y detalles relevantes.

5.2 Asignación Estratégica de Niveles de Logging

La tabla siguiente sugiere una asignación de niveles de log para diefrentes tipos de eventos:

Tipo de Operación Nivel de Log Descripción
Arranque/Parada del Sistema INFO Cambios de estado importantes.
Inicio/Cierre de Sesión de Usuario INFO Interacciones clave de usuarios.
Simulación/Detección de Incidentes INFO Eventos que alteran el flujo normal.
Excepciones del Sistema ERROR Fallos críticos que requieren atención inmediata.
Operaciones Fallidas (no críticas) WARN Problemas potenciales o errores recuperables.

Una clasificación precisa evita la saturación de logs y asegura que los problemas graves sean visibles.

5.3 Gestión de Rotación y Almacenamiento de Logs

La configuración de la rotación es esencial para controlar el tamaño de los archivos de log y optimizar el uso del disco. Un ejemplo con Logback:


<!-- logback.xml: Configuración para rotación de archivos -->
<appender name="ARCHIVO_ROTABLE" class="ch.qos.logback.core.rolling.RollingFileAppender">
   <file>logs/registro-eventos.log</file>
   <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
       <fileNamePattern>logs/registro-eventos.%d{yyyy-MM-dd}.gz</fileNamePattern>
       <maxHistory>60</maxHistory> <!-- Mantener logs de los últimos 60 días -->
       <totalSizeCap>1GB</totalSizeCap> <!-- Límite total de 1GB para logs históricos -->
   </rollingPolicy>
   <encoder>
       <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
   </encoder>
</appender>
   

<rollingPolicy> define las reglas de rotación, con fileNamePattern para el nombrado de archivos, maxHistory para el periodo de retención y totalSizeCap para un límite de almacenamiento global.

5.4 Seguridad y Privacidad en los Logs

Los logs de sistemas de transoprte pueden contener información sensible, como IDs de usuario o datos de ubicación. Es crucial implementar mecanismos para proteger esta información.


// Fragmento de la clase GestorRegistroActividad para enmascaramiento
private static String enmascararIdentificador(String id) {
   if (id == null || id.isEmpty()) {
       return "N/A";
   }
   // Ejemplo: Reemplazar el centro del ID con asteriscos
   if (id.length() > 6) {
       return id.substring(0, 3) + "***" + id.substring(id.length() - 3);
   }
   return id; // IDs cortos no se enmascaran, o se podría aplicar otro método.
}

// Uso en el método de logging de usuario:
public static void registrarEventoDelUsuario(String idUsuario, String accion, String estado, String descripcion) {
   String infoUsuario = "ID_Usuario: " + enmascararIdentificador(idUsuario) + ", " + descripcion;
   registrarActividad(TIPO_OPERACION_USUARIO, accion, estado, infoUsuario);
}
   

La función enmascararIdentificador ilustra cómo se puede proteger la privacidad de un ID de usuario, reemplazando partes del mismo con caracteres genéricos. Esta práctica es indispensable para cumplir con las normativas de protección de datos.

6. Caso Práctico: Sistema de Despacho de Metro Urbano

Contexto del Caso
  • Sistema: Plataforma de despacho para una red de metro en una gran ciudad.
  • Problema: Fallos intermitentes no documentados, incremento de quejas de usuarios.
  • Objetivo: Integrar un sistema de logging para facilitar la detección de anomalías.
Solución Implementada
  1. Selección de Framework: SLF4J con Logback debido a su balance entre rendimiento y flexibilidad.
  2. Desarrollo del Gestor: Creación de GestorRegistroActividad para una interfaz de logging unificada.
  3. Instrumentación: Incorporación de llamadas al gestor en puntos estratégicos (inicio/paro del sistema, acciones de operadores, errores en módulos críticos).
  4. Políticas de Retención: Configuración de Logback para rotación diaria y compresión, manteniendo 60 días de historial.
  5. Protección de Datos: Implementación de enmascaramiento para identificadores de usuarios y datos de ruta sensibles.
Resultados Obtenidos
  • Reducción del Tiempo de Diagnóstico: De un promedio de 4 horas a menos de 30 minutos.
  • Agilización en la Atención de Quejas: De 2 horas a aproximadamente 15 minutos.
  • Mejora en la Estabilidad del Sistema: Un incremento estimado del 30% en la fiabilidad operativa.

El logging dejó de ser una tarea secundaria para convertirse en una herramienta vital de gestión y seguridad.

7. Errores Comunes y Estrategias para Evitarlos

Trampa 1: Exceso de Logging

Problema: Generación excesiva de logs, resultando en archivos voluminosos que afectan el rendimiento y la facilidad de análisis.

Solución: Refinar los niveles de log para registrar únicamente la información verdaderamente relevante y configurar filtros específicos para evitar la verbosidad.

Trampa 2: Formato Inconsistente

Problema: Logs con formatos variados, lo que complica su procesamiento automatizado y la lectura manual.

Solución: Adherirse a un formato de log estándar en toda la aplicación, idealmente centralizado a través de un gestor de logging o una biblioteca común.

Trampa 3: Falta de Seguridad en los Datos de Log

Problema: Exposición de información personnel o confidencial en los logs, incumpliendo normativas de privacidad.

Solución: Implementar técnicas de enmascaramiento o anonimización para datos sensibles como IDs de usuario, direcciones IP, o coordenadas geográficas.

Trampa 4: Gestión Inadecuada de la Rotación de Logs

Problema: Archivos de log que crecen indefinidamente, agotando el espacio de almacenamiento y dificultando su manipulación.

Solución: Configurar políticas de rotación de logs basadas en tiempo o tamaño, junto con compresión y retención limitadas, utilizando las capacidades del framework de logging.

Un sistema de transporte debe ser transparente y auditable, no una "caja negra". El registro de operaciones es el "rastro digital" que asegura su buen funcionamiento y la confianza del público.

Etiquetas: java logging slf4j logback log4j2

Publicado el 6-28 03:04