14. Troubleshooting y best practices
14. Troubleshooting y best practices
Cierre del tutorial. Acá agrupamos los problemas que vas a ver en producción y las prácticas que distinguen un setup que se usa de uno que se ignora.
Troubleshooting
Síntoma 1: “Las trazas no aparecen”
Por orden de probabilidad:
1.1 — Endpoint mal configurado
# Verificá las variables en la app
echo $OTEL_EXPORTER_OTLP_ENDPOINT
# debería ser http://jaeger:4317 o similar
Errores típicos: https:// cuando es http://, host equivocado, puerto 4318 en lugar de 4317.
1.2 — Sampling al 0%
echo $OTEL_TRACES_SAMPLER_ARG
# si dice 0.0, ningún trace se exporta
Para debug poné OTEL_TRACES_SAMPLER=always_on temporalmente.
1.3 — Reloj desincronizado
Si el clock de la app difiere del de Jaeger en > 5 minutos, las trazas pueden caer fuera del rango “Last Hour” en la UI. Mirá la búsqueda con Lookback: Last 24h.
date # en la app
docker exec jaeger date # en Jaeger
# deberían diferir < 1 segundo
1.4 — Spans descartados
curl -s http://jaeger-collector:14269/metrics | grep dropped
# jaeger_collector_spans_dropped_total > 0 → estás perdiendo
Sube queue-size o agregá más réplicas de collector.
1.5 — SDK no terminó de exportar
En scripts cortos / lambdas, el batch processor puede no haber flushed antes de que el proceso muriera. Usá SimpleSpanProcessor o forzá sdk.shutdown() antes de exit.
Síntoma 2: “Las trazas existen pero están truncadas / desconectadas”
Servicio A y B en distintas trazas cuando deberían estar en la misma.
Causas:
- Propagator mismatch: A emite W3C, B solo lee B3. Configurá
OTEL_PROPAGATORS=tracecontext,baggage,b3multien ambos. - Proxy intermedio que strip headers: NGINX, Envoy mal configurado, API Gateway. Verificá que
traceparentpasa a través de cada salto. - Async sin context:
setTimeoutoPromise.allque se desancla del contexto. Capturácontext.active()antes y restaurá adentro.
Diagnóstico rápido:
# Ver headers de una request entre servicios
kubectl exec -it pod-de-A -- curl -v http://servicio-b/health 2>&1 | grep -i traceparent
Síntoma 3: “Hay error pero el span está OK”
Causa: la auto-instrumentación o tu código no llama a setStatus(ERROR). El span termina con status UNSET, indistinguible de OK visualmente.
Verificá en código:
try {
await op();
} catch (err) {
span.recordException(err);
span.setStatus({ code: SpanStatusCode.ERROR }); // <-- ESTA LÍNEA
throw err;
}
Para auto-instrumentación HTTP/gRPC, 5xx se marca como error automáticamente. Para custom, es responsabilidad tuya.
Síntoma 4: “Storage lento / OOM”
Cassandra
- ¿Compactaciones atrasadas?
nodetool compactionstats. - ¿Sstables creciendo sin compactarse?
- TTL configurado pero no sirviendo de filtro: revisá que el tombstone threshold está OK.
Elasticsearch
- ¿Heap al 80%+? Bajá la retención o agregá nodos.
- ¿Shards demasiado grandes? > 50 GB por shard es señal de re-shardear.
- ¿Búsquedas slow log? Tags con valores muy variables (UUIDs como tag indexado) matan ES.
General
Bajá el sampling primero. Después scaleás storage. Más datos no es más valor si el equipo nunca los mira.
Síntoma 5: “Cardinalidad explotó”
Métrica:
# Cuántas series únicas tenés en spanmetrics
count(count by (service_name, span_name, http_status_code) (traces_spanmetrics_calls_total))
Si es > 100k, tenés problema. Causas típicas:
- Atributos con UUIDs / IDs como dimensions de spanmetrics.
- Span names con valores variables:
GET /api/users/9374en lugar deGET /api/users/:id.
Revisá tu instrumentación y normalizá routes.
Best practices
1. Service name consistente
Mismo servicio en todos los entornos: service.name invariante. La diferencia entre dev/staging/prod va en deployment.environment, no en el nombre.
# Bien
service.name = order-service
deployment.environment = production
# Mal
service.name = order-service-prod
service.name = orders_dev
2. Trace ID en logs estructurados
Sin esto el observability stack está roto. En cada log estructurado:
{
"timestamp": "...",
"level": "error",
"message": "payment failed",
"trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
"span_id": "00f067aa0ba902b7"
}
La mayoría de las librerías de logging tienen integración con OpenTelemetry para inyectar el contexto activo automáticamente.
// Pino + OTel
const pino = require('pino')({
mixin() {
const ctx = trace.getActiveSpan()?.spanContext();
return ctx ? { trace_id: ctx.traceId, span_id: ctx.spanId } : {};
},
});
3. Atributos semánticos estándar
Usá los nombres de OpenTelemetry Semantic Conventions. El equipo de Jaeger los entiende, otros backends también, y te ahorrás bikeshedding.
4. Span por unidad de trabajo, no por línea de código
Mal:
with tracer.start_span("validar_input"):
pass # 50 microsegundos
with tracer.start_span("comparar_strings"):
pass # 10 microsegundos
Bien:
with tracer.start_span("procesar_orden") as span:
span.set_attribute("order.id", order_id)
validar_input()
comparar_strings()
span.add_event("validacion_completada")
cobrar()
Spans tienen overhead. Demasiados para operaciones triviales matan performance sin agregar señal útil.
5. Health checks fuera del trace
Los /health, /ready, /metrics ensucian la UI con miles de trazas inútiles. Filtrarlas:
# OTel Collector
processors:
filter:
traces:
span:
- 'attributes["http.route"] == "/health"'
- 'attributes["http.route"] == "/metrics"'
O en el SDK con un sampler custom que descarta esas rutas.
6. Sampling honesto
Tres errores comunes:
- Demasiado bajo: te llaman por un incidente y la traza no está. Subí.
- Demasiado alto: storage explota. Bajá.
- Cero estrategia: 100% en producción a tráfico alto. Tail-sampling errores + 1% del resto es la combinación común.
Métrica jaeger_sampler_decisions_total{sampled="true"} te dice cuánto guardás efectivamente.
7. Trace-first en incidentes
Cambio cultural difícil pero el ROI más grande:
- Incidente entra → primer reflejo: abrir Jaeger.
- Reportar bug del usuario X → pedir trace_id, no logs ni screenshots.
- Análisis de latencia → SPM + drill-down a trazas específicas.
Si tu equipo todavía empieza por logs ante todo, no estás capitalizando la inversión en tracing.
8. Documentar qué está instrumentado
Cada servicio debería tener un OBSERVABILITY.md:
- Qué framework de instrumentación usa.
- Qué auto-instrumentaciones están activas.
- Qué spans manuales se generan y para qué.
- Cómo levantar el servicio con tracing local.
Reduce el tiempo en que alguien nuevo entiende cómo debuggear.
9. Dogfooding del propio Jaeger
Jaeger expone métricas Prometheus. Monitorealo como cualquier otro servicio:
- Spans dropped → alerta crítica.
- Storage latency → alerta si > 1s p99.
- Kafka lag (si usás streaming) → alerta si > 100k.
- Memory de collectors → alerta si > 80%.
Si Jaeger se cae y no te enterás, perdés observabilidad sobre todo el sistema sin saberlo.
10. Retención corta + archive selectivo
Pocos equipos necesitan 30 días de trazas. La realidad: el 95% de los incidentes se debuggean con trazas de la última hora.
Estrategia común:
- 7 días de retención general (más que suficiente para post-mortems).
- Archive de trazas marcadas explícitamente (errores críticos, transacciones financieras): retención 90 días+.
- Trazas archivadas se pueden mover a cold storage (S3) cuyo lookup es lento pero barato.
Limitaciones honestas
Cosas que tracing no resuelve y conviene tener claras:
No reemplaza profiling
Trazas miden latencia entre operaciones, no CPU per-function. Para optimizar código de un solo proceso usá profilers (pprof, async-profiler, etc.).
No reemplaza logs
Los logs siguen siendo necesarios para detalle granular dentro de una operación. Trace + logs es la combo, no trace en lugar de logs.
Costos pueden ser significativos
A volúmenes altos, storage de tracing puede competir con storage de la base transaccional. Sampling agresivo + tail-sampling para casos importantes es la única forma sostenible.
Auto-instrumentación tiene techo
Para entender lógica de negocio crítica vas a necesitar spans manuales. La auto te da el esqueleto; vos completás el músculo.
Cierre del tutorial
Empezaste sin saber qué era un span. Ahora sabés:
- Qué son trazas, spans, contexto, baggage.
- Cómo instrumentar con OpenTelemetry, qué propagators usar, cómo samplear.
- Cómo leer la UI: timeline, span detail, system architecture, monitor.
- Cómo elegir storage backend según tu volumen y necesidades de búsqueda.
- Qué cambia con Jaeger v2 sobre OpenTelemetry Collector.
- Cómo desplegarlo en Kubernetes con HA y observabilidad propia.
- Cómo derivar RED metrics de los spans con SPM + Prometheus.
- Los problemas operacionales típicos y cómo evitarlos.
Tracing es un superpoder cuando se usa bien. La diferencia entre un equipo que lo aprovecha y uno que no, no es la herramienta — es la disciplina de instrumentar consistentemente, propagar siempre, samplear honestamente y mirar trazas antes que logs cuando algo sale mal.
Buena suerte. Y cuando tu próxima latencia inexplicable te lleve a una traza que apunte directo al span culpable, vas a entender por qué tantos equipos invierten en esto.