Observabilidad en Spring Boot 2026: OpenTelemetry, Tracing Distribuido y Preguntas de Entrevista

Guía completa sobre observabilidad en Spring Boot con OpenTelemetry y tracing distribuido. Configuración OTLP, Observation API, propagación de contexto y preguntas técnicas para entrevistas backend.

Observabilidad en Spring Boot con OpenTelemetry y tracing distribuido

La observabilidad en Spring Boot combina logging, métricas y tracing distribuido en un sistema unificado que revela cómo las solicitudes fluyen a través de los microservicios. Desde Spring Boot 3, el framework adoptó Micrometer Tracing (reemplazando Spring Cloud Sleuth) e introdujo la Observation API, que proporciona un único punto de instrumentación que emite tanto métricas como traces.

El Patrón Observation API

Spring Boot recomienda usar Observation.observe() en lugar de llamar a OpenTelemetry directamente. Una sola llamada de instrumentación produce métricas vía Micrometer y traces vía el bridge OpenTelemetry, reduciendo la duplicación de código y asegurando una nomenclatura consistente de tags entre señales.

Cómo la Observation API Conecta Micrometer y OpenTelemetry

La Observation API actúa como una fachada sobre métricas y tracing. Cuando el código llama a Observation.createNotStarted(), Spring Boot enruta la observación a los handlers registrados: MeterObservationHandler para métricas de Micrometer y TracingObservationHandler para traces distribuidos. Esta arquitectura significa que instrumentar una vez exporta a todas partes.

La dependencia bridge micrometer-tracing-bridge-otel conecta Micrometer Tracing al SDK de OpenTelemetry. Los traces fluyen a través del SdkTracerProvider de OpenTelemetry y se exportan vía OTLP a backends como Jaeger, Tempo, o cualquier colector compatible con OpenTelemetry.

ObservabilityConfig.javajava
@Configuration
public class ObservabilityConfig {

    @Bean
    public ObservationRegistryCustomizer<ObservationRegistry> addLowCardinalityTags() {
        return registry -> registry.observationConfig()
            .observationHandler(new ObservationTextPublisher()); // Logs observations to console
    }
}

La configuración anterior registra un ObservationHandler que registra cada observación en logs. En producción, los handlers auto-configurados envían datos a los registros de Micrometer y exportadores de OpenTelemetry sin código adicional.

Dependencias Requeridas para Tracing en Spring Boot 3.4

Spring Boot 3.4 requiere dependencias explícitas para el tracing distribuido. El spring-boot-starter-actuator proporciona la Observation API, pero el tracing necesita el bridge de OpenTelemetry y un exportador.

xml
<!-- pom.xml -->
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <dependency>
        <groupId>io.micrometer</groupId>
        <artifactId>micrometer-tracing-bridge-otel</artifactId>
    </dependency>
    <dependency>
        <groupId>io.opentelemetry</groupId>
        <artifactId>opentelemetry-exporter-otlp</artifactId>
    </dependency>
</dependencies>

El artefacto micrometer-tracing-bridge-otel conecta Micrometer Tracing a la API de OpenTelemetry. El opentelemetry-exporter-otlp envía spans a los colectores usando el protocolo OTLP sobre HTTP o gRPC. Spring Boot gestiona la alineación de versiones a través de su gestión de dependencias.

Configuración de Exportación OTLP hacia Jaeger o Tempo

Spring Boot auto-configura un OtlpHttpSpanExporter cuando la dependencia OTLP está presente. El exportador envía traces al endpoint especificado en application.yml.

yaml
# application.yml
management:
  tracing:
    sampling:
      probability: 1.0  # 100% sampling for dev, reduce in production
  otlp:
    tracing:
      endpoint: http://localhost:4318/v1/traces
  opentelemetry:
    resource-attributes:
      service.name: order-service
      deployment.environment: staging

Los resource-attributes adjuntan metadatos a cada span. Backends como Grafana Tempo y Jaeger usan estos atributos para agrupar traces por servicio y ambiente. Establecer sampling.probability en 1.0 captura todas las solicitudes durante desarrollo, pero los sistemas de producción típicamente muestrean entre 1% y 10% para controlar los costos de almacenamiento.

Crear Observaciones Personalizadas con Tags de Cardinalidad Baja y Alta

