← Calidad, observabilidad y documentación

Sesión 45 · Semana 23

Estrategia de pruebas y diagnóstico

Proyecto compartido. En el taller de Intermodular que abre esta semana has trabajado verificar archivos y efectos externos. En Servidor continúas la implementación del mismo producto.

Se explica

25 minutos · explicación y demostración

El producto funciona, pero necesitas saber qué protegen sus pruebas y cómo investigar un fallo. Cobertura indica qué código se ejecutó, no si está bien comprobado. Un identificador de correlación permite reconocer en los logs los mensajes de una misma petición.

La falacia de la cobertura: Cantidad frente a Calidad

En muchas empresas se impone un objetivo numérico ciego: «Todo el código debe tener al menos un 80 % de cobertura de tests».

Cumplir ese número es fácil y peligroso:

  • Puedes escribir un test que invoque un método con datos ideales, no añada ninguna aserción (assertNotNull, assertEquals) y el reporte de cobertura marcará el 100 % de esas líneas en verde.
  • La cobertura de líneas solo mide si el compilador pasó por esa instrucción; no mide si el test verificó el resultado, si probó valores nulos ni si comprobó qué pasa cuando la base de datos o la red fallan.

La ley de los casos límite

El código no se rompe en el camino feliz; se rompe en las fronteras.

Una suite de pruebas profesional no busca probar mil veces lo evidente. Busca probar con rigor las condiciones de frontera: valores cero o negativos, cadenas vacías, desbordamientos de longitud, caracteres no ASCII, duplicados en concurrencia y excepciones transaccionales.

La Pirámide de Pruebas en Spring Boot

Para que una batería de pruebas sea rápida, mantenible y fiable, se organiza siguiendo la Pirámide de Pruebas:

La Pirámide de Pruebas en el ecosistema Spring
  1. Base: Tests Unitarios puros (JUnit 5 + Mockito) · Ejecución en milisegundos
  2. Corte Web: @WebMvcTest (Rutas, validación, HTTP, Spring Security)
  3. Corte Datos: @DataJpaTest (Consultas SQL, repositorios, constraints de DB)
  4. Cima: @SpringBootTest (Integración de extremo a extremo con servidor real)
  1. Tests Unitarios puros (Base): No levantan el contexto de Spring. Mockean las dependencias con Mockito (@Mock, @InjectMocks). Prueban la lógica matemática, los cálculos de negocio y los adaptadores de integración en milisegundos.
  2. Tests de Corte (Slice Tests): Levantan solo un fragmento del framework:
    • @WebMvcTest: Arranca solo controladores, serialización Jackson y filtros de seguridad.
    • @DataJpaTest: Arranca solo Hibernate, entidades y repositorios contra una base de datos de pruebas.
  3. Tests de Integración completa (Cima): Levantan la aplicación completa con @SpringBootTest(webEnvironment = RANDOM_PORT). Son lentos y pesados; se reservan para verificar los 3 o 4 flujos de negocio más críticos del sistema.

La amnesia del servidor en producción

Cuando desarrollas en tu portátil, si algo falla miras la terminal de IntelliJ o VS Code y ves la excepción de inmediato.

En producción la realidad es muy distinta:

  • La aplicación corre en un contenedor Docker o en un servidor Linux en la nube a cientos de kilómetros.
  • Un usuario llama diciendo: «Hace 10 minutos la web me dio un error al guardar una factura».
  • Si tu código no registró trazas útiles con contexto (quién era, qué identificadores envió, qué falló), no puedes hacer nada más que conjeturas.

Por qué System.out.println() está prohibido

System.out no es observabilidad: es ruido incontrolado.

1. Es una operación síncrona bloqueante que ralentiza los hilos de Tomcat.
2. No incluye marcas temporales, nombre de clase ni identificador de hilo.
3. No se puede desactivar o filtrar por gravedad sin recompilar el código.
4. No permite escribir en archivos rotativos ni exportar a sistemas centralizados (Elasticsearch, Grafana Loki).

Los 5 niveles estándar de log

En Spring Boot utilizamos la interfaz SLF4J respaldada por el motor Logback:

