Skip to content
Volver al blog

Observabilidad con OpenTelemetry: por dónde empezar

Qué son las trazas, métricas y logs, para qué sirve el Collector y cómo instrumentar, muestrear y nombrar servicios con OpenTelemetry. Incluye una configuración del Collector, un ejemplo en Python, un plan por etapas y una lista de verificación.

Un cliente reclama que el pago tarda. En el servidor de aplicaciones hay logs, en la base de datos hay métricas y en el balanceador hay otra consola. Nada conecta una petición lenta con la consulta que la frenó, y cada herramienta usa sus propios agentes y formatos. Cambiar de proveedor obliga a instrumentar todo de nuevo.

OpenTelemetry (OTel) resuelve la parte de la recolección: define una API, SDK por lenguaje, un protocolo (OTLP) y un servicio intermedio, el Collector, para generar y enviar trazas, métricas y logs a cualquier backend compatible. La CNCF lo describe como un marco de observabilidad neutral respecto de proveedores y lo pasó a nivel Graduated en mayo de 2026.

Esta guía explica cada señal, el Collector, la instrumentación y el control de costos, y propone un plan por etapas. Las versiones citadas son las vigentes al 4 de noviembre de 2026.

Tres señales y cómo se conectan

OpenTelemetry define las señales como salidas del sistema que describen su actividad. Las tres principales son:

  • Trazas. El recorrido de una petición por tu aplicación. Una traza se compone de spans, cada uno una unidad de trabajo, con un trace ID común y un span ID propio. El span tiene atributos (pares clave-valor) y un estado: Unset por omisión, Error u Ok.
  • Métricas. Mediciones tomadas en ejecución: contadores, gauges e histogramas, entre otros.
  • Logs. El registro de un evento. OpenTelemetry no te pide cambiar tu biblioteca de logging: se conecta a ella mediante puentes (bridges).

La correlación es lo que hace útil al conjunto. Cuando hay un span activo, el SDK agrega a cada registro de log los campos TraceId y SpanId, y desde un log puedes saltar a la traza de esa petición. En métricas, los exemplars asocian un punto de datos agregado con el trace ID y el span ID de una medición concreta. Entre servicios, el contexto viaja en cabeceras: por omisión, los SDK usan los propagadores tracecontext (W3C Trace Context) y baggage.

Para empezar a medir, el libro de SRE de Google propone cuatro señales doradas para sistemas de cara al usuario: latencia, tráfico, errores y saturación. Recomienda distinguir la latencia de las peticiones exitosas de la de las fallidas.

El Collector: una pieza entre tus apps y el backend

El Collector recibe, procesa y exporta telemetría sin atarte a un proveedor. Le quita trabajo a la aplicación: reintentos, agrupación en lotes, cifrado y filtrado de datos sensibles. La documentación recomienda usar un Collector junto a tus servicios; enviar directo al backend sirve para empezar o para desarrollo.

En su configuración, los receivers reciben datos, los processors los modifican en el orden en que los listas y los exporters los envían a uno o más destinos. En service.pipelines defines un pipeline por señal. Un componente configurado que no aparece en service no se activa.

Este ejemplo sirve para un Collector central en una empresa pequeña. Usa la distribución otelcol-contrib, porque el procesador transform no viene en la distribución básica.

receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318

processors:
  memory_limiter:            # siempre primero
    check_interval: 1s
    limit_mib: 512
    spike_limit_mib: 128
  transform/sensibles:       # borra un atributo que no debe salir de la app
    error_mode: ignore
    trace_statements:
      - delete_key(span.attributes, "cliente.rut")
    log_statements:
      - delete_key(log.attributes, "cliente.rut")

exporters:
  otlp_grpc/trazas:          # p. ej., Jaeger
    endpoint: jaeger:4317
    tls:
      insecure: true         # solo dentro de una red privada
    sending_queue:
      batch: {}
  otlp_http/metricas:        # p. ej., Prometheus con receptor OTLP
    endpoint: http://prometheus:9090/api/v1/otlp
    sending_queue:
      batch: {}
  otlp_http/logs:
    endpoint: http://backend-logs:4318
    sending_queue:
      batch: {}

extensions:
  health_check:
    endpoint: 0.0.0.0:13133

service:
  extensions: [health_check]
  pipelines:
    traces:
      receivers: [otlp]
      processors: [memory_limiter, transform/sensibles]
      exporters: [otlp_grpc/trazas]
    metrics:
      receivers: [otlp]
      processors: [memory_limiter]
      exporters: [otlp_http/metricas]
    logs:
      receivers: [otlp]
      processors: [memory_limiter, transform/sensibles]
      exporters: [otlp_http/logs]