Las observaciones soportan dos tipos de tags: cardinalidad baja para métricas (valores acotados como métodos HTTP o códigos de estado) y cardinalidad alta para traces (valores no acotados como IDs de usuario o IDs de solicitud).

PaymentService.javajava
@Service
public class PaymentService {

    private final ObservationRegistry observationRegistry;

    public PaymentService(ObservationRegistry observationRegistry) {
        this.observationRegistry = observationRegistry;
    }

    public PaymentResult processPayment(PaymentRequest request) {
        return Observation.createNotStarted("payment.process", observationRegistry)
            .lowCardinalityKeyValue("payment.method", request.getMethod().name())  // enum: CARD, BANK_TRANSFER
            .lowCardinalityKeyValue("currency", request.getCurrency())              // bounded: USD, EUR, GBP
            .highCardinalityKeyValue("payment.id", request.getPaymentId())          // unique per request
            .highCardinalityKeyValue("customer.id", request.getCustomerId())        // high cardinality
            .observe(() -> executePayment(request));
    }

    private PaymentResult executePayment(PaymentRequest request) {
        // Payment gateway call
        return new PaymentResult(true, "TXN-" + UUID.randomUUID());
    }
}

Los tags de cardinalidad baja aparecen tanto en métricas como en traces. Los tags de cardinalidad alta aparecen solo en traces porque las métricas con dimensiones no acotadas explotan el almacenamiento. Esta distinción previene las bombas de cardinalidad de Prometheus mientras mantiene los traces ricos en detalles.

Pregunta de Entrevista: Cardinalidad de Tags

Los entrevistadores frecuentemente preguntan por qué algunos tags deben excluirse de las métricas. La respuesta involucra cardinalidad: un tag de ID de usuario en una métrica crea una serie temporal por usuario, potencialmente millones de series. Los backends de monitoreo tienen dificultades con la alta cardinalidad, llevando a agotamiento de memoria y consultas lentas. Los traces manejan la alta cardinalidad a través del muestreo, haciéndolos el lugar correcto para identificadores específicos de solicitud.

¿Listo para aprobar tus entrevistas de Spring Boot?

Practica con nuestros simuladores interactivos, flashcards y tests técnicos.

Propagación del Contexto de Trace a través de Límites Asíncronos

El tracing distribuido requiere propagación de contexto. Los headers HTTP transportan IDs de trace entre servicios, pero las operaciones asíncronas como métodos @Async o cadenas de CompletableFuture pueden perder contexto si no se configuran correctamente.

Spring Boot 3.4 proporciona propagación automática para streams reactivos y métodos @Async cuando se configura:

yaml
# application.yml
spring:
  reactor:
    context-propagation: auto
  task:
    execution:
      propagate-context: true

Para beans TaskExecutor personalizados, envolverlos con ContextPropagatingTaskDecorator:

AsyncConfig.javajava
@Configuration
@EnableAsync
public class AsyncConfig {

    @Bean
    public TaskExecutor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10);
        executor.setMaxPoolSize(50);
        executor.setTaskDecorator(new ContextPropagatingTaskDecorator());
        executor.initialize();
        return executor;
    }
}

El decorador copia la Observation actual y el contexto de trace al hilo asíncrono. Sin él, los spans iniciados en tareas async no tienen padre, rompiendo el árbol de traces.

Uso de las Anotaciones @Observed y @NewSpan

Spring Boot 3.4 soporta observabilidad declarativa a través de anotaciones. Habilitar el procesamiento de anotaciones agregando el weaver de AspectJ:

xml
<!-- pom.xml -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
yaml
# application.yml
management:
  observations:
    annotations:
      enabled: true
InventoryService.javajava
@Service
public class InventoryService {

    @Observed(name = "inventory.check", contextualName = "checkStock")
    public StockLevel checkStock(String productId) {
        // Database query
        return new StockLevel(productId, 42);
    }

    @NewSpan("inventory.reserve")  // Creates a child span under the current trace
    public ReservationResult reserveStock(String productId, int quantity) {
        // Update inventory
        return new ReservationResult(true, "RES-" + System.currentTimeMillis());
    }
}

@Observed crea tanto una métrica (timer) como un span. @NewSpan crea solo un span, útil cuando la operación ya tiene métricas en otro lugar. Ambas anotaciones manejan automáticamente el registro de excepciones y el estado del span.