private static final Logger log = LoggerFactory.getLogger(MiServicio.class);
Nivel de log Cuándo se utiliza Ejemplo en nuestro proyecto
ERROR El sistema no pudo completar una operación requerida y necesita atención técnica. Base de datos inaccesible, fallo de escritura en disco, error 500 no controlado.
WARN Ocurrió una anomalía pero el sistema pudo recuperarse o degradar el servicio. Timeout con API de Open-Meteo aplicando degradación, token expirado, intento de acceso sin rol.
INFO Hitos relevantes del ciclo de vida normal de la aplicación. Arranque del sistema, proyecto creado, tarea asignada, fichero subido con éxito.
DEBUG Información detallada de diagnóstico útil para desarrolladores. Parámetros recibidos en DTO, tiempo de ejecución de una consulta, cabeceras procesadas.
TRACE Inspección forense extrema paso a paso (muy ruidoso). Volcado byte a byte de tramas de red o inicialización interna de beans del framework.

Se trabaja

140 minutos · implementación guiada sobre el proyecto propio

Paso 1 · Retomar el proyecto y preparar la comprobación

  1. Ejecuta los tests actuales y abre el servicio con reglas más relevantes. Enumera casos de negocio que quedarían sin detectar si el código estuviera mal.
  2. Localiza pom.xml, la configuración de logs y los tests. El informe de JaCoCo se generará a partir de la ejecución indicada en el taller.
  3. Prepara dos peticiones distinguibles y un fallo de desarrollo. Los usarás para comprobar que el identificador permite seguir cada recorrido.

Paso 2 · Auditoría de cobertura con JaCoCo y tests parametrizados

Vamos a ampliar las pruebas que ya existen. JaCoCo mide qué código se ejecuta; no decide si las aserciones comprueban el comportamiento correcto.

  1. Añade este plugin dentro de build/plugins en pom.xml, junto al plugin de Spring Boot, y sincroniza Maven:
<plugin>
    <groupId>org.jacoco</groupId>
    <artifactId>jacoco-maven-plugin</artifactId>
    <version>0.8.11</version>
    <executions>
        <execution><goals><goal>prepare-agent</goal></goals></execution>
        <execution>
            <id>report</id><phase>verify</phase>
            <goals><goal>report</goal></goals>
        </execution>
    </executions>
</plugin>
  1. Ejecuta .\mvnw.cmd verify en PowerShell (./mvnw verify en Linux/macOS). Si falla un test previo, corrígelo antes de analizar cobertura. Abre target/site/jacoco/index.html y localiza una clase de tu servicio y una regla aún sin comprobar.
  2. Crea src/test/java/com/ejemplo/gestor/ProyectoBoundaryTest.java para probar el ProyectoRequest(nombre, descripcion) usado en la sesión 31. Si tu DTO ya tiene más componentes, conserva el mismo caso y rellena los restantes con datos válidos. No cambies el DTO del producto para encajar el test.
package com.ejemplo.gestor;

import com.ejemplo.gestor.dto.ProyectoRequest;
import jakarta.validation.Validation;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.NullAndEmptySource;
import org.junit.jupiter.params.provider.ValueSource;
import static org.junit.jupiter.api.Assertions.assertTrue;

class ProyectoBoundaryTest {
    @ParameterizedTest
    @NullAndEmptySource
    @ValueSource(strings = {"   ", "\t", "\n"})
    void nombreVacioIncumpleLaValidacion(String nombre) {
        try (var factory = Validation.buildDefaultValidatorFactory()) {
            var errores = factory.getValidator().validate(
                new ProyectoRequest(nombre, "Descripción válida"));
            assertTrue(errores.stream().anyMatch(error ->
                error.getPropertyPath().toString().equals("nombre")));
        }
    }

    @ParameterizedTest
    @ValueSource(strings = {"Portal", "Gestor de préstamos", "Reservas de aula"})
    void nombreValidoNoProduceErrores(String nombre) {
        try (var factory = Validation.buildDefaultValidatorFactory()) {
            var errores = factory.getValidator().validate(
                new ProyectoRequest(nombre, "Descripción válida"));
            assertTrue(errores.isEmpty());
        }
    }
}