Cinco detalles de la versión vigente, la 0.164:

  • Desde la 0.144, el exportador otlp se llama otlp_grpc y otlphttp pasó a otlp_http. Los nombres antiguos siguen como alias obsoletos; conviene migrar.
  • Los lotes se arman en el propio exportador con sending_queue y batch: {}. El proyecto ya publicó el procesador queue_batch, que reemplaza al batch heredado; está en beta y todavía no viene en las distribuciones oficiales.
  • El memory_limiter va primero en cada pipeline, y su documentación recomienda fijar además la variable GOMEMLIMIT en todo Collector.
  • El Collector escucha en localhost por omisión. 0.0.0.0 lo expone a toda la red; úsalo solo si otras máquinas deben enviarle datos, y protege ese acceso.
  • Valida el archivo con otelcol-contrib validate --config=config.yaml.

Hay dos formas básicas de desplegarlo. Como agente, en el mismo host que la aplicación: fácil de empezar, pero escala mal. Como gateway, un punto central: concentra credenciales y políticas de filtrado o muestreo, a cambio de otra pieza que mantener, algo más de latencia y más recursos.

Instrumentación automática y manual

La instrumentación sin código (zero-code) captura telemetría de las bibliotecas y frameworks que ya usas, sin tocar tu código. La instrumentación en código agrega lo que solo tu aplicación sabe, como qué pedido falló y por qué. Puedes usar ambas a la vez.

Antes de elegir, revisa el estado de cada señal en tu lenguaje:

Lenguaje

Trazas

Métricas

Logs

Java

Stable

Stable

Stable

C#/.NET

Stable

Stable

Stable

PHP

Stable

Stable

Stable

Go

Stable

Stable

Release candidate

JavaScript

Stable

Stable

Development

Python

Stable

Stable

Development

Ruby

Stable

Development

Development

La misma página advierte que, sea cual sea ese estado, la instrumentación que use convenciones semánticas experimentales puede cambiar de forma incompatible.

Un ejemplo con Python. El SDK estable vigente es el 1.45.2 (requiere Python 3.10 o superior); el agente y las bibliotecas de instrumentación se publican todavía como beta, en la 0.66b2. opentelemetry-bootstrap revisa los paquetes instalados e instala la instrumentación que corresponda, por ejemplo la de Flask.

pip install opentelemetry-distro opentelemetry-exporter-otlp
opentelemetry-bootstrap -a install

export OTEL_SERVICE_NAME=tienda-api
export OTEL_RESOURCE_ATTRIBUTES=service.version=1.8.3,deployment.environment.name=production
export OTEL_EXPORTER_OTLP_PROTOCOL=grpc
export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317
export OTEL_TRACES_EXPORTER=otlp
export OTEL_METRICS_EXPORTER=otlp
export OTEL_LOGS_EXPORTER=otlp
opentelemetry-instrument python app.py

Con eso ya tienes spans de las peticiones HTTP y de las bibliotecas instrumentadas. Para agregar una operación de negocio, usa la API:

from opentelemetry import metrics, trace
from opentelemetry.trace import Status, StatusCode

tracer = trace.get_tracer("tienda.pedidos")
meter = metrics.get_meter("tienda.pedidos")
pedidos = meter.create_counter(
    "tienda.pedidos", unit="{pedido}", description="Pedidos procesados"
)

def procesar_pedido(pedido):
    # Si el bloque lanza una excepción, el span la registra y queda en Error.
    with tracer.start_as_current_span("procesar_pedido") as span:
        span.set_attribute("tienda.pedido.canal", pedido.canal)
        resultado = cobrar(pedido)
        if not resultado.aprobado:
            span.set_status(Status(StatusCode.ERROR, "pago rechazado"))
        pedidos.add(1, {"tienda.pedido.canal": pedido.canal,
                        "tienda.pago.aprobado": resultado.aprobado})

En Python, start_as_current_span registra la excepción y marca el span con error por omisión; no hace falta repetirlo. Los logs de Python siguen en Development: la autoinstrumentación de logs viene activa desde la 1.40.0, y OTEL_PYTHON_LOG_CORRELATION=true inserta el contexto de traza en los registros.

Muestreo, cardinalidad y costos

Por omisión, el SDK usa parentbased_always_on: guarda todas las trazas. La documentación sugiere no muestrear si generas poco volumen, del orden de decenas de trazas pequeñas por segundo o menos, y considerarlo desde unas 1.000 trazas por segundo.

  • Muestreo en la cabeza (head). Se decide al iniciar la traza. Es simple y eficiente, pero no puede garantizar que se guarden todas las trazas con error. Con OTEL_TRACES_SAMPLER=parentbased_traceidratio y OTEL_TRACES_SAMPLER_ARG=0.25 conservas una de cada cuatro trazas raíz y respetas la decisión del servicio que llama.
  • Muestreo en la cola (tail). Se decide con la traza completa, en el Collector. Permite guardar todos los errores y las peticiones lentas, a cambio de memoria y complejidad.
