OpenTelemetry: El Estándar de Observabilidad en 2026 — Guía Completa
¿Qué es la observabilidad y por qué importa?
La observabilidad es la capacidad de entender el estado interno de un sistema a partir de sus salidas externas. A diferencia del monitoreo tradicional, que responde preguntas predefinidas ("¿está arriba el servidor?"), la observabilidad te permite hacer preguntas que nunca anticipaste: "¿por qué este request específico tardó 12 segundos solo para usuarios de Colombia?"
Los tres pilares de la observabilidad son:
- Logs — Registros textuales de eventos discretos. Útiles para debugging puntual.
- Métricas — Valores numéricos agregados en el tiempo (latencia p99, tasa de errores, uso de CPU).
- Trazas (Traces) — El recorrido completo de una request a través de múltiples servicios.
El problema histórico es que cada pilar usaba herramientas distintas con formatos incompatibles. OpenTelemetry resuelve exactamente esto: un estándar unificado para los tres pilares.
¿Qué es OpenTelemetry (OTel)?
OpenTelemetry es un framework de observabilidad open source y vendor-neutral que proporciona APIs, SDKs y herramientas para generar, recopilar y exportar datos de telemetría (trazas, métricas y logs). Nació en 2019 de la fusión de dos proyectos: OpenTracing y OpenCensus.
La propuesta de valor es clara: instrumentas tu código una sola vez con OTel y puedes enviar los datos a cualquier backend — Jaeger, Grafana Tempo, Datadog, New Relic, Prometheus, Elastic — sin cambiar tu código.

