====== 09 - Logs e instrumentación ====== En 1.º DAW ya se usa el sistema de logs de Spring Boot: el **evento de log** con su **nivel**, la fachada **SLF4J**, la implementación **Logback** de serie y el nivel por paquete en ''application.properties''. Esas bases bastaban con la aplicación en el equipo local: la consola lo mostraba todo y cada fallo se reproducía a mano. Una vez desplegada, la misma aplicación corre en un servidor o contenedor donde la consola es efímera, el acceso es remoto y las peticiones se solapan: el registro pasa a ser la principal herramienta para saber qué ocurre y para reconstruir después qué le sucedió a una petición concreta. ===== 1. El log en producción ===== El cambio de contexto cambia la función del log: ya no es una versión mejorada del ''printf'' de desarrollo, sino una fuente de información sobre un proceso que nadie está mirando en directo. El diagnóstico se hace después de los hechos, leyendo lo que la aplicación registró por el camino. ^ Condición del despliegue ^ Qué exige al registro ^ | El proceso muere o se reinicia y la consola desaparece | **Persistencia**: fichero con **retención** limitada (rotación) | | Peticiones concurrentes comparten la misma salida | **Correlación**: cada suceso lleva el contexto de la petición que lo originó | | El accidente depende del entorno (carga, datos, hora) | Sucesos cronológicos en formato estable: buscar, filtrar, comparar | Las preguntas que el log de producción debe responder no se conocen por adelantado: ''¿qué falló esta noche?'', ''¿qué hizo esta petición?'', ''¿cómo se comportó tras el despliegue?''. Responderlas exige conservar los sucesos con contexto suficiente y poder localizarlos. ===== 2. Configuración de la salida ===== ==== 2.1 Fichero y rotación ==== Con una propiedad el registro se guarda en fichero, pero uno que crece sin control acabaría llenando el disco. La **rotación** trocea el historial, comprime los ficheros cerrados y limita cuántos se conservan: logging.file.name=logs/bookstore.log logging.logback.rollingpolicy.max-file-size=10MB logging.logback.rollingpolicy.max-history=30 Al llegar al tamaño máximo, el fichero se archiva comprimido y se abre uno nuevo; solo se conservan los últimos 30. El nombre del fichero activo no cambia, así que las herramientas de lectura siempre consultan la misma ruta. Estas propiedades son de Spring Boot: las traduce a la configuración de la implementación (Logback), por eso viven bajo ''logging.logback.rollingpolicy''. Con otra implementación los equivalentes cambian de nombre; el concepto de rotación es el mismo. ==== 2.2 El nivel según el entorno ==== El nivel de cada logger se configura por paquete; los **perfiles** de Spring permiten que la misma aplicación registre de manera distinta según dónde corra: # application.properties — desarrollo: consola y máximo detalle propio logging.level.com.bookstore=DEBUG # application-prod.properties — producción: fichero y solo lo relevante logging.file.name=logs/bookstore.log logging.logback.rollingpolicy.max-file-size=10MB logging.logback.rollingpolicy.max-history=30 logging.level.com.bookstore=INFO logging.level.org.springframework=WARN Activar el perfil con ''spring.profiles.active=prod'' cambia destino y detalle sin recompilar. El detalle amplio (''DEBUG'') es una herramienta de desarrollo; en producción se reserva para ventanas de diagnóstico puntuales. ===== 3. Correlación de peticiones con MDC ===== En desarrollo las peticiones llegan una a una y se distinguen solas; en producción una ráfaga entrelaza en la salida los eventos de decenas de flujos distintos. Un mensaje ''Error al procesar el pedido'' aportaría bien poco contexto: ¿qué pedido, qué petición, qué cliente? La **correlación** consiste en que cada evento del flujo de una petición quede marcado con el identificador de esa petición. **Tomcat** atiende cada petición en un hilo de un pool (visto en 1.º DAW), así que durante la petición todos los logs del flujo salen del mismo hilo. Aprovechando esa correspondencia, **SLF4J** ofrece el **MDC** (*Mapped Diagnostic Context*): un mapa de contexto por hilo que el patrón de formato puede incluir en cada mensaje con ''%X{clave}''. El filtro situado delante del ''DispatcherServlet'' abre el flujo y prepara el contexto: @Component public class RequestTrackingFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { String requestId = UUID.randomUUID().toString().substring(0, 8); MDC.put("requestId", requestId); try { chain.doFilter(request, response); } finally { MDC.clear(); } } } Los eventos que el hilo emita durante la petición llevan el identificador, pero solo si el patrón lo declara: logging.pattern.level=%5p [%X{requestId}] 2026-10-21 14:07:21 INFO [3e9f2c8a] BookService - Pedido 42 cancelado @startuml participant "RequestTrackingFilter" as F participant "Controlador" as C participant "Servicio" as S database "Fichero de log" as L F -> C : petición HTTP\n(requestId en el MDC) C -> S : coordina el caso de uso S -> L : evento [3e9f2c8a] C -> L : evento [3e9f2c8a] S --> C : resultado C --> F : respuesta HTTP F -> F : el hilo vuelve al pool\n(finally limpia el MDC) @enduml El hilo no muere al terminar la petición: vuelve al pool y atenderá otra. Si el ''MDC'' no se limpia en el ''finally'', la segunda petición heredaría el identificador de la primera y la correlación se corrompería en silencio. El **filtro de autenticación** del tema 07 es el otro punto natural de correlación: una vez validado el token, el principal identificado entra también en el ''MDC'' y cada suceso del flujo lleva petición y usuario. Un límite que tener presente: el contexto pertenece al hilo, y en el procesamiento asíncrono del tema 08 el hilo cambia — el contexto no viaja solo; quien lanza el trabajo asíncrono debe propagarlo explícitamente si quiere mantener la correlación. ===== 4. Instrumentación: Actuator ===== Los logs solo dicen lo que el código escribió en cada mensaje. Quedan preguntas que el registro no cubre: ¿está sano el proceso?, ¿cuánta memoria consume?, ¿cuántas peticiones por segundo atiende? La **observabilidad** completa combina las tres ventanas: ^ Ventana ^ Pregunta que responde ^ | **Logs** | ¿Qué pasó, suceso a suceso? | | **Métricas** | ¿Cómo se comporta el sistema, agregado y a lo largo del tiempo? | | **Health** | ¿Está vivo y listo para recibir tráfico? | **Spring Boot Actuator** expone las ventanas de métricas y salud como endpoints operativos. La dependencia ''spring-boot-starter-actuator'' y la exposición se declaran en propiedades: management.endpoints.web.exposure.include=health,info,metrics { "status": "UP" } ^ Endpoint ^ Pregunta que responde ^ | ''/actuator/health'' | ¿Está operativa cada dependencia: base de datos, broker, disco? | | ''/actuator/info'' | ¿Qué versión está desplegada? | | ''/actuator/metrics'' | ¿Cuánta memoria, peticiones HTTP por código de estado, pool de conexiones? | Estos endpoints son operativos: exponen incluso el estado interno del sistema. En producción se mantienen al mínimo necesario y se quedan tras la misma autenticación que construye el tema 07, nunca abiertos al anonimato. ===== 5. La disciplina del registro ===== La configuración decide qué se guarda y dónde; la calidad del contenido distingue un log diagnóstico de una cascada de frases. Unas reglas de partida suficientes: * **El mensaje sirve para buscar, no para mirar**: incluye los identificadores que localizarán el suceso después (pedido, usuario, petición).\\ \\ * **Cero secretos**: contraseñas, tokens y cabeceras de autenticación nunca entran en el log; son lo más expuesto del sistema (temas 06 y 07).\\ \\ * **Nivel coherente**: ''INFO'' para los sucesos del negocio, ''WARN'' para lo recuperable, ''ERROR'' para lo que rompe la operación, con su traza completa.\\ \\ * **El contexto viaja en el ''MDC''**: el mensaje no repite petición ni usuario; están en el contexto y el patrón los imprime. En el tema siguiente la función en la nube cierra el círculo: en una **Lambda** el registro es prácticamente la única ventana de observación; el ''context'' que la plataforma entrega sirve de logger y su salida llega al servicio de logs de la nube (**CloudWatch**).