Distributed Tracing with Jaeger
Install Jaeger
Kubernetes:Copy
Ask AI
## Install Jaeger operator
kubectl create namespace observability
kubectl create -f https://github.com/jaegertracing/jaeger-operator/releases/download/v1.51.0/jaeger-operator.yaml -n observability
## Deploy Jaeger instance
cat << 'EOF' | kubectl apply -f -
apiVersion: jaegertracing.io/v1
kind: Jaeger
metadata:
name: jaeger
namespace: observability
spec:
strategy: production
storage:
type: elasticsearch
options:
es:
server-urls: http://elasticsearch:9200
EOF
Copy
Ask AI
services:
jaeger:
image: jaegertracing/all-in-one:latest
ports:
- "16686:16686" # UI
- "4318:4318" # OTLP HTTP
- "4317:4317" # OTLP gRPC
environment:
- COLLECTOR_OTLP_ENABLED=true
OpenTelemetry Configuration
Install dependencies:Copy
Ask AI
uv pip install opentelemetry-api \
opentelemetry-sdk \
opentelemetry-instrumentation-fastapi \
opentelemetry-exporter-otlp
Copy
Ask AI
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
from opentelemetry.instrumentation.httpx import HTTPXClientInstrumentor
## Setup tracing
trace.set_tracer_provider(TracerProvider())
tracer = trace.get_tracer(__name__)
## Configure OTLP exporter
otlp_exporter = OTLPSpanExporter(
endpoint="http://jaeger:4318/v1/traces"
)
trace.get_tracer_provider().add_span_processor(
BatchSpanProcessor(otlp_exporter)
)
## Auto-instrument FastAPI
FastAPIInstrumentor.instrument_app(app)
HTTPXClientInstrumentor().instrument()
## Manual instrumentation
@app.post("/chat")
async def chat(query: str):
with tracer.start_as_current_span("chat") as span:
span.set_attribute("query_length", len(query))
# LLM call with tracing
with tracer.start_as_current_span("llm_invoke") as llm_span:
llm_span.set_attribute("provider", "anthropic")
llm_span.set_attribute("model", "claude-sonnet-4-5-20250929")
response = await llm.ainvoke(query)
llm_span.set_attribute("prompt_tokens", response.usage.prompt_tokens)
llm_span.set_attribute("completion_tokens", response.usage.completion_tokens)
# OpenFGA check with tracing
with tracer.start_as_current_span("openfga_check") as auth_span:
allowed = await openfga_client.check_permission(
user=f"user:{user_id}",
relation="executor",
object="tool:chat"
)
auth_span.set_attribute("allowed", allowed)
span.set_attribute("response_length", len(response.content))
return response
Trace Context Propagation
Copy
Ask AI
from opentelemetry.trace.propagation.tracecontext import TraceContextTextMapPropagator
propagator = TraceContextTextMapPropagator()
## Inject trace context into HTTP headers
async def call_external_service():
headers = {}
propagator.inject(headers)
async with httpx.AsyncClient() as client:
response = await client.post(
"https://external-api.com/endpoint",
headers=headers
)
return response