Componentes principales de OTel
- API — Define las interfaces para instrumentación (Tracer, Meter, Logger). Es estable y segura para librerías.
- SDK — Implementación de la API con procesamiento, sampling y exportación.
- OTel Collector — Agente independiente que recibe, procesa y exporta telemetría.
- Exporters — Plugins que envían datos al backend final (OTLP, Jaeger, Prometheus, etc.).
- Auto-instrumentación — Agentes que instrumentan frameworks populares sin cambiar código.
Arquitectura del OpenTelemetry Collector
El OTel Collector es el corazón de cualquier pipeline de observabilidad con OpenTelemetry. Actúa como un proxy inteligente entre tus aplicaciones y los backends de almacenamiento.
Su arquitectura se basa en tres componentes conectados en pipeline:
- Receivers — Reciben datos en múltiples formatos (OTLP, Jaeger, Zipkin, Prometheus).
- Processors — Transforman, filtran y enriquecen los datos (batch, memory limiter, attributes).
- Exporters — Envían los datos al destino final (OTLP, Prometheus, Loki, etc.).
1# otel-collector-config.yaml
2receivers:
3 otlp:
4 protocols:
5 grpc:
6 endpoint: 0.0.0.0:4317
7 http:
8 endpoint: 0.0.0.0:4318
9
10processors:
11 batch:
12 timeout: 5s
13 send_batch_size: 1024
14 memory_limiter:
15 check_interval: 1s
16 limit_mib: 512
17 spike_limit_mib: 128
18 attributes:
19 actions:
20 - key: environment
21 value: production
22 action: upsert
23
24exporters:
25 otlp/tempo:
26 endpoint: tempo:4317
27 tls:
28 insecure: true
29 prometheus:
30 endpoint: 0.0.0.0:8889
31 namespace: myapp
32 loki:
33 endpoint: http://loki:3100/loki/api/v1/push
34
35service:
36 pipelines:
37 traces:
38 receivers: [otlp]
39 processors: [memory_limiter, batch, attributes]
40 exporters: [otlp/tempo]
41 metrics:
42 receivers: [otlp]
43 processors: [memory_limiter, batch]
44 exporters: [prometheus]
45 logs:
46 receivers: [otlp]
47 processors: [memory_limiter, batch]
48 exporters: [loki]
memory_limiter primero en la cadena. Esto evita que un pico de telemetría haga crash al Collector por falta de memoria.
Auto-instrumentación en Java (Spring Boot)
Una de las grandes ventajas de OTel es la auto-instrumentación: puedes obtener trazas completas de tu aplicación Spring Boot sin escribir una sola línea de código de instrumentación. Solo necesitas adjuntar el agente Java.
1# Dockerfile para Spring Boot con OTel Agent
2FROM eclipse-temurin:21-jre-alpine
3
4WORKDIR /app
5
6# Descargar el agente de OpenTelemetry
7ADD https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/latest/download/opentelemetry-javaagent.jar /app/otel-agent.jar
8
9COPY target/my-api-0.0.1-SNAPSHOT.jar /app/app.jar
10
11ENV OTEL_SERVICE_NAME=my-spring-api
12ENV OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4317
13ENV OTEL_EXPORTER_OTLP_PROTOCOL=grpc
14ENV OTEL_TRACES_SAMPLER=parentbased_traceidratio
15ENV OTEL_TRACES_SAMPLER_ARG=0.5
16ENV OTEL_METRICS_EXPORTER=otlp
17ENV OTEL_LOGS_EXPORTER=otlp
18ENV OTEL_RESOURCE_ATTRIBUTES=deployment.environment=production,service.version=1.2.0
19
20EXPOSE 8080
21
22ENTRYPOINT ["java", "-javaagent:/app/otel-agent.jar", "-jar", "/app/app.jar"]
Con esto, el agente automáticamente captura:
- Todas las requests HTTP entrantes (Spring MVC / WebFlux)
- Llamadas a bases de datos (JDBC, R2DBC)
- Requests HTTP salientes (RestTemplate, WebClient, HttpClient)
- Mensajería (Kafka, RabbitMQ, SQS)
- Caché (Redis, Caffeine)
parentbased_traceidratio con arg 0.5 solo muestrea el 50% de las trazas raíz. En producción esto reduce costos significativamente, pero asegúrate de que trazas de errores siempre se capturen configurando un sampler personalizado.
Instrumentación manual en TypeScript/Node.js
Para aplicaciones Node.js, la auto-instrumentación cubre Express, Fastify, bases de datos y más. Pero muchas veces necesitas instrumentación manual para capturar lógica de negocio específica.
1// src/tracing.ts — Configuración de OpenTelemetry para Node.js
2import { NodeSDK } from '@opentelemetry/sdk-node';
3import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-grpc';
4import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-grpc';
5import { PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics';
6import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
7import { Resource } from '@opentelemetry/resources';
8import {
9 ATTR_SERVICE_NAME,
10 ATTR_SERVICE_VERSION,
11} from '@opentelemetry/semantic-conventions';
12
13const sdk = new NodeSDK({
14 resource: new Resource({
15 [ATTR_SERVICE_NAME]: 'order-service',
16 [ATTR_SERVICE_VERSION]: '2.1.0',
17 'deployment.environment': process.env.NODE_ENV || 'development',
18 }),
19 traceExporter: new OTLPTraceExporter({
20 url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT || 'http://localhost:4317',
21 }),
22 metricReader: new PeriodicExportingMetricReader({
23 exporter: new OTLPMetricExporter({
24 url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT || 'http://localhost:4317',
25 }),
26 exportIntervalMillis: 15000,
27 }),
28 instrumentations: [
29 getNodeAutoInstrumentations({
30 '@opentelemetry/instrumentation-fs': { enabled: false },
31 }),
32 ],
33});
34
35sdk.start();
36console.log('OpenTelemetry SDK iniciado');
37
38process.on('SIGTERM', () => {
39 sdk.shutdown().then(() => console.log('OTel SDK apagado'));
40});
Ahora veamos cómo agregar spans personalizados para lógica de negocio:
1// src/services/order.service.ts
2import { trace, SpanStatusCode, metrics } from '@opentelemetry/api';
3
4const tracer = trace.getTracer('order-service', '2.1.0');
5const meter = metrics.getMeter('order-service', '2.1.0');
6
7// Métricas personalizadas
8const orderCounter = meter.createCounter('orders.created', {
9 description: 'Total de órdenes creadas',
10});
11const orderDuration = meter.createHistogram('orders.processing_duration_ms', {
12 description: 'Duración de procesamiento de orden en ms',
13 unit: 'ms',
14});
15
16export async function createOrder(userId: string, items: CartItem[]) {
17 return tracer.startActiveSpan('createOrder', async (span) => {
18 const start = Date.now();
19
20 try {
21 span.setAttribute('user.id', userId);
22 span.setAttribute('order.item_count', items.length);
23
24 // Validar inventario
25 const available = await tracer.startActiveSpan('validateInventory', async (child) => {
26 const result = await inventoryService.checkAvailability(items);
27 child.setAttribute('inventory.all_available', result.allAvailable);
28 child.end();
29 return result;
30 });
31
32 if (!available.allAvailable) {
33 span.setStatus({ code: SpanStatusCode.ERROR, message: 'Inventario insuficiente' });
34 throw new InsufficientInventoryError(available.missing);
35 }
36
37 // Procesar pago
38 const payment = await tracer.startActiveSpan('processPayment', async (child) => {
39 const total = items.reduce((sum, i) => sum + i.price * i.qty, 0);
40 child.setAttribute('payment.amount', total);
41 child.setAttribute('payment.currency', 'MXN');
42 const result = await paymentService.charge(userId, total);
43 child.end();
44 return result;
45 });
46
47 span.setAttribute('order.payment_id', payment.id);
48 orderCounter.add(1, { status: 'success', region: 'mx' });
49
50 return { orderId: payment.orderId, status: 'confirmed' };
51
52 } catch (error) {
53 span.setStatus({ code: SpanStatusCode.ERROR });
54 span.recordException(error as Error);
55 orderCounter.add(1, { status: 'error', region: 'mx' });
56 throw error;
57
58 } finally {
59 orderDuration.record(Date.now() - start);
60 span.end();
61 }
62 });
63}
Propagación de contexto (W3C Trace Context)
La propagación de contexto es lo que permite que una traza cruce fronteras entre servicios. Cuando el Servicio A llama al Servicio B, necesita pasar el trace_id y span_id para que B cree spans hijos de A.
OpenTelemetry usa el estándar W3C Trace Context por defecto, que define dos headers HTTP:
traceparent— Contiene version, trace-id, parent-id y trace-flagstracestate— Metadata adicional vendor-specific
Ejemplo de un header traceparent:
1traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
2# v trace-id (32 hex) parent-id (16 hex) flags

Baggage API
Además del contexto de traza, OTel proporciona la Baggage API para propagar datos de negocio entre servicios sin acoplarlos. Por ejemplo, propagar el tenant_id o user_tier para que todos los servicios downstream lo usen en sus métricas.
OTel en Kubernetes: DaemonSet vs Sidecar
Cuando despliegas el OTel Collector en Kubernetes, tienes dos patrones principales:
Patrón DaemonSet
Un Collector por nodo. Todos los pods del nodo envían su telemetría al Collector local. Es más eficiente en recursos porque compartes un solo Collector entre muchos pods.
Patrón Sidecar
Un Collector por pod. Cada pod tiene su propio Collector como container sidecar. Ofrece mejor aislamiento pero consume más recursos.
1# kubernetes/otel-collector-daemonset.yaml
2apiVersion: apps/v1
3kind: DaemonSet
4metadata:
5 name: otel-collector
6 namespace: observability
7spec:
8 selector:
9 matchLabels:
10 app: otel-collector
11 template:
12 metadata:
13 labels:
14 app: otel-collector
15 spec:
16 containers:
17 - name: collector
18 image: otel/opentelemetry-collector-contrib:0.97.0
19 ports:
20 - containerPort: 4317 # gRPC OTLP
21 hostPort: 4317
22 - containerPort: 4318 # HTTP OTLP
23 hostPort: 4318
24 - containerPort: 8889 # Prometheus metrics
25 volumeMounts:
26 - name: config
27 mountPath: /etc/otelcol-contrib
28 resources:
29 requests:
30 cpu: 200m
31 memory: 256Mi
32 limits:
33 cpu: 500m
34 memory: 512Mi
35 livenessProbe:
36 httpGet:
37 path: /
38 port: 13133
39 readinessProbe:
40 httpGet:
41 path: /
42 port: 13133
43 volumes:
44 - name: config
45 configMap:
46 name: otel-collector-config
47---
48apiVersion: v1
49kind: Service
50metadata:
51 name: otel-collector
52 namespace: observability
53spec:
54 type: ClusterIP
55 selector:
56 app: otel-collector
57 ports:
58 - name: otlp-grpc
59 port: 4317
60 - name: otlp-http
61 port: 4318
62 - name: metrics
63 port: 8889
Comparación con soluciones propietarias
¿Por qué elegir OTel sobre soluciones como Datadog, New Relic o Dynatrace? Aquí una comparación honesta:
| Aspecto | OpenTelemetry | Datadog / New Relic |
|---|---|---|
| Costo | Gratuito (open source) | $15-35/host/mes + ingesta |
| Vendor lock-in | Ninguno — cambia backend sin cambiar código | Alto — SDKs propietarios |
| Setup inicial | Mayor complejidad — debes gestionar Collector y backends | Rápido — SaaS listo para usar |
| Dashboards | Tú los construyes (Grafana) | Predefinidos y potentes |
| Alertas | Configuras con Alertmanager o Grafana | Integradas con ML |
| Soporte | Comunidad + docs | Soporte empresarial 24/7 |
| Escala | Sin límites (tú gestionas infra) | Costos crecen linealmente |
Métricas personalizadas con OTel
OTel soporta tres tipos de instrumentos de métricas:
- Counter — Valor que solo incrementa (requests totales, errores totales)
- Histogram — Distribución de valores (latencia, tamaño de payload)
- Gauge — Valor puntual que sube y baja (conexiones activas, temperatura)
1// MetricsConfig.java — Métricas personalizadas en Spring Boot
2import io.opentelemetry.api.GlobalOpenTelemetry;
3import io.opentelemetry.api.metrics.LongCounter;
4import io.opentelemetry.api.metrics.DoubleHistogram;
5import io.opentelemetry.api.metrics.Meter;
6import io.opentelemetry.api.common.Attributes;
7import io.opentelemetry.api.common.AttributeKey;
8import org.springframework.stereotype.Component;
9
10@Component
11public class OrderMetrics {
12
13 private final LongCounter ordersCreated;
14 private final DoubleHistogram orderProcessingTime;
15 private final LongCounter paymentFailures;
16
17 public OrderMetrics() {
18 Meter meter = GlobalOpenTelemetry.getMeter("com.myapp.orders", "1.0.0");
19
20 this.ordersCreated = meter.counterBuilder("app.orders.created")
21 .setDescription("Número total de órdenes creadas")
22 .setUnit("{orders}")
23 .build();
24
25 this.orderProcessingTime = meter.histogramBuilder("app.orders.processing_time")
26 .setDescription("Tiempo de procesamiento de orden")
27 .setUnit("ms")
28 .build();
29
30 this.paymentFailures = meter.counterBuilder("app.payments.failures")
31 .setDescription("Número total de fallos de pago")
32 .setUnit("{failures}")
33 .build();
34 }
35
36 public void recordOrderCreated(String region, String tier) {
37 ordersCreated.add(1, Attributes.of(
38 AttributeKey.stringKey("region"), region,
39 AttributeKey.stringKey("customer.tier"), tier
40 ));
41 }
42
43 public void recordProcessingTime(long durationMs) {
44 orderProcessingTime.record(durationMs);
45 }
46
47 public void recordPaymentFailure(String reason) {
48 paymentFailures.add(1, Attributes.of(
49 AttributeKey.stringKey("failure.reason"), reason
50 ));
51 }
52}
Mejores prácticas para producción
Después de implementar OTel en múltiples proyectos, estas son las prácticas que marcan la diferencia:
- Usa sampling inteligente — No captures el 100% de las trazas. Usa
parentbased_traceidratiocon 10-50% para tráfico normal, y 100% para errores. - Nombra tus spans con convenciones semánticas — Usa los Semantic Conventions de OTel:
http.server.request,db.query, etc. - Agrega resource attributes — Siempre incluye
service.name,service.version,deployment.environment. - Configura el memory_limiter — Evita que el Collector se caiga por picos de telemetría.
- Usa el batch processor — Agrupa spans antes de exportar para reducir overhead de red.
- Correlaciona logs con trazas — Inyecta
trace_idyspan_iden tus logs estructurados. - Monitorea al Collector mismo — El Collector expone métricas propias en
/metrics. Dashboardea dropped spans, queue size, etc. - Separa el Collector en tiers — Un Collector por nodo (agent) que reenvía a un Collector central (gateway) para procesamiento pesado.
Comments
Sign in to leave a comment
No comments yet. Be the first!