OpenTelemetry: estándar único para trace, log y métrica
1. Descripción general
Cuando un sistema se divide en múltiples servicios, una solicitud puede pasar por cinco, diez procesos diferentes. Cuando hay un error, necesitas saber por dónde fue la solicitud, dónde se ralentizó y qué logs le corresponden. OpenTelemetry (OTel) es un estándar abierto que resuelve exactamente esto. Unifica la forma de recopilar traces, métricas y logs, y luego los envía a cualquier backend.
Después de leer este artículo, entenderás los conceptos centrales de OTel y por qué se ha convertido en un estándar. También sabrás cómo integrar OTel en una aplicación Spring Boot 3, ejecutar un Collector, escribir tests para la instrumentación y reconocer cuándo no deberías usarlo.
2. ¿Qué es OpenTelemetry?
OpenTelemetry es un proyecto de código abierto bajo CNCF. Define cómo crear, describir y transportar datos de telemetría. OTel no es un lugar de almacenamiento ni una interfaz de visualización. Esa parte la cubren backends como Jaeger, Prometheus, Grafana Tempo/Loki o servicios comerciales.
2.1. Tres tipos de señales
- Trace: el recorrido de una solicitud. Cada trace consta de múltiples spans, donde cada span es una unidad de trabajo, por ejemplo “llamar a PaymentService” o “consultar la tabla orders”. Un span tiene hora de inicio, hora de finalización y atributos.
- Métrica: números agregados a lo largo del tiempo, como solicitudes por segundo, latencia p99 o pedidos fallidos.
- Log: registros de eventos discretos. OTel añade
traceIdyspanIda los logs, permitiéndote saltar desde una línea de log al trace correcto.
El mayor valor reside en la interconexión. Los tres tipos de señales comparten contexto y descripción de recursos comunes, como service.name.
2.2. Componentes principales
| Componente | Función |
|---|---|
| API | Interfaz para que el código cree spans y métricas, independiente del proveedor |
| SDK | Implementa la API: muestreo, batching, exportación |
| OTLP | Protocolo de transmisión de datos (gRPC o HTTP/protobuf) |
| Collector | Proceso independiente que recibe, procesa y reenvía telemetría |
| Semantic conventions | Convenciones de nomenclatura de atributos como http.request.method, db.system… |
| Instrumentation | Librerías o agente que generan telemetría automáticamente para frameworks populares |
2.3. Propagación de contexto
Para que los spans en múltiples servicios se unan en un trace, el contexto debe acompañar la solicitud. OTel usa por defecto el estándar W3C Trace Context a través de un header traceparent:
traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
Este header tiene cuatro partes: versión, traceId, spanId del span padre y flag de muestreo. El servicio que recibe la solicitud lee este header y crea un span hijo perteneciente al mismo trace.
3. ¿Por qué OpenTelemetry está ganando atención?
- Neutral con respecto al proveedor: instrumentas el código una sola vez. Si quieres cambiar de backend, solo cambias la configuración del exporter o el Collector, sin tocar el código.
- Unifica estándares antiguos: OTel surgió de fusionar OpenTracing y OpenCensus. La comunidad ya no debe elegir entre dos caminos.
- Un estándar para los tres tipos de señales: anteriormente, cada tipo de dato tenía su propio agente y formato.
- Ecosistema amplio: muchos frameworks, proveedores cloud y herramientas de observabilidad soportan OTLP. Spring Boot 3 también se integra a través de Micrometer.
- Collector flexible: puedes filtrar datos sensibles, hacer muestreo y enrutar a múltiples backends en la capa de infraestructura, sin tocar la aplicación.
4. Aplicar OpenTelemetry en Spring Boot 3
Spring Boot 3 usa Micrometer Observation como capa de abstracción. Escribes una observation y Micrometer genera tanto métrica (timer) como span. El puente hacia OTel se encarga de la exportación.
4.1. Dependencias
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- Puente Micrometer Tracing -> OpenTelemetry -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-otel</artifactId>
</dependency>
<!-- Exportar traces vía OTLP -->
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-exporter-otlp</artifactId>
</dependency>
<!-- Exportar métricas vía OTLP -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-otlp</artifactId>
</dependency>
<!-- Para pruebas -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-observation-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
No necesitas especificar versiones. Spring Boot ya gestiona las versiones de Micrometer y OpenTelemetry a través de BOM.
4.2. Configuración
spring:
application:
name: order-service
management:
tracing:
sampling:
probability: 1.0 # dev: 100%; en producción reducir
otlp:
tracing:
endpoint: http://localhost:4318/v1/traces
metrics:
export:
url: http://localhost:4318/v1/metrics
step: 30s
logging:
pattern:
level: "%5p [${spring.application.name:},%X{traceId:-},%X{spanId:-}]"
Significado de cada grupo de configuración:
spring.application.namese usa comoservice.name. Este es el atributo más importante para que el backend distinga los servicios.management.tracing.sampling.probabilitydetermina qué porcentaje de traces se registran.- Los dos endpoints
otlpapuntan al Collector vía OTLP/HTTP, puerto por defecto 4318. logging.pattern.levelinsertatraceIdyspanIddel MDC en cada línea de log. Esta es la forma más simple de conectar logs con traces.
4.3. Instrumentar lógica de negocio con la API de Observation
Las solicitudes HTTP al controlador y las llamadas vía RestClient/WebClient creadas por Spring ya están instrumentadas automáticamente. La parte que necesitas añadir tú mismo son los pasos de negocio importantes:
public record Order(String id, String paymentMethod, long amount) {}
@FunctionalInterface
public interface PaymentService {
String charge(Order order);
}
@Service
public class CheckoutService {
private static final Logger log = LoggerFactory.getLogger(CheckoutService.class);
private final ObservationRegistry registry;
private final PaymentService paymentService;
public CheckoutService(ObservationRegistry registry, PaymentService paymentService) {
this.registry = registry;
this.paymentService = paymentService;
}
public String checkout(Order order) {
return Observation.createNotStarted("order.checkout", registry)
.lowCardinalityKeyValue("payment.method", order.paymentMethod())
.highCardinalityKeyValue("order.id", order.id())
.observe(() -> {
log.info("Procesando orden {}", order.id());
return paymentService.charge(order);
});
}
}
Puntos a tener en cuenta en el código anterior:
Observation.createNotStarted("order.checkout", registry)crea una observation llamadaorder.checkout.lowCardinalityKeyValueañade un atributo con pocos valores (CARD,CASH,WALLET). Este atributo aparece en tanto la métrica como el span.highCardinalityKeyValueañade un atributo con muchos valores como el ID del pedido. Se añade solo al span, sin inflar la métrica.observe(...)abre el span, mide el tiempo, registra excepciones si las hay, y cierra el span cuando el supplier retorna.
Cuando se ejecuta, el log se ve así:
INFO [order-service,4bf92f3577b34da6a3ce929d0e0e4736,a1b2c3d4e5f60718] ... Procesando orden A-1001
El backend recibe dos cosas:
- Un span
order.checkoutque cuelga del span HTTP de la solicitud. - Una métrica timer
order.checkout, desglosada porpayment.method. A partir de esta métrica puedes calcular el número de llamadas, tiempo total y tiempo máximo.
Nota: Prefiere la API de Observation en lugar de llamar directamente a
io.opentelemetry.api.trace.Traceren código Spring. Tu código no dependerá de una librería de tracing específica. Una única medición genera tanto métrica como trace.
4.4. Alternativa: agente Java
Si no quieres modificar el código o agregar dependencias, puedes usar el agente Java de OpenTelemetry. El agente instrumenta automáticamente muchas librerías populares como JDBC, clientes HTTP y Kafka:
export OTEL_SERVICE_NAME=order-service
export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
export OTEL_TRACES_SAMPLER=parentbased_traceidratio
export OTEL_TRACES_SAMPLER_ARG=0.1
java -javaagent:./opentelemetry-javaagent.jar -jar order-service.jar
El agente es apropiado para aplicaciones antiguas o cuando el equipo de operaciones quiere controlar la instrumentación de forma centralizada. El precio es que tienes menos control detallado, el tiempo de arranque aumenta, y el agente actúa como una “caja negra” que interviene en el bytecode.
Nota: No ejecutes simultáneamente el agente Java y el puente Micrometer para el mismo tipo de señal. Recibirás spans duplicados o contexto desalineado.
5. Ejecutar OpenTelemetry Collector
La aplicación debe enviar datos al Collector en lugar de enviarlos directamente al backend. El Collector es donde haces batch, filtras y encaminas los datos. A continuación hay una configuración mínima:
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
processors:
batch: {}
attributes/strip-pii:
actions:
- key: customer.email
action: delete
exporters:
debug:
verbosity: detailed
service:
pipelines:
traces:
receivers: [otlp]
processors: [attributes/strip-pii, batch]
exporters: [debug]
metrics:
receivers: [otlp]
processors: [batch]
exporters: [debug]
logs:
receivers: [otlp]
processors: [batch]
exporters: [debug]
Esta configuración consta de cuatro partes:
receivers.otlprecibe datos vía gRPC (4317) e HTTP (4318).attributes/strip-piielimina el atributocustomer.emailantes de que los datos salgan de tu infraestructura.batchagrupa los datos antes de enviar para reducir el número de solicitudes.- El exporter
debugimprime los datos en la consola para que puedas verificar. En un entorno de producción, lo reemplazas con el exporter del backend que estés usando. Las versiones antiguas de Collector usaban el nombrelogging.
Inicia el Collector con Docker:
docker run --rm -p 4317:4317 -p 4318:4318 \
-v "$(pwd)/otel-collector.yaml:/etc/otelcol-contrib/config.yaml" \
otel/opentelemetry-collector-contrib
Prueba llamar al API de checkout, y luego verifica la consola del Collector. Verás un span similar a:
Span #0
Trace ID : 4bf92f3577b34da6a3ce929d0e0e4736
Parent ID : 9a8b7c6d5e4f3a2b
Name : order.checkout
Attributes:
-> payment.method: Str(CARD)
-> order.id: Str(A-1001)
6. Pruebas para la instrumentación
La instrumentación también es código, así que debe ser probada. TestObservationRegistry te permite verificar observations sin necesidad de iniciar el contexto Spring ni el Collector:
import io.micrometer.observation.tck.TestObservationRegistry;
import io.micrometer.observation.tck.TestObservationRegistryAssert;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
class CheckoutServiceTest {
@Test
void whenCheckout_thenObservationIsRecordedWithPaymentMethod() {
TestObservationRegistry registry = TestObservationRegistry.create();
PaymentService paymentService = order -> "PAID-" + order.id();
CheckoutService service = new CheckoutService(registry, paymentService);
String result = service.checkout(new Order("A-1001", "CARD", 250_000));
assertThat(result).isEqualTo("PAID-A-1001");
TestObservationRegistryAssert.assertThat(registry)
.hasObservationWithNameEqualTo("order.checkout")
.that()
.hasLowCardinalityKeyValue("payment.method", "CARD")
.hasHighCardinalityKeyValue("order.id", "A-1001")
.hasBeenStarted()
.hasBeenStopped();
}
}
Esta prueba afirma tres cosas:
- El resultado de negocio sigue siendo correcto.
- Se ha creado la observation
order.checkoutcon los atributos correctos. - La observation ha sido abierta y cerrada. Un span que no se cierre es un error muy difícil de detectar en producción.
7. Errores comunes
- Explosión de cardinalidad: poner
userId,orderIdo URLs con parámetros en las etiquetas de métrica hará que el número de series de métricas aumente exponencialmente y los costos de almacenamiento también. Estos valores solo deben agregarse a los spans. - Muestreo al 100% en producción: el volumen de datos y costos crecen linealmente con el tráfico. Reduce
probabilityo usa tail sampling en el Collector para retener traces con errores o traces lentos. - Pérdida de contexto en ejecución asíncrona: con
@Async,CompletableFutureo thread pools personalizados, el contexto no se propaga automáticamente al nuevo thread. Necesitas envolver el executor con un mecanismo de propagación de contexto, o usar un executor gestionado por Spring que ya esté configurado. - Fuga de datos sensibles: correos, números de tarjeta o tokens pueden filtrarse en atributos o logs. Filtra en el código y añade otra capa de filtrado en el Collector.
- Nomenclatura arbitraria: sin seguir las convenciones semánticas, hace que los dashboards y alertas sean difíciles de reutilizar entre servicios.
- Saltarse el Collector: enviar directamente desde la aplicación al backend significa que cada vez que cambies de backend tienes que desplegar todos los servicios nuevamente.
8. Cuándo NO usar (o aún no necesitas)
- Aplicación pequeña, una sola instancia, pocas dependencias: logs estructurados junto con algunas métricas de Actuator suelen ser suficientes. El distributed tracing apenas aporta valor cuando no hay nada “distribuido”.
- El equipo aún no tiene un backend ni personal de operaciones: OTel es solo una tubería. Si nadie mira los datos, solo gastas más CPU, ancho de banda y dinero en almacenamiento.
- Sistema extremadamente sensible a la latencia o recursos: por ejemplo aplicaciones integradas o rutas críticas medidas en microsegundos. Mide el overhead antes de habilitar la instrumentación ampliamente.
- Esperar que OTel reemplace todas tus herramientas existentes de una sola vez: el nivel de madurez de cada signal y de cada lenguaje puede variar. Verifica el estado de soporte de la parte que planeas usar y migra paso a paso, comenzando por tracing.
9. Conclusión
OpenTelemetry unifica la forma de crear y transportar traces, métricas y logs a través de una API común, el protocolo OTLP y el Collector. Gracias a esto puedes separar la instrumentación en el código de la elección del backend.
Con Spring Boot 3, el camino más limpio consta de cuatro pasos. Primero, usa Micrometer Observation para que una sola medición genere tanto métrica como span. Segundo, exporta vía OTLP al Collector. Tercero, inserta traceId en los logs. Cuarto, escribe pruebas para las observations importantes. Al mismo tiempo, controla la cardinalidad, el muestreo y los datos sensibles desde el principio.
Si tu sistema aún es pequeño, no hay prisa. Cuando el número de servicios crece y la depuración comienza a tomar horas, OTel es una plataforma que vale la pena.