# Día 30: Observabilidad Completa: Grafana, Prometheus, Loki y OpenTelemetry / Tempo (LKA / LGTM)

> Los Tres Pilares: Métricas con Micrometer, Logs Estructurados JSON con Loki y Trazas Distribuidas con Tempo

- Plan: [JavaMastery](https://aprende-java.webs-economicas.es/), día 30 de 30
- Fase 8: Contenedores, Kubernetes / OpenShift y Observabilidad LKA / LGTM
- Dedicación: 4h
- Profundidad: visión de conjunto
- Revisado: 2026-10-29
- Versión interactiva: https://aprende-java.webs-economicas.es/dias/observabilidad-grafana-prometheus-opentelemetry/

## Al terminar este día sabrás

- Distinguir para qué sirven métricas, logs y trazas y cómo se correlacionan.
- Instrumentar métricas propias con Micrometer y exponerlas a Prometheus.
- Emitir logs JSON estructurados con identificador de traza para Loki.
- Seguir una petición entre servicios con OpenTelemetry y Tempo, y saltar entre señales en Grafana.


## Conceptos

### 1. Los Tres Pilares de la Observabilidad: Métricas, Logs y Trazas

**Métricas** (Prometheus): valores numéricos agregados a lo largo del tiempo (requests/sec, latencia p99, uso de CPU, conexiones JDBC libres). **Logs** (Loki): registros textuales enriquecidos con contexto de eventos ocurridos. **Trazas Distribuidas** (Tempo / Jaeger): el recorrido completo de una petición a través de múltiples microservicios con spans y tiempos de ejecución.

**Cómo funciona por dentro.**

Las métricas son valores numéricos agregados en el tiempo (peticiones por segundo, latencia p99, uso de memoria). Son baratas de almacenar, ideales para paneles y alertas, y responden «¿algo va mal?». Los logs son eventos discretos con detalle (qué pedido falló y por qué) y responden «¿qué pasó exactamente?». Las trazas siguen una petición a través de todos los servicios y operaciones que toca, con su duración, y responden «¿dónde se va el tiempo o dónde falla?».

Cada pilar por separado se queda corto. La potencia aparece al correlacionarlos con un identificador común, el trace id: una alerta de latencia lleva a una traza lenta, la traza señala el servicio y el tramo culpable, y sus logs explican el motivo.

El stack LGTM de Grafana cubre los tres con herramientas abiertas: Loki para logs, Grafana para visualizar, Tempo para trazas y Mimir o Prometheus para métricas. OpenTelemetry es el estándar para generar y transportar las señales sin atarse a un proveedor.

*Stack LGTM completo en local con un solo contenedor*

```yaml
# compose.yaml: Grafana + Loki + Tempo + Prometheus + colector OpenTelemetry
services:
  lgtm:
    image: grafana/otel-lgtm:latest
    ports:
      - "3000:3000"     # Grafana (admin / admin)
      - "4317:4317"     # OTLP gRPC
      - "4318:4318"     # OTLP HTTP: aquí envía la aplicación trazas, métricas y logs

# Con spring-boot-docker-compose en el classpath, Spring Boot 3.4+ arranca este compose
# al ejecutar la aplicación y configura automáticamente los endpoints OTLP.
```

- **En una línea:** `Métrica alerta en Grafana -> Clic en el TraceID en Tempo -> Abre directamente los logs correlacionados en Loki con el mismo TraceID.`
- **Por qué importa:** Permite diagnosticar incidentes en minutos: la métrica detecta la anomalía, la traza localiza el microservicio culpable y el log revela la causa raíz exacta.
- **Error típico:** Depender únicamente de logs en ficheros de texto dispersos entre 20 servidores o pods sin correlación centralizada.
- **Java 17 frente a 21:** Universal.
- **Se conecta con:** SRE, Grafana LGTM Stack, MTTR (Mean Time To Resolve)

### 2. Métricas con Micrometer y Prometheus en Spring Boot 3

Spring Boot Actuator integra Micrometer como fachada de métricas. Al añadir `micrometer-registry-prometheus`, expone el endpoint `/actuator/prometheus` con métricas de JVM (heap, GC pauses), servidor HTTP (`http.server.requests`), conexiones HikariCP (`hikaricp.connections.active`) y métricas de negocio personalizadas con `Counter`, `Timer` y `Gauge`.

**Cómo funciona por dentro.**

Micrometer es para las métricas lo que SLF4J para los logs: una fachada neutral. Instrumentas una vez y exportas a Prometheus, Datadog, OTLP u otros. Spring Boot ya instrumenta automáticamente peticiones HTTP, pool de conexiones, JVM, GC, caché, Kafka y más. Con `micrometer-registry-prometheus`, todo se publica en `/actuator/prometheus`, que Prometheus lee periódicamente.

Los tipos básicos: `Counter` (solo crece: pedidos creados), `Gauge` (valor que sube y baja: tamaño de una cola), `Timer` (duración y recuento: tiempo de procesar un pago) y `DistributionSummary` (distribución de valores que no son tiempo: importe de pedidos). Las etiquetas (tags) permiten desglosar: `pedidos_creados_total{canal="web"}`.

Para latencias, publica histogramas (`publishPercentileHistogram`) y calcula percentiles en Prometheus con `histogram_quantile`: a diferencia de percentiles calculados en cada instancia, se pueden agregar correctamente entre réplicas.

*Métricas de negocio con Micrometer*

```java
@Service
class PedidoMetricas {
    private final Counter creados;
    private final Timer procesado;
    private final AtomicInteger pendientes = new AtomicInteger();

    PedidoMetricas(MeterRegistry registry) {
        this.creados = Counter.builder("pedidos.creados")
            .description("Pedidos creados")
            .tag("canal", "web")                         // pocas combinaciones posibles
            .register(registry);
        this.procesado = Timer.builder("pedidos.procesado")
            .publishPercentileHistogram()                 // permite p95/p99 agregados entre pods
            .register(registry);
        Gauge.builder("pedidos.pendientes", pendientes, AtomicInteger::get).register(registry);
    }

    void registrarCreacion() { creados.increment(); pendientes.incrementAndGet(); }

    void procesar(Runnable trabajo) {
        procesado.record(trabajo);
        pendientes.decrementAndGet();
    }
}

// En Prometheus / Grafana:
//   rate(pedidos_creados_total[5m])
//   histogram_quantile(0.99, sum by (le) (rate(pedidos_procesado_seconds_bucket[5m])))
```

- **En una línea:** `Timer.Sample sample = Timer.start(registry);
// operación...
sample.stop(registry.timer("pedidos.procesados", "tipo", "express"));`
- **Por qué importa:** Prometheus hace scraping periódico de este endpoint y Grafana renderiza dashboards visuales en tiempo real con alertas automáticas.
- **Error típico:** Crear métricas personalizadas con etiquetas (tags) de alta cardinalidad (e.g. incluir el ID del usuario o el email en un tag), lo que satura la memoria de Prometheus.
- **Java 17 frente a 21:** Idéntico en 17 y 21.
- **Se conecta con:** PromQL, Grafana Dashboards, Alertmanager

### 3. Logs Estructurados JSON con Grafana Loki

Loki indexa metadatos y etiquetas (labels: `app=servicio-pedidos`, `env=prod`) en lugar del texto completo del log, haciéndolo extremadamente ligero y eficiente. Para aprovecharlo, Spring Boot debe formatear los logs en formato JSON estructurado usando `logstash-logback-encoder`, incluyendo automáticamente campos como `timestamp`, `level`, `traceId`, `spanId` y `message`.

**Cómo funciona por dentro.**

Un log en texto libre es cómodo de leer y muy incómodo de consultar a escala. Un log estructurado en JSON separa cada dato en un campo (`level`, `logger`, `message`, `traceId`, `pedidoId`), así que puedes filtrar y agregar sin expresiones regulares frágiles. Spring Boot 3.4 incorpora logging estructurado nativo con los formatos ECS, Logstash y GELF; en versiones anteriores se usa `logstash-logback-encoder`.

Loki indexa solo las etiquetas (labels) del flujo, no el contenido, lo que lo hace barato. Las etiquetas deben tener baja cardinalidad: aplicación, entorno, namespace, nivel. Los valores con muchísimas combinaciones (id de usuario, id de pedido, trace id) van dentro del JSON y se filtran en la consulta con LogQL.

Con Micrometer Tracing activo, Spring añade automáticamente `traceId` y `spanId` al MDC, así que cada línea de log queda enlazada con su traza.

*Logs JSON y una consulta LogQL*

```yaml
# application.yml (Spring Boot 3.4+)
logging:
  structured:
    format:
      console: ecs                    # JSON en formato Elastic Common Schema
  level:
    com.acme.taskflow: INFO

# Salida (una línea por evento; formateada aquí para leerla):
# {"@timestamp":"2026-03-01T10:15:30.123Z","log.level":"ERROR",
#  "service.name":"taskflow","log.logger":"c.a.t.pagos.PagoService",
#  "message":"Pago rechazado","traceId":"4bf92f3577b34da6a3ce929d0e0e4736",
#  "spanId":"00f067aa0ba902b7","pedidoId":"A-1029"}

# LogQL en Grafana: errores de pagos de la última hora para un pedido
#   {app="taskflow", env="prod"} | json | log_level="ERROR" | pedidoId="A-1029"
# Tasa de errores por logger
#   sum by (log_logger) (rate({app="taskflow"} | json | log_level="ERROR" [5m]))
```

- **En una línea:** `{"timestamp":"2026-10-28T10:00:00Z","level":"INFO","traceId":"a1b2c3d4","message":"Pedido creado con éxito","pedidoId":1234}`
- **Por qué importa:** Permite búsquedas potentes con LogQL en Grafana y correlación inmediata con trazas distribuidas.
- **Error típico:** Imprimir logs multilínea (stack traces de excepciones) en formato plano tradicional sin estructurar; Loki los interpreta como líneas inconexas separadas.
- **Java 17 frente a 21:** Idéntico en 17 y 21.
- **Se conecta con:** LogQL, Logback, Promtail / FluentBit

### 4. Trazabilidad Distribuida: Micrometer Tracing, OpenTelemetry y Grafana Tempo

Cuando una petición entra por el API Gateway, se le asigna un **Trace ID** único. Cada microservicio que participa en procesar la petición genera un **Span ID** subordinado y propaga el Trace ID en las cabeceras HTTP (`traceparent` del estándar W3C). Las librerías `micrometer-tracing-bridge-otel` envían las trazas a Grafana Tempo.

**Cómo funciona por dentro.**

Una traza representa el recorrido completo de una petición y está formada por spans: cada span es una operación con nombre, inicio, duración, atributos y un padre. La petición HTTP de entrada es el span raíz; la consulta SQL, la llamada a otro servicio o el envío a Kafka son spans hijos.

Para que la traza continúe entre servicios, el contexto (trace id y span id del padre) viaja en cabeceras con el estándar W3C Trace Context (`traceparent`). En Spring Boot 3, Micrometer Tracing con el puente de OpenTelemetry lo hace automáticamente en `RestClient`, `WebClient`, `KafkaTemplate` y los listeners, siempre que construyas los clientes a partir de los builders que inyecta Spring.

Las trazas se exportan por OTLP a un colector o directamente a Tempo. En producción no se guarda el 100 %: se configura una probabilidad de muestreo, o muestreo por cola en el colector para guardar siempre las trazas con errores o lentas.

*Trazas con OpenTelemetry y spans propios*

```java
// application.yml
// management:
//   tracing:
//     sampling:
//       probability: 0.1            # 10 % en producción, 1.0 en local
//   otlp:
//     tracing:
//       endpoint: http://otel-collector:4318/v1/traces

@Service
class FacturacionService {
    private final ObservationRegistry observaciones;
    private final RestClient pagos;

    FacturacionService(ObservationRegistry observaciones, RestClient.Builder builder) {
        this.observaciones = observaciones;
        this.pagos = builder.baseUrl("http://pagos").build();   // el builder ya propaga traceparent
    }

    Factura facturar(Pedido pedido) {
        // Una Observation genera a la vez un span y una métrica de duración
        return Observation.createNotStarted("facturacion.generar", observaciones)
            .lowCardinalityKeyValue("tipo", pedido.tipo())        // también va a la métrica
            .highCardinalityKeyValue("pedido.id", pedido.id())    // solo al span, nunca a la métrica
            .observe(() -> {
                var cobro = pagos.post().uri("/cobros").body(pedido).retrieve().body(Cobro.class);
                return Factura.de(pedido, cobro);
            });
    }
}
```

- **En una línea:** `<dependency><groupId>io.micrometer</groupId><artifactId>micrometer-tracing-bridge-otel</artifactId></dependency>`
- **Por qué importa:** Visualiza el diagrama de cascada (waterfall) de la petición completa: sabes exactamente si los 800ms de latencia se consumieron en la query SQL del microservicio de pagos o en la llamada a Kafka.
- **Error típico:** Hacer llamadas HTTP con `RestTemplate` o `WebClient` sin configurar el bean gestionado por Spring, perdiendo la propagación automática de las cabeceras de traza.
- **Java 17 frente a 21:** Java 21 con Virtual Threads requiere versiones recientes de Micrometer Tracing para propagación correcta.
- **Se conecta con:** W3C Trace Context, OpenTelemetry, Grafana Tempo

### 5. La Correlación Completa en Grafana: Del Dashboard a la Línea de Código

El verdadero poder de la observabilidad moderna (LKA / LGTM) reside en la interconexión: en un dashboard de Grafana, una alerta de latencia alta te lleva al trace en Tempo con un solo clic. En Tempo, el trace tiene un botón derivado que busca automáticamente en Loki todos los logs que tengan ese mismo `traceId`.

**Cómo funciona por dentro.**

El valor de tener las tres señales en Grafana está en saltar entre ellas sin copiar identificadores a mano. Los exemplars enlazan métricas con trazas: junto a un punto del histograma de latencia, Prometheus guarda el trace id de una petición concreta que cayó en ese rango, y Grafana lo muestra como un punto clicable que abre la traza en Tempo.

Desde una traza en Tempo, la integración traza-logs abre en Loki los logs filtrados por ese trace id. Y en Loki, un campo derivado detecta el `traceId` dentro del JSON y lo convierte en un enlace hacia Tempo. El flujo típico de diagnóstico: alerta de p99 alto, clic en un exemplar, la traza muestra que el tramo lento es una consulta SQL en el servicio de pagos, y sus logs muestran un bloqueo.

Para los paneles de servicio, el método RED (Rate, Errors, Duration: peticiones por segundo, tasa de errores y latencia) da una visión rápida y homogénea de cualquier servicio.

*Enlazar Loki y Tempo en el aprovisionamiento de Grafana*

```yaml
# grafana/provisioning/datasources/datasources.yaml
apiVersion: 1
datasources:
  - name: Prometheus
    type: prometheus
    uid: prom
    url: http://prometheus:9090
    jsonData:
      exemplarTraceIdDestinations:
        - name: trace_id             # nombre de la etiqueta del exemplar
          datasourceUid: tempo       # clic en el exemplar -> traza en Tempo

  - name: Loki
    type: loki
    uid: loki
    url: http://loki:3100
    jsonData:
      derivedFields:
        - name: TraceID
          matcherRegex: '"traceId":"(\w+)"'
          datasourceUid: tempo
          url: "$${__value.raw}"      # el traceId del log se convierte en enlace a Tempo

  - name: Tempo
    type: tempo
    uid: tempo
    url: http://tempo:3200
    jsonData:
      tracesToLogsV2:
        datasourceUid: loki
        filterByTraceID: true        # desde la traza, ver sus logs
```

- **En una línea:** `Grafana Explore: Métricas (Prometheus) -> Trace Waterfall (Tempo) -> Logs filtrados por traceId (Loki)`
- **Por qué importa:** Reduce el tiempo de diagnóstico de incidencias críticas de horas a menos de 3 minutos.
- **Error típico:** Configurar herramientas de observabilidad en silos separados con interfaces distintas sin correlación por TraceId.
- **Java 17 frente a 21:** Universal.
- **Se conecta con:** Data Links en Grafana, DevOps Culture, SLA / SLO


## Sesión de 90 minutos

### Bloque 1: Métricas de Spring Boot con Micrometer y Prometheus (0 - 25 min)

- **Qué estudiar:** Endpoint /actuator/prometheus, métricas JVM, Timers personalizados.
- **Pregunta a responder:** ¿Cómo instrumentar un método de negocio para medir su tiempo de ejecución p95?
- **Entregable:** Controlador con métrica personalizada de Micrometer expuesta en /actuator/prometheus.

### Bloque 2: Logs Estructurados JSON para Grafana Loki (25 - 50 min)

- **Qué estudiar:** Configuración de logback-spring.xml con encoder JSON, campos traceId y spanId.
- **Pregunta a responder:** ¿Por qué los logs estructurados facilitan las consultas en Grafana?
- **Entregable:** logback-spring.xml configurado emitiendo JSON por consola.

### Bloque 3: Trazas Distribuidas con OpenTelemetry y Tempo (50 - 80 min)

- **Qué estudiar:** micrometer-tracing-bridge-otel, exportador OTLP hacia Tempo, propagación W3C.
- **Pregunta a responder:** ¿Cómo viaja el traceparent en las cabeceras HTTP entre microservicios?
- **Entregable:** Microservicio enviando trazas a Grafana Tempo local con docker-compose.

### Cierre y Consolidación (80 - 90 min)

- **Qué estudiar:** La experiencia de depuración correlacionada en Grafana.
- **Pregunta a responder:** ¿Cuál es el flujo ideal para depurar una incidencia en producción?
- **Entregable:** Diagrama del flujo Métrica -> Traza -> Log guardado.


## Explícalo desde cero

### Por qué el Trace ID es el hilo conductor de toda la arquitectura de microservicios

- **Qué entender:** En un monolito puedes seguir el hilo con un debugger o un log secuencial. En 20 microservicios distribuidos, una petición de usuario se fragmenta en decenas de llamadas HTTP y mensajes Kafka asíncronos. El Trace ID es el pasaporte único que acompaña a la petición desde que entra al Gateway hasta que sale. Cualquier log o métrica sin Trace ID es un mensaje en una botella en medio del océano.
- **Qué suele explicarse mal:** Pensar que observabilidad es solo "poner Grafana para ver gráficos bonitos de CPU". La CPU es solo un síntoma; las trazas correlacionadas con logs dan la causa exacta.
- **Ejemplo sencillo:** El Trace ID es como el número de seguimiento de un paquete de Amazon: te dice en qué camión está, en qué almacén se detuvo y cuánto tiempo estuvo en cada parada.
- **Señal de alerta:** Si tienes que pedirle al usuario su hora exacta para buscar en los logs qué pasó con su petición.


## Preguntas

### Nivel 1 (Básico): ¿Cuál es la diferencia entre un Trace ID y un Span ID en OpenTelemetry?

El Trace ID representa el viaje completo de la petición a través de todo el sistema distribuido (es común a todos los microservicios involucrados). El Span ID representa una operación individual y acotada dentro de un servicio concreto (e.g. una consulta SQL o una llamada HTTP específica).

**Respuesta razonada:** El Trace ID identifica la traza completa, es decir, el recorrido de una petición de principio a fin a través de todos los servicios. Es un identificador de 128 bits (32 caracteres hexadecimales) que se genera en el primer servicio y se propaga sin cambios a todos los demás.

El Span ID identifica una única operación dentro de esa traza (64 bits, 16 caracteres): la petición HTTP en el gateway, la consulta SQL en un servicio, la publicación en Kafka. Cada span guarda además el id de su padre, lo que permite reconstruir el árbol de llamadas y ver qué operación tardó cuánto. En la cabecera `traceparent` viajan ambos: el trace id común y el span id del padre.

### Nivel 2 (Intermedio): ¿Por qué no se debe incluir el ID de usuario o el email en las etiquetas (tags) de una métrica de Micrometer / Prometheus?

Problema de alta cardinalidad: cada combinación única de etiquetas genera una nueva serie temporal en la base de datos de Prometheus. Con miles de usuarios, Prometheus consumirá gigabytes de RAM y colapsará. Los identificadores únicos deben ir en los Logs estructurados o en los atributos de las Trazas, nunca en las métricas agregadas.

**Respuesta razonada:** Por la cardinalidad. Cada combinación distinta de valores de etiquetas crea una serie temporal independiente en Prometheus, con su propio coste de memoria y almacenamiento. Una etiqueta `userId` con un millón de usuarios multiplica por un millón el número de series de esa métrica, lo que satura Prometheus, dispara el coste y hace las consultas lentísimas. Las etiquetas deben tener un número pequeño y acotado de valores: método HTTP, código de estado, ruta con plantilla (`/tareas/{id}`, no `/tareas/42`), región.

Además, un email en una métrica es un dato personal que acabaría en un sistema que no está pensado para protegerlo ni para borrarlo a petición (RGPD). El detalle por usuario va en logs o en atributos de los spans, que se consultan individualmente.

### Nivel 3 (Entrevista Senior): ¿Qué problema específico de propagación de contexto de trazas surge al usar Virtual Threads de Java 21 con ThreadLocal clásico, y cómo lo resuelven Micrometer Tracing y OpenTelemetry?

Las librerías de tracing clásicas guardaban el TraceContext en un `ThreadLocal`. Al usar millones de Virtual Threads creados y destruidos rápidamente, o pools de tareas asíncronas, el contexto no se propaga automáticamente entre hilos a menos que se use un Executor decorado (`ContextPropagatingExecutorService`) o `ScopedValue` de Java 21+. Las versiones modernas de Micrometer Tracing gestionan esta propagación interceptando la creación de tareas asíncronas.

**Respuesta razonada:** Los hilos virtuales sí soportan `ThreadLocal`, pero cada tarea se ejecuta en un hilo virtual nuevo, y un hilo nuevo no hereda el contexto del que lo lanzó. Si dentro de una petición repartes trabajo en un `newVirtualThreadPerTaskExecutor()`, las subtareas arrancan sin traza activa ni MDC: sus spans aparecen como trazas huérfanas y sus logs sin `traceId`. Además, guardar objetos costosos en `ThreadLocal` deja de tener sentido con millones de hilos de vida corta.

Micrometer lo resuelve con la librería Context Propagation: captura el contexto actual (`ContextSnapshot`) y lo restaura en el hilo de destino. Se aplica envolviendo el executor (`ContextExecutorService.wrap(...)`) o, para `@Async` y los ejecutores de Spring, con `ContextPropagatingTaskDecorator`. Reactor usa el mismo mecanismo con `Hooks.enableAutomaticContextPropagation()`. A futuro, Scoped Values ofrecen un modelo de contexto inmutable y heredable más adecuado para hilos virtuales.


## Reto del tutor

**Reto:** Un microservicio en producción tiene picos de latencia de 5 segundos esporádicos. Los logs no muestran excepciones ni errores (HTTP 200). En Grafana ves que la CPU está al 15% y la memoria al 40%. ¿Qué pasos exactos seguirías para encontrar la causa raíz usando observabilidad completa?

- **Lo que está bien:** Propone mirar las trazas y los tiempos de base de datos.
- **Respuesta superficial:** Decir "revisar más logs".
- **Lo que falta de fondo:** No usar la cascada de observabilidad metódicamente: métricas de percentil p99 -> trazas distribuidas en Tempo -> spans individuales de BD o llamadas externas -> logs correlacionados.
- **Respuesta senior:** El procedimiento metodológico SRE: (1) En Grafana, consultar la métrica de latencia por percentil `http_server_requests_seconds{quantile="0.99"}` para identificar la ventana temporal exacta de los picos; (2) En Grafana Tempo, filtrar trazas de esa ventana con duración > 4s; (3) Abrir el waterfall de una de esas trazas y observar qué Span consume el 95% del tiempo (e.g. una query a PostgreSQL o una llamada a un servicio externo); (4) Si es la BD, verificar en Prometheus la métrica `hikaricp_connections_pending` (espera por conexión libre en el pool) o pausas de GC; (5) Con el TraceID del span lento, ir a Grafana Loki para ver los logs contextuales de esa ejecución específica (parámetros de consulta, volumen de datos procesado).


## Katas

### Kata 30.1: Instrumentación de Métricas y Trazas en Spring Boot 3

Añade micrometer-registry-prometheus y micrometer-tracing-bridge-otel a tu pom.xml. Crea un `@Service` con una llamada simulada, mide su duración con un `Timer` de Micrometer, verifica que la respuesta incluye cabecera `traceparent` y que `/actuator/prometheus` contiene la métrica.


## Reglas de oro

- Nunca pongas IDs de usuario ni valores de alta cardinalidad en tags de métricas.
- Emite siempre logs en formato JSON estructurado con TraceID y SpanID automáticos.
- Propaga las cabeceras de traza W3C (traceparent) en todas las llamadas HTTP y mensajes Kafka.
- Configura un endpoint /actuator/prometheus seguro y monitorea el percentil p99, no solo el promedio.
- Integra Data Links en Grafana para navegar con un clic entre Métricas, Trazas (Tempo) y Logs (Loki).


## Proyecto guiado «Pedidos»: Observabilidad completa

Saber qué pasa en producción: cuántos pedidos entran, cuánto tardan y dónde fallan.

**Tareas:**

- Publica métricas de negocio con Micrometer: pedidos confirmados, importe vendido y tiempo de confirmación.
- Emite los logs en JSON con el trace id incluido.
- Propaga la traza de `pedidos` a `notificaciones` a través de Kafka y visualízala en Grafana con Tempo.

**Está listo cuando:**

- Un panel de Grafana muestra pedidos por minuto y la latencia p95 de la API.
- Desde un log de error puedes saltar a la traza completa del pedido en los dos servicios.


## Fuentes oficiales

- [OpenTelemetry para Java](https://opentelemetry.io/docs/languages/java/)
- [Spring Boot: observabilidad](https://docs.spring.io/spring-boot/reference/actuator/observability.html)
- [Prometheus: visión general](https://prometheus.io/docs/introduction/overview/)
- [Grafana Loki](https://grafana.com/docs/loki/latest/)
- [Grafana Tempo](https://grafana.com/docs/tempo/latest/)