@ParameterizedTest ejecuta el método una vez por dato. Validator comprueba las anotaciones del DTO directamente: no arranca HTTP ni llama al servicio. No esperes que una llamada Java normal al servicio active automáticamente @Valid del controlador.

  1. Ejecuta solo esta clase con .\mvnw.cmd test "-Dtest=ProyectoBoundaryTest". Retira temporalmente @NotBlank de nombre para comprobar que los casos nulos o en blanco detectan la regresión; restáurala y deja los tests en verde.
  2. Para las coordenadas que añadiste en la sesión 41, prepara por separado latitud (-90 a 90) y longitud (-180 a 180). Comprueba cada límite aceptado y un valor justo fuera. No uses el límite de longitud como si fuera el de latitud. Añade estas entradas al test de tu DTO real, conservando válidos todos los otros campos.
  3. Reutiliza el test de clonación de la sesión 25 para verificar rollback. Prepara el proyecto original con sus tareas en gestor_test, llama al servicio inyectado por Spring y provoca el fallo controlado usado en aquel taller. El test que observa el rollback no debe envolver esa llamada en su propia transacción: comprueba desde una transacción posterior que no quedaron filas nuevas. Retira el fallo de producción y conserva el caso mediante el doble de prueba correspondiente.
  4. Clasifica en el análisis de las pruebas las evidencias reales: DTO/validación, servicio/reglas, repositorio/SQL, controlador/HTTP y seguridad. Para cada regla pendiente escribe primero entrada, resultado esperado y capa responsable; después añade la prueba. Repite verify y compara qué regla ha quedado cubierta, además del porcentaje.

Paso 3 · Ejecutar y analizar el informe JaCoCo

Ejecuta el ciclo de verificación de Maven:

./mvnw clean verify jacoco:report
  1. Localiza el informe generado: Entra en la carpeta target/site/jacoco/ y abre el archivo index.html en tu navegador.
  2. Inspecciona las columnas:
    • Element: Paquetes y clases del proyecto.
    • Line Coverage: Porcentaje de líneas ejecutadas.
    • Branch Coverage: Porcentaje de ramas lógicas (if, switch, operadores ternarios) recorridas.
  3. Análisis crítico:
    • Entra en ProyectoService o ClimaAdapter.
    • Si una línea aparece en amarillo, significa que solo se probó una rama del condicional (ej: se probó el caso if (true) pero nunca el caso else).
    • Comprueba cómo tras añadir los tests de casos límite, las ramas críticas pasan a color verde completo.

Paso 4 · Las cuatro familias de caso límite que siempre faltan

Cuando alguien dice «no sé qué más probar», casi siempre es porque solo ha pensado en valores razonables. Recorre estas cuatro listas sobre cada regla de negocio de tu aplicación y aparecerán los huecos solos:

1 · Los bordes de un rango
Si el presupuesto máximo son 150.000 €, hay que probar 149.999, 150.000 y 150.001. El error de programación más común del mundo es confundir > con >=, y solo el valor exacto del borde lo detecta. Lo mismo con longitudes: un campo de 3 a 80 caracteres se prueba con 2, 3, 80 y 81.
2 · El vacío y la ausencia
No son lo mismo y se comportan distinto: una cadena vacía, una cadena de espacios, un null y un campo que ni siquiera consta en el JSON. En las colecciones, el caso equivalente es la lista vacía, que invalida cualquier cálculo de media o de máximo.
3 · Lo que rompe el formato
Acentos y eñes, emojis, comillas simples dentro de un texto, cadenas de 10.000 caracteres, números negativos donde esperas positivos, y una fecha de fin anterior a la de inicio. Ninguno es rebuscado: todos llegan de usuarios reales.
4 · El orden y la repetición
Hacer dos veces la misma operación, deshacer algo que no se ha hecho, borrar un recurso que ya se borró, cerrar un proyecto ya cerrado. La pregunta en todos: ¿es un error, o debería ser idempotente y responder lo mismo?

Cuando varios de estos casos comparten la misma lógica, @ParameterizedTest con @ValueSource o @CsvSource te ahorra escribir el mismo test cinco veces cambiando un número.

