OpenTelemetry no es solo un framework de tracing. Es una plataforma de telemetría completa que define cinco tipos de signals: las categorías fundamentales de datos que puedes recopilar de tus sistemas.
La mayoría de equipos conoce las trazas y, si tiene suerte, las métricas. Pero entender los cinco signals — y más importante, saber cuándo usar cada uno — es lo que separa una instrumentación básica de una estrategia de observabilidad real.
¿Qué es un Signal en OpenTelemetry?
Un signal es un mecanismo estandarizado para capturar un tipo específico de dato de telemetría. Cada signal tiene su propio modelo de datos, su propio protocolo de exportación (vía OTLP) y sus propias semánticas. No son intercambiables — cada uno responde preguntas distintas.
La pregunta que deberías hacerte es: ¿qué pregunta necesito responder? La respuesta determina qué signal usar.
1. Traces — ¿qué hizo el sistema para atender esta petición?
Un trace representa el recorrido completo de una petición a través de tu sistema. Está formado por spans: unidades de trabajo con un inicio, un fin, un nombre y atributos de contexto.
[Traza: POST /checkout]
├── [Span: api-gateway] 12ms
│ ├── [Span: auth.validate] 3ms
│ ├── [Span: order.process] 7ms
│ │ ├── [Span: inventory.check] 2ms → db: inventory
│ │ └── [Span: payment.charge] 4ms → external: stripe
│ └── [Span: notification.send] 1ms
Lo que hace único a los traces es la correlación causal: cada span conoce a su padre. Puedes ver exactamente qué llamó a qué, en qué orden y cuánto tardó cada parte.
Cuándo usar traces
- Para entender la latencia de una petición específica y dónde se pierde el tiempo
- Para depurar errores intermitentes que dependen del contexto de ejecución
- Para visualizar dependencias entre servicios en sistemas distribuidos
- Para identificar qué servicio es el cuello de botella de una operación concreta
Lo que los traces no te dan
Los traces son caros de almacenar y, por eso, suelen muestrearse. No son buenos para responder preguntas agregadas como “¿cuántas peticiones fallaron en la última hora?”. Para eso están las métricas.
2. Metrics — ¿cómo está rindiendo el sistema en este momento?
Las métricas son mediciones numéricas agregadas a lo largo del tiempo. A diferencia de los traces, no describen una petición individual — describen el comportamiento del sistema en su conjunto.
OpenTelemetry define varios instrumentos de métricas según el tipo de dato que quieres capturar:
| Instrumento | Uso |
|---|---|
Counter | Valores que solo suben: peticiones totales, errores totales |
UpDownCounter | Valores que suben y bajan: conexiones activas, tamaño de cola |
Histogram | Distribuciones: latencia de peticiones, tamaño de payloads |
Gauge | Valor instantáneo: uso de memoria, temperatura de CPU |
val meter = openTelemetry.getMeter("order-service")
// Contar pedidos procesados
val ordersProcessed = meter.counterBuilder("orders.processed")
.setDescription("Total de pedidos procesados")
.setUnit("orders")
.build()
// Medir latencia de procesamiento
val processingTime = meter.histogramBuilder("order.processing.duration")
.setDescription("Tiempo de procesamiento de pedidos")
.setUnit("ms")
.build()
fun processOrder(order: Order) {
val start = System.currentTimeMillis()
// ...
processingTime.record(System.currentTimeMillis() - start)
ordersProcessed.add(1, Attributes.of(
AttributeKey.stringKey("order.type"), order.type
))
}
Cuándo usar métricas
- Para dashboards de salud del sistema en tiempo real
- Para alertas: las métricas son eficientes y baratas de evaluar continuamente
- Para SLOs: los objetivos de fiabilidad se miden sobre métricas, no sobre traces
- Para tendencias a largo plazo: ¿el uso de memoria creció un 20% este mes?
La complementariedad con traces
Las métricas te dicen que algo está mal — “la tasa de error subió al 5%”. Los traces te dicen por qué está mal — “el 95% de esos errores vienen de usuarios del plan Enterprise llamando al endpoint /export”. Úsalos juntos.
3. Logs — ¿qué pasó exactamente en este momento concreto?
Los logs son registros textuales de eventos discretos con una marca de tiempo. Son el signal más antiguo y el más familiar, pero OpenTelemetry les añade algo crucial: correlación automática con traces.
Cuando emites un log dentro del contexto de un span activo, OpenTelemetry adjunta automáticamente el trace_id y el span_id al log. Esto transforma logs aislados en datos que puedes navegar directamente desde una traza.
// Dentro de un span activo, el logger hereda el contexto
val logger = LoggerFactory.getLogger(OrderService::class.java)
fun processOrder(orderId: String) {
tracer.spanBuilder("order.process").startSpan().use { span ->
span.setAttribute("order.id", orderId)
logger.info("Iniciando procesamiento de pedido") // trace_id adjunto automáticamente
val order = fetchOrder(orderId)
if (order.hasRiskFlag) {
logger.warn("Pedido marcado como riesgo alto",
kv("order.id", orderId),
kv("risk.score", order.riskScore)
)
}
logger.info("Pedido procesado correctamente",
kv("order.amount", order.total),
kv("payment.provider", order.paymentProvider)
)
}
}
Cuándo usar logs
- Para eventos discretos que necesitan descripción textual rica: errores con stack traces, eventos de negocio complejos
- Para contexto de depuración que no encaja en los atributos de un span
- Para auditoría: “el usuario X modificó el permiso Y a las 14:32:07”
- Para eventos de baja cardinalidad donde el texto libre es más expresivo que una métrica
El error más común con logs
Tratar los logs como en el año 2010: mensajes de texto sin estructura, sin correlación con otras señales, sin contexto de negocio. Un log bien diseñado en 2024 es estructurado (JSON), contiene el trace_id de la operación actual y tiene atributos tipados que se pueden filtrar.
4. Baggage — ¿cómo propago contexto a través de servicios?
El Baggage es el signal menos conocido y probablemente el más malentendido. No es telemetría en sí mismo — es un mecanismo de propagación de contexto que viaja junto a las peticiones a través de todo el sistema distribuido.
Piénsalo como metadatos que adjuntas a una petición en el borde de tu sistema y que están disponibles en cualquier servicio que participe en esa petición, sin que ningún servicio intermedio tenga que pasarlos explícitamente.
// En el API Gateway, al recibir la petición
val baggage = Baggage.current().toBuilder()
.put("user.tier", "enterprise")
.put("user.region", "eu-west")
.put("feature.flags", "new-checkout,beta-pricing")
.build()
// El baggage viaja automáticamente a todos los servicios downstream
// vía las cabeceras HTTP: baggage: user.tier=enterprise,user.region=eu-west
// En el servicio de pricing, 3 saltos más tarde
val userTier = Baggage.current().getEntryValue("user.tier")
val userRegion = Baggage.current().getEntryValue("user.region")
// No necesitas que nadie te lo haya pasado explícitamente
Cuándo usar Baggage
- Para propagar información de identidad del usuario (tier, región, ID) sin modificar todas las firmas de función
- Para propagar feature flags activos sin que cada servicio tenga que consultarlos
- Para enriquecer spans y logs en servicios downstream con contexto del origen de la petición
- Para implementar lógica de routing o priorización basada en atributos del cliente
La advertencia crítica
El Baggage tiene un coste de red: cada valor que metes viaja en las cabeceras HTTP de todas las peticiones de la cadena. No metas datos grandes ni sensibles. Mantén las claves cortas, los valores pequeños y los datos no-PII.
// MAL: no hagas esto
baggage.put("user.full_profile", userProfileJson) // pueden ser KB de datos
// BIEN: solo identificadores
baggage.put("user.id", userId)
baggage.put("user.tier", "enterprise")
5. Profiles — ¿qué está consumiendo los recursos de mi sistema?
Los Profiles (también llamados Continuous Profiling) son el signal más reciente en OpenTelemetry, actualmente en fase de especificación activa. Capturan el call stack del proceso a intervalos regulares, permitiéndote ver qué código está consumiendo CPU, memoria o tiempo de I/O.
A diferencia de los traces, que requieren instrumentación manual, el profiling trabaja a nivel del proceso: el profiler muestrea el estado del programa cada pocos milisegundos sin que tengas que modificar tu código.
CPU Profile — order-service — snapshot cada 10ms
├── 42% → OrderService.processOrder
│ ├── 28% → DatabaseClient.executeQuery
│ │ └── 28% → JDBC.prepareStatement ← cuello de botella real
│ └── 14% → PricingEngine.calculate
├── 35% → GC Threads ← presión de memoria
└── 23% → Netty I/O Threads
Cuándo usar Profiles
- Para encontrar dónde se consume realmente la CPU en producción, no en pruebas de carga sintéticas
- Para investigar regresiones de rendimiento sin un culpable obvio en las trazas
- Para optimizar el coste de infraestructura: ¿qué código está quemando más compute?
- Para correlacionar picos de CPU con trazas específicas — “este spike de CPU ocurrió durante estas trazas”
El estado actual de Profiles en OTel
La especificación de Profiles en OpenTelemetry está en desarrollo activo. Ya existen implementaciones funcionando (Parca, Pyroscope, Grafana Beyla), y el formato pprof es el estándar de facto hoy. La integración nativa con OTLP está llegando, lo que permitirá correlacionar profiles directamente con traces y logs en el mismo pipeline.
Cómo encajan los cinco signals
Ningún signal es mejor que los demás — son herramientas distintas para preguntas distintas. Una estrategia de observabilidad madura usa los cinco de forma complementaria:
| Pregunta | Signal |
|---|---|
| ¿Qué hizo el sistema para atender esta petición? | Traces |
| ¿Cuántas peticiones están fallando ahora mismo? | Metrics |
| ¿Qué pasó exactamente a las 14:32:07? | Logs |
| ¿Qué contexto tiene este servicio sobre el usuario? | Baggage |
| ¿Qué código está quemando la CPU en producción? | Profiles |
El flujo típico durante un incidente es: métrica te alerta de que algo va mal → traza te muestra qué operación está fallando → log te da el detalle exacto del error → profile te ayuda a entender si hay una regresión de rendimiento subyacente. El baggage, mientras tanto, asegura que el contexto del usuario viaje automáticamente por toda la cadena.
Conclusión
OpenTelemetry es una apuesta deliberada por la unificación: un solo SDK, un solo protocolo (OTLP), cinco signals. Antes de OTel, cada signal tenía su propio agente, su propio formato y su propio vendor. Hoy puedes instrumentar tu sistema una vez y enviar todos los tipos de telemetría al backend que elijas.
Pero la unificación técnica no te regala la estrategia. Sigue siendo tu trabajo decidir qué signals activar, con qué granularidad instrumentar y qué preguntas necesitas que tu sistema sea capaz de responder a las 3am. Empieza por las preguntas, no por los signals.
Si quieres profundizar en cómo diseñar spans que realmente sirvan, te recomiendo leer también por qué tu setup de OpenTelemetry genera spans inútiles.