Componentes Auto-Instrumentados en Spring Boot 3.4

Spring Boot 3.4 auto-instrumenta varios componentes sin cambios de código:

ComponenteNombre de ObservaciónQué Captura
Spring MVChttp.server.requestsRuta de solicitud, método, estado, excepción
WebClienthttp.client.requestsLlamadas HTTP salientes
RestClienthttp.client.requestsLlamadas HTTP salientes (Spring 6.1+)
Spring Kafkaspring.kafka.listenerGrupo de consumidores, topic, partición
Spring Data JPAspring.data.repositoryMétodo del repositorio, tiempo de consulta
Tareas Programadasspring.schedulingNombre de tarea, tiempo de ejecución

La documentación de Spring Boot Actuator lista todos los componentes auto-instrumentados. Bibliotecas de terceros como Datasource Micrometer agregan tracing de consultas JDBC.

Filtrar Observaciones para Reducir el Ruido

Los health checks, sondas de disponibilidad y recursos estáticos generan ruido en los traces. Filtrarlos usando predicados:

ObservationFilterConfig.javajava
@Configuration
public class ObservationFilterConfig {

    @Bean
    public ObservationPredicate noHealthChecks() {
        return (name, context) -> !name.equals("http.server.requests")
            || !isHealthEndpoint(context);
    }

    private boolean isHealthEndpoint(Observation.Context context) {
        if (context instanceof ServerRequestObservationContext http) {
            String path = http.getCarrier().getRequestURI();
            return path.startsWith("/actuator/health") || path.startsWith("/actuator/ready");
        }
        return false;
    }
}

Alternativamente, deshabilitar observaciones por nombre en la configuración:

yaml
# application.yml
management:
  observations:
    enable:
      spring.security: false  # Disable Spring Security observations
      http.server.requests.actuator: false  # Custom predicate name
Contexto de Entrevista: Muestreo vs Filtrado

El muestreo reduce el porcentaje de traces recolectados en todos los endpoints. El filtrado elimina endpoints específicos completamente. Usar filtrado para endpoints que nunca proporcionan valor diagnóstico (health checks, scraping de métricas). Usar muestreo para controlar costos mientras se preservan datos representativos.

Correlación de Logs con IDs de Trace

Spring Boot 3.4 automáticamente agrega IDs de trace y span al MDC (Mapped Diagnostic Context) cuando Micrometer Tracing está activo. Logback y Log4j2 pueden incluir estos IDs en la salida de logs:

xml
<!-- logback-spring.xml -->
<configuration>
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - traceId=%X{traceId} spanId=%X{spanId} - %msg%n</pattern>
        </encoder>
    </appender>
    <root level="INFO">
        <appender-ref ref="CONSOLE" />
    </root>
</configuration>

Con este patrón, cada línea de log incluye el ID de trace. Buscar logs por ID de trace retorna todos los mensajes de una sola solicitud a través de todos los servicios, correlacionando logs con spans en Jaeger o Tempo.

Preguntas de Entrevista Comunes sobre Observabilidad en Spring Boot

Los entrevistadores evalúan tanto la comprensión conceptual como la experiencia práctica con observabilidad. Estas preguntas aparecen frecuentemente para roles backend senior.

P: ¿Cuál es la diferencia entre Micrometer Tracing y OpenTelemetry?

Micrometer Tracing es una API agnóstica de proveedor para tracing distribuido, similar a cómo Micrometer abstrae métricas. OpenTelemetry es una implementación y protocolo específico. Spring Boot usa Micrometer Tracing como API y hace bridge hacia OpenTelemetry para exportación vía micrometer-tracing-bridge-otel. Esta separación permite a las aplicaciones cambiar backends sin cambios de código.

P: ¿Por qué Spring recomienda la Observation API sobre la instrumentación directa de OpenTelemetry?

La Observation API proporciona un único punto de instrumentación que emite tanto métricas como traces. Las llamadas directas a OpenTelemetry producen solo traces. Usar Observation.observe() genera una métrica timer y un span desde el mismo código, reduciendo duplicación y asegurando nomenclatura consistente.

P: ¿Cómo se depura un trace que muestra una brecha entre spans?