Paso 5 · Añadir pruebas para las reglas y casos límite del servicio

  1. Haz primero el inventario, antes de escribir ningún test. Una tabla con una fila por regla de negocio de tu aplicación y tres columnas: qué la comprueba hoy, qué caso límite le falta, y qué pasaría en producción si fallase. Sin ese inventario, escribirás tests de lo que ya está probado, que es lo que hace subir la cobertura sin mejorar nada.
  2. Revisa en el informe JaCoCo qué métodos o ramas de TareaService tienen menos del 70 % de cobertura de ramas —no de líneas—, y crúzalo con tu inventario.
  3. Aplica las cuatro familias de arriba a las reglas de tarea: valores en el borde del presupuesto, título vacío o con solo espacios, título de 10.000 caracteres, fecha de fin anterior a la de inicio, transición de estado repetida y asignación a un usuario inactivo.
  4. Escribe los tests correspondientes, usando @ParameterizedTest donde se repita la lógica, y vuelve a compilar hasta que la cobertura de ramas supere el 80 %.
  5. Comprueba el rollback, que es el caso límite que casi nadie prueba: provoca un fallo a mitad de una operación que escribe en dos tablas y verifica en PostgreSQL que no ha quedado nada de la primera escritura. Una transacción que no revierte deja datos corruptos que ningún test de código detecta.
  6. Cierra con la pregunta honesta que ordena toda la unidad: ¿qué parte de tu aplicación sigue sin estar probada, y por qué has decidido dejarla así? Esa respuesta, escrita, vale más que un porcentaje: es el punto de partida de la sesión 46 y un apartado de la memoria de la UD12.
Cómo saber que lo has terminado
Tienes el inventario de reglas con sus huecos identificados; la cobertura de ramas de service supera el 80 %; cada rango numérico está probado en sus tres valores frontera; has comprobado al menos un rollback mirando la base de datos; y puedes decir qué queda sin probar y por qué.

Logging y depuración

Paso 6 · Correlación de peticiones con MDC (Mapped Diagnostic Context)

Cuando 50 usuarios lanzan peticiones simultáneas, las líneas de log de todos los hilos se intercalan en el mismo archivo.

Para no volverse loco buscando qué línea corresponde a qué petición, utilizamos el patrón Correlation ID mediante el MDC de SLF4J:

  • Al entrar una petición, un filtro genera un identificador único (ej: req-7f3a1b).
  • Se almacena en el hilo actual con MDC.put("correlationId", id).
  • Cada línea de log que se emita en cualquier servicio o repositorio imprimirá automáticamente ese identificador.
  • Se añade la cabecera X-Correlation-ID: req-7f3a1b en la respuesta HTTP para que el cliente pueda reportar ese código ante cualquier incidencia.

Paso 7 · Configuración de observabilidad con MDC

Crea config/CorrelationIdFilter.java y comprueba sus imports antes de configurar Logback. El filtro asigna el identificador al entrar y limpia el MDC en finally, incluso si se produce una excepción. Añade el archivo logback-spring.xml en resources sin duplicar otra configuración de logs activa. Después incorpora los mensajes a los métodos existentes del servicio; no reemplaces el servicio por un ejemplo que omite sus reglas. Haz dos peticiones y comprueba que sus identificadores son diferentes y no se mezclan.

package com.ejemplo.gestor.config;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.MDC;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;
import java.util.UUID;

@Component
@Order(Ordered.HIGHEST_PRECEDENCE) // Debe ejecutarse antes que cualquier filtro de seguridad o negocio
public class CorrelationIdFilter extends OncePerRequestFilter {

    public static final String CORRELATION_ID_HEADER = "X-Correlation-ID";
    public static final String MDC_KEY = "correlationId";

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain) throws ServletException, IOException {

        // 1. Si el cliente envía un ID lo respetamos; si no, generamos un UUID corto
        String correlationId = request.getHeader(CORRELATION_ID_HEADER);
        if (correlationId == null || correlationId.isBlank()) {
            correlationId = UUID.randomUUID().toString().substring(0, 8);
        }

        try {
            // 2. Registramos el identificador en el contexto de diagnóstico del hilo
            MDC.put(MDC_KEY, correlationId);

            // 3. Devolvemos la cabecera en la respuesta para trazabilidad del cliente
            response.setHeader(CORRELATION_ID_HEADER, correlationId);

            filterChain.doFilter(request, response);

        } finally {
            // 4. Limpieza obligatoria para evitar fugas en pools de hilos reutilizados
            MDC.remove(MDC_KEY);
        }
    }
}

