14. Troubleshooting y best practices

Por: Artiko
jaegertroubleshootingbest-practicesproduccion

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:

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

Elasticsearch

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:

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:

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:

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:

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:

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:


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:

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.


Recursos para seguir