2. Conceptos fundamentales: trace, span, context
2. Conceptos fundamentales: trace, span, context
Sin entender estos conceptos no se puede operar Jaeger. Son el ABC del distributed tracing y son comunes a Jaeger, Zipkin, Tempo y cualquier backend OpenTelemetry-compatible.
Trace
Un trace es la representación completa de una request o transacción a través de un sistema distribuido.
Cada trace tiene un identificador único (trace_id) de 128 bits y representa una unidad lógica de trabajo: una request HTTP, un mensaje de Kafka procesado, un cron job que corrió, etc.
Características:
- Inmutable una vez completado.
- Atraviesa procesos, hosts y redes.
- Se construye con spans anidados.
flowchart TD
T[Trace: checkout_user_123\ntrace_id=4bf92f3577b34da6a3ce929d0e0e4736]
T --> S1[Span: HTTP POST /checkout\n800ms]
S1 --> S2[Span: validate cart\n50ms]
S1 --> S3[Span: charge payment\n600ms]
S3 --> S4[Span: stripe.charge\n550ms]
S1 --> S5[Span: send email\n100ms]
Span
Un span representa una operación individual con un inicio y un fin: una llamada HTTP, una query SQL, una función crítica.
Cada span contiene:
| Campo | Qué guarda |
|---|---|
span_id | ID único del span (64 bits) |
trace_id | A qué trace pertenece |
parent_span_id | Quién es su padre (vacío si es root) |
name | Nombre legible: GET /api/orders, db.query |
start_time / end_time | Marca temporal con precisión de nanosegundos |
attributes | Pares clave-valor (ex-tags) |
events | Eventos puntuales con timestamp (ex-logs) |
status | OK, ERROR, UNSET |
kind | SERVER, CLIENT, PRODUCER, CONSUMER, INTERNAL |
links | Referencias a otros spans (no padre/hijo) |
Anatomía visual de un span
┌───────────────────────────────────────────────┐
│ span_id: 7c3d92ab... │
│ name: HTTP GET /api/users/42 │
│ kind: SERVER │
│ start: 2026-05-10T15:00:01.123Z │
│ duration: 87ms │
│ status: OK │
│ attributes: │
│ http.method: GET │
│ http.status_code: 200 │
│ http.route: /api/users/:id │
│ user.id: 42 │
│ events: │
│ 2026-05-10T15:00:01.180Z "cache_miss" │
│ 2026-05-10T15:00:01.205Z "db_query_start" │
└───────────────────────────────────────────────┘
Span kind — la pieza que más se olvida
El kind indica el rol del span en la comunicación:
| Kind | Cuándo usarlo |
|---|---|
SERVER | Recibiste una request entrante (handler HTTP, gRPC server) |
CLIENT | Hiciste una request saliente (fetch, llamada a API externa) |
PRODUCER | Publicaste un mensaje a una cola (Kafka, RabbitMQ) |
CONSUMER | Consumiste un mensaje de una cola |
INTERNAL | Operación interna (función crítica, transformación) — default |
Un par CLIENT + SERVER típicamente forman el cruce entre dos servicios. Si los marcás bien, Jaeger puede dibujar el mapa de dependencias automáticamente.
Trace context
El trace context es la información mínima que se propaga entre procesos para mantener un trace coherente:
trace_id,span_idyflags.
Cuando el servicio A llama por HTTP al servicio B, el cliente del servicio A inyecta el contexto en headers, y el servidor de B los lee. Sin esto, B empezaría un trace nuevo sin saber que es continuación del de A.
El estándar dominante es W3C Trace Context (RFC publicado por W3C):
traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
│ │ │ │
│ trace_id (128 bits) span_id (64 b) flags
versión
Y opcionalmente tracestate para metadata vendor-específica.
sequenceDiagram
participant A as Service A
participant B as Service B
participant C as Service C
A->>B: GET /orders<br/>traceparent: 00-abc...-111-01
B->>B: span "B handler" (parent=111)
B->>C: GET /pricing<br/>traceparent: 00-abc...-222-01
C->>C: span "C handler" (parent=222)
C-->>B: 200
B-->>A: 200
Notá que trace_id (abc...) no cambia en toda la cadena. Lo que cambia en cada salto es span_id (el ID del span actual, que será el padre del siguiente).
Capítulo dedicado a esto: 07-context-propagation.
Baggage
El baggage es información extra que viaja junto al contexto y se puede leer en cualquier servicio del trace.
Es una especie de “diccionario global” para esa request. Útil para cosas como:
user.tier=premiumfeature_flag.checkout_v2=trueregion=us-east-1
Un servicio downstream puede leer user.tier sin tener que hacer otra request al servicio de usuarios.
Cuidado: el baggage viaja en headers HTTP en cada salto. Si lo llenás de basura, agregás overhead a cada request. Mantenelo pequeño y semánticamente útil.
Atributos vs eventos vs logs
Tres mecanismos parecidos pero distintos.
Atributos (attributes)
Pares clave-valor que describen el span de forma estática:
http.method = GET
http.status_code = 200
db.system = postgresql
db.statement = SELECT * FROM users WHERE id=$1
Estandarizados por las OpenTelemetry Semantic Conventions. Usá los nombres oficiales — la UI de Jaeger los reconoce y los muestra de manera especial.
Eventos (events)
Cosas puntuales que pasaron dentro del span con timestamp:
t=10ms: "cache_miss"
t=12ms: "db_query_start"
t=78ms: "db_query_done"
Útil para hitos internos sin abrir un span hijo (overkill para algo de pocos microsegundos).
Logs
En OpenTelemetry “log” es una señal aparte (logs, métricas, trazas son las 3 señales). En Jaeger v1 “log” era sinónimo de “evento de span”. Hoy se prefiere el término event.
Status del span
status:
code: OK | ERROR | UNSET
description: "connection refused" (solo si ERROR)
Importante: una excepción en tu código no marca automáticamente el span como ERROR. La instrumentación tiene que llamar span.setStatus(ERROR) explícitamente, o usar las helpers recordException(). Las auto-instrumentaciones de OpenTelemetry suelen hacerlo bien para HTTP/gRPC, pero código manual es responsabilidad tuya.
Links
Un span puede linkear a otros spans que no son su padre. Útiles para:
- Una operación que procesa múltiples mensajes de Kafka en batch — un span con N links a los spans productores.
- Operaciones asíncronas donde el “padre” lógico no es el padre temporal.
flowchart LR
P1[Producer span 1] -.link.-> B[Batch processor span]
P2[Producer span 2] -.link.-> B
P3[Producer span 3] -.link.-> B
Resource
Las propiedades del proceso/servicio que emite los spans, comunes a todos sus spans:
service.name = order-service
service.version = 1.4.2
deployment.environment = production
host.name = order-service-7b4f9c-xkj2p
process.runtime.name = node
process.runtime.version = 20.10.0
Se configuran una vez al inicio del proceso. La UI los muestra agrupados como “Process” en el detalle del span.
Diagrama mental completo
flowchart TB
subgraph Trace[Trace - trace_id]
S1[Span root\nkind=SERVER]
S2[Span hijo\nkind=CLIENT]
S3[Span hijo\nkind=INTERNAL]
S4[Span nieto\nkind=CLIENT]
S1 --> S2
S1 --> S3
S2 --> S4
end
Resource[Resource\nservice.name, env, host] --> S1
Resource --> S2
Resource --> S3
Resource --> S4
Context[Context\ntrace_id + span_id] -.propagación HTTP/gRPC.-> Otra[Otro proceso]
Baggage[Baggage\npairs k=v] -.acompaña al context.-> Otra
Con esto en la cabeza, ya estás listo para instalar Jaeger en el próximo capítulo y empezar a generar trazas reales.