Creamos el archivo src/main/resources/logback-spring.xml configurando consola y archivo rotativo con inclusión del [correlationId]:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <!-- Patrón de log con fecha, hilo, identificador MDC, nivel, logger y mensaje -->
    <property name="LOG_PATTERN"
              value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%X{correlationId}] %-5level %logger{36} - %msg%n"/>

    <!-- Salida por consola -->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>${LOG_PATTERN}</pattern>
        </encoder>
    </appender>

    <!-- Archivo rotativo: guarda un fichero por día, máximo 30 días o 100MB por archivo -->
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>logs/aplicacion.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>logs/aplicacion-%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
            <maxFileSize>10MB</maxFileSize>
            <maxHistory>30</maxHistory>
            <totalSizeCap>1GB</totalSizeCap>
        </rollingPolicy>
        <encoder>
            <pattern>${LOG_PATTERN}</pattern>
        </encoder>
    </appender>

    <!-- Niveles de log según entorno -->
    <root level="INFO">
        <appender-ref ref="CONSOLE"/>
        <appender-ref ref="FILE"/>
    </root>

    <!-- Nivel específico para nuestro paquete de negocio -->
    <logger name="com.ejemplo.gestor" level="DEBUG"/>
</configuration>

Privacidad y cumplimiento normativo (GDPR / OWASP)

Nunca registres contraseñas en claro, tokens JWT completos ni datos personales sensibles en los logs. Usa identificadores o máscaras:

package com.ejemplo.gestor.service;

import com.ejemplo.gestor.dto.ProyectoRequest;
import com.ejemplo.gestor.dto.ProyectoResponse;
import com.ejemplo.gestor.model.Proyecto;
import com.ejemplo.gestor.repository.ProyectoRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

@Service
public class ProyectoService {

    private static final Logger log = LoggerFactory.getLogger(ProyectoService.class);
    private final ProyectoRepository proyectoRepository;

    public ProyectoService(ProyectoRepository proyectoRepository) {
        this.proyectoRepository = proyectoRepository;
    }

    public ProyectoResponse crearProyecto(ProyectoRequest request, String usuario) {
        log.info("Solicitud de creación de proyecto '{}' por usuario '{}'", request.nombre(), usuario);

        if (proyectoRepository.existsByNombre(request.nombre())) {
            log.warn("Rechazada creación de proyecto: el nombre '{}' ya existe en base de datos", request.nombre());
            throw new IllegalArgumentException("Ya existe un proyecto con el nombre: " + request.nombre());
        }

        Proyecto nuevo = new Proyecto();
        nuevo.setNombre(request.nombre());
        nuevo = proyectoRepository.save(nuevo);

        log.debug("Proyecto persistido en base de datos con id={}", nuevo.getId());
        return new ProyectoResponse(nuevo.getId(), nuevo.getNombre());
    }
}

Paso 8 · Relacionar una petición fallida con sus mensajes de log

  1. Lanza una petición en Bruno:
    • Haz un POST /api/v1/proyectos intentando crear un proyecto con un nombre que ya existe.
    • En la respuesta de Bruno, revisa la pestaña Headers: Comprueba la cabecera devuelta: X-Correlation-ID: a1b2c3d4.
  2. Abre el archivo logs/aplicacion.log: Filtra las líneas buscando ese código:
    2026-09-03 08:30:15.120 [http-nio-8080-exec-3] [a1b2c3d4] INFO  c.e.p.s.ProyectoService - Solicitud de creación de proyecto 'Hospital Norte' por usuario 'admin'
    2026-09-03 08:30:15.135 [http-nio-8080-exec-3] [a1b2c3d4] WARN  c.e.p.s.ProyectoService - Rechazada creación de proyecto: el nombre 'Hospital Norte' ya existe en base de datos
    2026-09-03 08:30:15.142 [http-nio-8080-exec-3] [a1b2c3d4] INFO  c.e.p.e.GlobalExceptionHandler - Error 409 Conflict devuelto al cliente: Ya existe un proyecto con el nombre: Hospital Norte
  3. El poder de la correlación: Aunque hubiera 50 usuarios operando en paralelo, con un simple grep a1b2c3d4 aplicacion.log reconstruyes la película completa de esa llamada en 5 segundos sin mezclarte con las acciones de otros clientes.

Paso 9 · Integrar el Correlation ID en las respuestas RFC 7807

Abre el manejador creado en la UD3, aunque en otros ejemplos se llame GlobalExceptionHandler: no crees un segundo manejador. En el auxiliar que construye ProblemDetail añade problem.setProperty("correlationId", MDC.get("correlationId")), utilizando el nombre real de tu variable e importando org.slf4j.MDC. Reproduce un 409 y comprueba que el id del cuerpo coincide con el de la cabecera y los logs. Si el fallo ocurre en un filtro de seguridad, deberá utilizar su propio manejador de respuesta.

