OpenTelemetry (OTel) es una especificación neutral para el proveedor y un conjunto de bibliotecas para producir telemetría (trazas, métricas y registros) en un modelo de datos común, de modo que el código de instrumentación no tenga que reescribirse para el agente propietario de cada proveedor de APM.
Conceptos Básicos de Observabilidad ya introdujo los tres pilares y mostró fragmentos de OTel para cada uno; esta página profundiza un nivel más en OTel en sí mismo: cómo está estructurado internamente, por qué la API y el SDK se dividen en paquetes separados y cómo la identidad de una única solicitud se enlaza a través de cada span que toca.
Comprender este modelo cambia la forma en que lees la documentación y el código de OTel: la mayor parte de la confusión sobre "por qué mi span no aparece" o "por qué necesito ambos paquetes" se remonta a no tener aún esta estructura en mente.
OpenTelemetry define la telemetría como un conjunto de señales (trazas, métricas, registros) producidas a través de una API estable y neutral para el proveedor, procesadas y exportadas por un SDK configurado por separado, de modo que la instrumentación y la elección del backend estén desacopladas.
Por Qué Importa: Sin un modelo compartido, cada proveedor de APM requiere su propia instrumentación propietaria, lo que vincula tu código a un backend específico y duplica el esfuerzo en cada servicio y lenguaje que ejecutas.
Cuándo Usar Este Modelo: Instrumentar un nuevo servicio para el trazado distribuido, decidir entre instrumentación automática y manual, cablear una tubería de exportador/colector y depurar por qué los spans de dos servicios no se vinculan en una sola traza.
Limitaciones / Compromisos: El SDK agrega sobrecarga de inicio y una superficie de dependencia real; la división API/SDK es fácil de entender al revés (incluir el SDK donde solo se necesita la API); los atributos de span sin restricciones y el muestreo completo se vuelven costosos rápidamente a escala.
Temas Relacionados: métricas importantes (el método RED), SLOs y presupuestos de errores, selección de herramientas APM, el modelo de observabilidad más amplio.
La telemetría, en el vocabulario de OTel, se presenta en tres señales: trazas (la ruta y el tiempo de una operación a través de un sistema), métricas (números agregados a lo largo del tiempo, como recuentos de solicitudes o histogramas de latencia) y registros (eventos discretos con marca de tiempo).
Antes de OpenTelemetry, cada una de estas típicamente requería un SDK separado y específico del proveedor (un trazador de Datadog, un cliente de Prometheus, un registrador de logs propietario), cada uno con su propia API de instrumentación, lo que significaba que el código escrito para un backend tenía que reescribirse para cambiar a otro.
La contribución principal de OTel es estandarizar la forma de estos datos y la API utilizada para producirlos, mientras que deja a dónde van como una preocupación conectable e intercambiable.
La unidad fundamental de trazado es el span: una única operación con nombre, con una hora de inicio, una hora de finalización, un conjunto de atributos clave-valor y un estado.
Una traza no es una lista plana de spans, es un árbol, donde cada span (excepto la raíz) tiene exactamente un padre, formando una jerarquía que refleja cómo se anidó realmente el trabajo.
Esa forma (un span raíz, hijos que se ramifican para cada suboperación, algunos hijos que abarcan servicios completamente diferentes) es lo que permite que una interfaz de usuario de trazado reconstruya "dónde fueron realmente los 180ms" en lugar de solo "esta solicitud tardó 180ms".
Un recurso son metadatos que describen qué produjo la telemetría (nombre del servicio, versión, entorno de implementación), adjuntos una vez por proceso en lugar de repetirse en cada span, de modo que un backend pueda agrupar y filtrar la telemetría por servicio sin que cada span lleve campos redundantes.
La división entre la API (@opentelemetry/api) y el SDK (@opentelemetry/sdk-node) es la decisión estructural más importante en OTel, y es deliberada en lugar de incidental.
Los autores de bibliotecas (las personas que escriben un controlador de base de datos o un cliente HTTP que otras personas importarán) dependen solo del paquete de la API.
La API es una interfaz estable, en su mayoría sin operación: llamar a tracer.startSpan() sin un SDK registrado simplemente no hace nada y regresa inmediatamente, a un costo insignificante.
Los autores de aplicaciones registran un SDK una vez, en su propio punto de entrada, que es lo que realmente convierte esas llamadas sin operación en spans reales que se procesan y exportan a algún lugar.
Esta división existe para que la instrumentación de una biblioteca no imponga un backend, exportador o política de muestreo específicos a cada aplicación que la importa: la biblioteca simplemente describe lo que sucedió; la aplicación decide qué hacer con esa descripción.
La instrumentación se presenta en dos tipos, y se complementan en lugar de competir.
La instrumentación automática funciona parcheando módulos conocidos (http, express, controladores de bases de datos comunes) en el momento de la carga del módulo, envolviendo sus componentes internos para crear automáticamente spans para solicitudes, consultas y llamadas salientes sin ningún cambio de código en tus propios manejadores.
Es por eso que el archivo de registro de instrumentación debe ser la primera importación en tu punto de entrada: si express se importa antes de que el paquete de instrumentación automática lo haya parcheado, el parche no tiene nada más que interceptar.
La instrumentación manual es cuando llamas explícitamente a tracer.startActiveSpan() alrededor de una parte de la lógica de negocio que la instrumentación automática no puede ver, porque no tiene forma de conocer tu dominio: "capturó un pago", "aplicó un descuento", "concilió un pedido".
// propagación de contexto, ilustrada: el span hijo hereda el ID de traza del padre// automáticamente porque startActiveSpan ejecuta la devolución de llamada dentro del contexto de ese spantracer.startActiveSpan("captureOrder", async (parent) => { // cualquier span iniciado dentro de esta devolución de llamada se convierte en un hijo de `parent`, // incluso a través de un límite asíncrono esperado - esto es lo que significa "contexto activo" await tracer.startActiveSpan("chargeCard", async (child) => { child.end(); }); parent.end();});
La propagación de contexto es el mecanismo que mantiene una traza coherente a través de los límites del proceso: los identificadores del span activo se serializan en un encabezado HTTP saliente (traceparent, según el estándar W3C Trace Context) al salir, y se deserializan de nuevo en un contexto activo en el servicio receptor, que es exactamente lo que permite que una traza abarque dos procesos de Node completamente independientes y aún se represente como un árbol conectado.
El muestreo decide qué trazas se registran y exportan realmente, y es un compromiso de ingeniería real, no una nota al pie: registrar cada span para cada solicitud con mucho tráfico es costoso de almacenar y consultar, pero muestrear de forma demasiado agresiva significa que la única traza que necesitabas durante un incidente nunca fue capturada.
El muestreo basado en la cabecera decide al inicio de una traza, de forma económica, antes de saber si algo saldrá mal; el muestreo basado en la cola espera hasta que una traza se completa y puede decidir "mantener esta, fue lenta o tuvo un error" con una señal mucho mejor, a costa de necesitar almacenar spans en algún lugar (generalmente un colector) antes de que se tome la decisión de muestreo.
La cardinalidad es una preocupación tanto para los spans como para los registros: adjuntar un ID de usuario sin procesar, un cuerpo de solicitud completo o cualquier valor efectivamente ilimitado como atributo de span infla el costo de almacenamiento y consulta en los modelos de precios de la mayoría de los backends, razón por la cual las convenciones semánticas de OTel impulsan un vocabulario fijo y conocido de nombres de atributos (http.status_code, db.system) en lugar de campos de forma libre.
El colector (un proceso independiente que recibe telemetría OTLP, puede agrupar, filtrar y reexportarla a uno o más backends) desacopla aún más tu aplicación de cualquier proveedor único: tu servicio exporta a un colector local a través de OTLP, y la configuración del colector (no el código de tu aplicación) decide si esos datos terminan en Datadog, Honeycomb, Grafana Tempo o varios de ellos a la vez.
Aquí es también hacia donde se dirige la evolución de OTel: la señal de Logs está madurando para coexistir con las trazas y métricas bajo el mismo modelo de recurso y contexto, de modo que una línea de registro, un span y una métrica sobre la misma solicitud comparten cada vez más el mismo trace_id sin un mecanismo de correlación separado añadido después.
Estrategia de Muestreo
Fortaleza
Debilidad
Mejor Ajuste
Basado en la cabecera (tasa fija)
Simple, económico, decidido instantáneamente al inicio de la traza
Puede perder las trazas lentas/fallidas exactas que realmente deseas
Servicios de alto volumen donde una muestra representativa es suficiente
Basado en la cola (post-hoc)
Puede garantizar que las trazas con errores/lentas siempre se conserven
Requiere almacenar spans en un colector antes de decidir; más infraestructura
Servicios donde cada traza relevante para un incidente importa
Siempre activo (sin muestreo)
Nunca se pierde nada
El costo de almacenamiento/consulta escala linealmente con el tráfico
Servicios de bajo tráfico o ventanas de depuración de corta duración
"OpenTelemetry es un producto APM que instalo." Es un estándar de instrumentación y un conjunto de bibliotecas para producir telemetría; aún necesitas apuntar un exportador a un backend (un colector, un APM SaaS, un almacén de código abierto) para ver algo.
"La instrumentación automática captura todo lo que necesito." Cubre bibliotecas y protocolos conocidos (HTTP, controladores de DB comunes), pero no tiene visibilidad de tu propia lógica de negocio: las operaciones específicas del dominio necesitan spans manuales para aparecer en una traza.
"Importar @opentelemetry/api configura exportadores o comienza a enviar datos." La API por sí sola es una interfaz estable y en su mayoría sin operación; nada se registra ni se exporta hasta que una aplicación registra un SDK, lo cual es una elección de diseño deliberada para mantener las bibliotecas agnósticas al backend.
"Más spans siempre significa mejor observabilidad." A partir de cierto punto, la creación excesiva de spans y los atributos de alta cardinalidad aumentan el costo de almacenamiento y consulta sin agregar un valor de depuración proporcional; la misma disciplina de cardinalidad que se aplica a los registros se aplica a los spans.
"Las trazas hacen que los registros y las métricas sean innecesarios." Cada señal responde a una pregunta diferente: las trazas muestran el tiempo y la estructura causal, las métricas muestran tendencias y agregados, los registros contienen detalles arbitrarios específicos, y el modelo de OTel está explícitamente diseñado para correlacionar los tres, no para reemplazar dos de ellos con el tercero.
Una especificación neutral para el proveedor y un conjunto de bibliotecas para producir trazas, métricas y registros en un modelo de datos común, de modo que el código de instrumentación no esté vinculado a un backend de observabilidad específico.
¿Por qué la API y el SDK son paquetes npm separados en lugar de uno solo?
Para que los autores de bibliotecas puedan instrumentar su código (un controlador de base de datos, un cliente HTTP) sin obligar a cada aplicación que lo importa a incluir también un exportador, muestreador o configuración de backend específicos. La API es estable y en su mayoría sin operación por sí misma; el SDK es lo que una aplicación registra una vez para convertir la instrumentación en telemetría registrada y exportada.
¿Cómo se estructura realmente una "traza" internamente?
Como un árbol de spans, no una lista plana: cada span (excepto la raíz) tiene exactamente un padre, formando una jerarquía que refleja cómo se anidó el trabajo subyacente, incluido el trabajo que cruzó a servicios completamente diferentes.
¿Cómo se propaga realmente el contexto a través de dos procesos de Node separados?
Los identificadores del span activo se serializan en un encabezado saliente (traceparent, según el estándar W3C Trace Context) cuando tu servicio realiza una llamada saliente, y el servicio receptor deserializa ese encabezado de nuevo en un contexto activo antes de crear su propio span hijo; esto es lo que permite que una única traza abarque múltiples procesos independientes.
¿Por qué el archivo de registro de instrumentación debe importarse primero?
Porque la instrumentación automática funciona parcheando módulos (como express o http) en el momento en que se cargan; si el módulo que se está parcheando se importa antes de que se haya ejecutado el paquete de instrumentación, el parche no tiene nada más que interceptar y no se crean spans para ese módulo.
¿Cuándo necesito instrumentación manual en lugar de depender de la instrumentación automática?
Siempre que la operación que te interesa sea lógica de negocio en lugar de una llamada a una biblioteca conocida: la instrumentación automática solo conoce los protocolos y controladores que está específicamente diseñada para parchear, por lo que no tiene forma de saber que "aplicar un descuento" o "conciliar un pedido" es una unidad de trabajo significativa que merece su propio span.
¿Cuál es la diferencia entre el muestreo basado en la cabecera y el muestreo basado en la cola?
El muestreo basado en la cabecera es económico y simple, pero decide antes de saber si una traza resultó interesante, por lo que puede perder exactamente las trazas lentas o con errores que desearías durante un incidente. El muestreo basado en la cola espera hasta que una traza se completa y puede mantener de forma fiable las interesantes, pero requiere almacenar spans en un colector, lo que añade infraestructura.
¿Siempre vale la pena ejecutar OpenTelemetry por la sobrecarga que añade?
No automáticamente: el SDK añade tiempo de inicio y una huella de dependencia no trivial, y la instrumentación sin restricciones (demasiados spans, atributos de alta cardinalidad) puede añadir un costo real sin una visión proporcional. Se amortiza más claramente una vez que un servicio tiene suficientes llamadas o complejidad descendentes como para que "dónde se fue el tiempo" no se pueda responder solo con los registros.
¿Qué significa un "recurso" en OpenTelemetry, y por qué no es solo otro atributo de span?
Un recurso describe qué produjo la telemetría (nombre del servicio, versión, entorno), adjunto una vez por proceso en lugar de repetirse en cada span. Mantenerlo separado de los atributos por span permite a los backends agrupar y filtrar por servicio de forma económica, sin que cada span lleve campos redundantes.
¿Cuál es el objetivo de ejecutar un colector en lugar de exportar directamente a un proveedor?
Un colector recibe telemetría a través de OTLP y puede agrupar, filtrar y reexportarla a uno o más backends basándose en su propia configuración, no en el código de tu aplicación. Esto desacopla tu servicio de cualquier proveedor único: cambiar o añadir un backend se convierte en un cambio de configuración del colector, no en una nueva implementación.
¿Por qué los nombres de los atributos de span siguen un vocabulario fijo como `http.status_code`?
Las convenciones semánticas de OpenTelemetry definen nombres de atributos estándar para conceptos comunes específicamente para que las herramientas y los paneles de control creados para un servicio funcionen de la misma manera para cualquier otro servicio instrumentado con OTel, en lugar de que cada equipo invente sus propios nombres de campo para los mismos datos.
¿La adopción de OpenTelemetry significa que ya no necesito un registrador estructurado como Pino?
No, la señal de Logs de OTel está estandarizando cómo los registros estructurados se correlacionan con las trazas y métricas a través de identificadores compartidos, pero aún emites registros a través de una biblioteca como Pino; los dos funcionan juntos en lugar de que uno reemplace al otro.