Las brechas indican instrumentación faltante o propagación de contexto rota. Verificar si el código usa operaciones async sin ContextPropagatingTaskDecorator. Verificar que los clientes HTTP estén instrumentados (WebClient, RestClient, o un RestTemplate envuelto manualmente). Para colas de mensajes, confirmar que los headers de trace se propaguen a través de las propiedades del mensaje.

P: ¿Qué sucede si se agrega un tag de alta cardinalidad a una métrica?

Cada valor único de tag crea una nueva serie temporal. Un tag de ID de usuario con millones de usuarios crea millones de series, agotando la memoria en Prometheus u otros backends TSDB. La solución es usar highCardinalityKeyValue() en lugar de lowCardinalityKeyValue(), lo cual agrega el tag solo a traces donde se espera alta cardinalidad.

Construir un Pipeline de Tracing Distribuido con Docker Compose

Un stack de observabilidad local ayuda a validar la instrumentación antes de desplegar a producción. Este ejemplo usa el OpenTelemetry Collector y Jaeger:

yaml
# docker-compose.yml
services:
  otel-collector:
    image: otel/opentelemetry-collector-contrib:0.102.0
    command: ["--config", "/etc/otel-collector-config.yml"]
    volumes:
      - ./otel-collector-config.yml:/etc/otel-collector-config.yml
    ports:
      - "4318:4318"   # OTLP HTTP
      - "4317:4317"   # OTLP gRPC

  jaeger:
    image: jaegertracing/jaeger:2.3
    ports:
      - "16686:16686"  # UI
    environment:
      - COLLECTOR_OTLP_ENABLED=true

  app:
    build: .
    environment:
      - MANAGEMENT_OTLP_TRACING_ENDPOINT=http://otel-collector:4318/v1/traces
    depends_on:
      - otel-collector
yaml
# otel-collector-config.yml
receivers:
  otlp:
    protocols:
      http:
        endpoint: 0.0.0.0:4318
      grpc:
        endpoint: 0.0.0.0:4317

processors:
  batch:
    timeout: 1s
    send_batch_size: 1024

exporters:
  otlp/jaeger:
    endpoint: jaeger:4317
    tls:
      insecure: true

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [batch]
      exporters: [otlp/jaeger]

El colector recibe spans de la aplicación Spring Boot y los reenvía a Jaeger. Esta arquitectura permite agregar exportadores (Tempo, Zipkin, backends cloud) sin cambiar el código de la aplicación.

¡Empieza a practicar!

Pon a prueba tu conocimiento con nuestros simuladores de entrevista y tests técnicos.

Puntos Clave para la Observabilidad en Spring Boot

  • La Observation API unifica métricas y tracing: una sola llamada Observation.observe() produce tanto una métrica timer como un span, eliminando la instrumentación doble.
  • Agregar micrometer-tracing-bridge-otel y opentelemetry-exporter-otlp para habilitar la exportación OTLP. Spring Boot 3.4 auto-configura el exportador HTTP con el endpoint de management.otlp.tracing.endpoint.
  • Usar lowCardinalityKeyValue() para dimensiones que aparecen en métricas (valores acotados). Usar highCardinalityKeyValue() para datos solo en traces (IDs de usuario, IDs de solicitud).
  • Habilitar propagación de contexto para código async con spring.task.execution.propagate-context=true y envolver executors personalizados con ContextPropagatingTaskDecorator.
  • Filtrar endpoints ruidosos como /actuator/health usando beans ObservationPredicate para mantener los traces enfocados en operaciones de negocio.
  • Los patrones de log deben incluir %X{traceId} para correlacionar líneas de log con traces distribuidos en el backend de observabilidad.
  • Las preguntas de entrevista sobre observabilidad se enfocan en cardinalidad, fallas de propagación de contexto, y la distinción entre la Observation API y el uso directo de OpenTelemetry.
Reto diario

¿Sabrías detectar el bug en Spring Boot?

Un fragmento real, un bug oculto, un intento al día. Sin cuenta para probar.

Anthony Fillion-Maillet

Escrito por

Anthony Fillion-Maillet

Fundador de SharpSkill

Desarrollador fullstack desde hace más de 10 años. Dirige SharpSkill y responde por todo lo que se publica aquí.

Actualizado el 19 de septiembre de 2026

Etiquetas

#spring-boot
#observability
#opentelemetry
#distributed-tracing
#micrometer

Compartir

Artículos relacionados