Paso 10 · Comprobar y registrar el resultado del proyecto

  1. Añade pruebas para los casos ausentes y verifica que fallan al introducir temporalmente el defecto que deberían detectar; restaura después el código correcto.
  2. Reproduce las dos peticiones y localiza cada una por su identificador. El mensaje público debe permitir relacionar el fallo sin revelar trazas internas ni datos sensibles.

Ampliación si has completado el trabajo

Primero termina y verifica los pasos anteriores. Estos retos profundizan en el mismo contenido; no sustituyen la entrega ni obligan a iniciar otro proyecto.

Reto · Umbrales de cobertura mínimos obligatorios en CI/CD

En proyectos profesionales se configura Maven para que la compilación falle automáticamente si un desarrollador introduce código nuevo sin tests suficientes.

Configura una regla de verificación en jacoco-maven-plugin:

  1. Añade una ejecución con el objetivo check en pom.xml.
  2. Establece un límite mínimo de cobertura de ramas (BRANCH) del 75 % a nivel de paquete de servicios (com.ejemplo.gestor.service.*).
  3. Comprueba que si borras un test crítico, ./mvnw verify termina con código de error y aborta el empaquetado del archivo JAR.

Formato de entrega

Incluye esta explicación en el registro de la sesión dentro del repositorio de GitHub, junto al código y las comprobaciones. La entrega es el enlace al repositorio y al commit de la sesión.

Objetivo mínimoPlugin JaCoCo integrado en pom.xml e informe HTML generado con ./mvnw verify.
Si lo tienesTests parametrizados con @ParameterizedTest cubriendo valores límite y verificación de rollback.
RetoRegla obligatoria de umbral de cobertura (jacoco:check) bloqueando compilaciones sin tests.
Ver respuestas

1 · Mide si todas las posibles decisiones lógicas booleanas de una estructura condicional (ambas ramas de un if, todos los case de un switch) han sido ejecutadas y evaluadas en los tests.

2 · Porque los tests unitarios con Mockito se ejecutan en pocos milisegundos sin levantar el contenedor Spring ni la base de datos, permitiendo ciclos de feedback casi instantáneos.

3 · Inyecta automáticamente dos casos de prueba: un valor null y una cadena vacía ("") para verificar que el método receptor los gestiona adecuadamente.

4 · Que ante un error a mitad de una operación compuesta, todas las modificaciones previas se revierten (rollback), garantizando que o se guarda todo o no se guarda nada.

Reto · Enmascaramiento automático de datos sensibles

Diseña un filtro o conversor personalizado en Logback (PatternLayoutEncoder o CompositeConverter):

  1. Investiga cómo aplicar expresiones regulares en Logback para sustituir números de tarjetas bancarias o emails por valores enmascarados (ej: j***@empresa.com).
  2. Comprueba que si un programador despistado escribe log.info("Usuario: {}", usuario) la contraseña o datos protegidos nunca lleguen en texto plano al archivo de disco.
Objetivo mínimoSustitución de System.out por Logger de SLF4J y niveles clasificados con criterio.
Si lo tienesFiltro de CorrelationIdFilter con MDC y rotación de archivos en logback-spring.xml.
RetoInclusión de correlationId en respuestas RFC 7807 y enmascaramiento automático de datos sensibles.
Ver respuestas

1 · Porque Tomcat reutiliza hilos de su pool de trabajo; si no limpias el MDC, la siguiente petición procesada por ese mismo hilo heredaría el ID de la petición anterior causando contaminación de trazas.

2 · WARN indica una contingencia o comportamiento anómalo donde el sistema pudo seguir funcionando o aplicar una degradación; ERROR indica que una operación requerida falló de forma irrecuperable.

3 · Evita que el archivo de log crezca hasta agotar el disco duro de la máquina, comprime los históricos antiguos (.gz) y facilita las búsquedas delimitadas por fecha.

4 · Permite al ingeniero buscar directamente ese código alfanumérico en el archivo de logs y obtener exactamente las líneas de traza de esa petición aisladas de la concurrencia de otros usuarios.

Cierre

15 minutos · resultado comprobable y explicación individual

Al terminar la sesión:

La corrección tiene evidencia reproducible y los logs no exponen credenciales ni datos innecesarios.

Cada integrante explica una decisión del código apoyándose en una de las comprobaciones realizadas.