processors:
  tail_sampling:
    policies:
      - name: errores
        type: status_code
        status_code: {status_codes: [ERROR]}
      - name: lentas
        type: latency
        latency: {threshold_ms: 2000}
      - name: resto
        type: probabilistic
        probabilistic: {sampling_percentage: 10}

El procesador tail_sampling guarda los spans en memoria mientras decide: espera 30 segundos (decision_wait) y retiene hasta 50.000 trazas (num_traces) por omisión. Todos los spans de una traza deben llegar a la misma instancia. Con más de un Collector necesitas una capa previa con el exportador de balanceo de carga y routing_key: traceID, como describe el patrón gateway.

En métricas, el costo lo define la cardinalidad: el número de combinaciones distintas de atributos. Un ID de usuario o una ruta URL cruda como atributo hacen crecer la memoria sin límite. El SDK corta en 2.000 combinaciones por flujo de métricas y agrupa el resto bajo otel.metric.overflow=true; desde ahí, toda consulta que filtre por ese atributo cuenta de menos. Usa atributos de pocos valores, como el canal del ejemplo, y ajusta con Views lo que no necesitas.

Por último, minimiza los datos: recolecta solo lo que tenga un propósito de observabilidad y evita datos personales. El cumplimiento legal es responsabilidad de quien implementa, no de la herramienta.

Convenciones semánticas y atributos de recurso

Las convenciones semánticas fijan nombres comunes para operaciones y datos. Las de spans HTTP son estables y usan http.request.method, url.path, http.route y http.response.status_code. El nombre del span debe ser {método} {ruta} solo si la ruta es de baja cardinalidad, por ejemplo GET /pedidos/{id}; nunca la ruta URL cruda.

El recurso describe a quien produce la telemetría. El atributo clave es service.name: es obligatorio, debe ser igual en todas las réplicas y, si no lo fijas, el SDK usa unknown_service más el nombre del ejecutable. Agrega service.version y deployment.environment.name para separar producción de pruebas. Prometheus, por ejemplo, arma sus etiquetas job e instance a partir de service.name, service.namespace y service.instance.id.

Backends open source

Como OTLP es un estándar, cambiar de backend significa cambiar un exportador del Collector, no reinstrumentar. Algunas opciones open source que reciben OTLP de forma nativa:

  • Trazas: Jaeger, cuya versión 2 es una distribución del propio Collector y guarda datos en Cassandra, Elasticsearch, OpenSearch o ClickHouse, entre otros.
  • Métricas: Prometheus, con el receptor OTLP activado mediante --web.enable-otlp-receiver.
  • Logs y más: la lista del proyecto marca qué soluciones son open source y cuáles aceptan OTLP de forma nativa. No es exhaustiva y ya no recibe entradas nuevas.

Elige según lo que tu equipo pueda operar: cada backend es otro servicio que mantener.

Plan por etapas para una empresa pequeña

  1. Nombres primero. Define service.name, service.version y deployment.environment.name para cada servicio.
  2. Un Collector y debug. Despliega un Collector e instrumenta sin código uno o dos servicios críticos. El exportador debug muestra qué llega antes de elegir backend.
  3. Trazas y métricas. Conecta un backend de trazas y uno de métricas. Arma paneles y alertas con las cuatro señales doradas.
  4. Logs correlacionados. Envía los logs con TraceId y SpanId. Para logs que ya se escriben en archivos, el Collector trae un receptor de archivos.
  5. Spans de negocio. Instrumenta a mano solo donde la automática no basta.
  6. Control de volumen. Revisa la cardinalidad y decide si necesitas muestreo.

Si usas Kubernetes, la sección de observabilidad de Kubernetes en producción: 9 errores complementa este plan.

Lista de verificación

Área

Verificación

Recurso

service.name explícito en todos los servicios; ninguno aparece como unknown_service

Entorno

deployment.environment.name distingue producción de pruebas

Collector

memory_limiter primero, GOMEMLIMIT fijado y configuración validada

Red

Puertos 4317 y 4318 expuestos solo a quien debe enviar datos

Nombres

Exportadores sin alias obsoletos (otlp → otlp_grpc, otlphttp → otlp_http); el receptor otlp no cambia de nombre

Señales

Estado de trazas, métricas y logs revisado para cada lenguaje

Correlación

Logs con TraceId y SpanId; propagación W3C entre servicios

Cardinalidad

Sin IDs de usuario ni rutas URL crudas como atributos de métricas

Muestreo

Decisión escrita: sin muestreo, head o tail, según el volumen

Datos sensibles

Atributos con datos personales eliminados en el Collector

Alertas

Latencia, tráfico, errores y saturación de cada servicio crítico

Fuentes


Si quieres revisar por dónde partir con tu equipo, conversemos.

Comparte este post
Enviar por email

Sigue leyendo

Infraestructura

Guardias y postmortems sin culpa para equipos pequeños

11 min
Infraestructura

Kubernetes en producción: 9 errores y cómo evitarlos

11 min