Propagate traces over HTTP and add Redis spans

setup_telemetry now instruments httpx and redis process-wide. Both patch
the client classes, so clients created at import time (gateway's
_proxy_client, common.redis_client) are covered too. httpx CLIENT spans
inject traceparent, so the downstream FastAPI SERVER spans join the
caller's trace. gateway and fraud get setup_telemetry, the FastAPI span,
and the OTLP auth header. All seven services now export traces.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Movses Aslanyan
2026-10-13 11:28:03 +03:30
co-authored by Claude Opus 5.5
parent ec6ddb6a26
commit 14938ab36b
4 changed files with 29 additions and 0 deletions
+7
View File
@@ -3,6 +3,8 @@ from opentelemetry.sdk.resources import SERVICE_NAME, Resource
from opentelemetry.sdk.trace import TracerProvider from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.instrumentation.httpx import HTTPXClientInstrumentor
from opentelemetry.instrumentation.redis import RedisInstrumentor
def setup_telemetry(service_name: str): def setup_telemetry(service_name: str):
@@ -11,3 +13,8 @@ def setup_telemetry(service_name: str):
provider = TracerProvider(resource=resource) provider = TracerProvider(resource=resource)
provider.add_span_processor(processor) provider.add_span_processor(processor)
trace.set_tracer_provider(provider) trace.set_tracer_provider(provider)
# Process-wide patches on the client classes, so they also cover clients
# created at import time (gateway's _proxy_client, common.redis_client).
HTTPXClientInstrumentor().instrument()
RedisInstrumentor().instrument()
+4
View File
@@ -86,6 +86,8 @@ services:
environment: environment:
<<: *app-env <<: *app-env
SERVICE_NAME: gateway SERVICE_NAME: gateway
env_file:
- .env.observability
command: uvicorn services.gateway.app:app --host 0.0.0.0 --port 8000 --reload --no-access-log command: uvicorn services.gateway.app:app --host 0.0.0.0 --port 8000 --reload --no-access-log
ports: ports:
- "8000:8000" - "8000:8000"
@@ -128,6 +130,8 @@ services:
environment: environment:
<<: *app-env <<: *app-env
SERVICE_NAME: fraud SERVICE_NAME: fraud
env_file:
- .env.observability
command: uvicorn services.fraud.app:app --host 0.0.0.0 --port 8002 --reload --no-access-log command: uvicorn services.fraud.app:app --host 0.0.0.0 --port 8002 --reload --no-access-log
ports: ports:
- "8002:8002" - "8002:8002"
+9
View File
@@ -2,19 +2,28 @@ import httpx
from fastapi import FastAPI, Request from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse from fastapi.responses import JSONResponse
from pydantic import BaseModel from pydantic import BaseModel
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
from common.http import downstream_client from common.http import downstream_client
from common.logging_config import configure_logging from common.logging_config import configure_logging
from common.redis_client import redis_client from common.redis_client import redis_client
from common.settings import settings from common.settings import settings
from common.telemetry import setup_telemetry
from . import chaos from . import chaos
from .scoring import score from .scoring import score
SERVICE_NAME = "fraud" SERVICE_NAME = "fraud"
logger = configure_logging(SERVICE_NAME) logger = configure_logging(SERVICE_NAME)
setup_telemetry(service_name=SERVICE_NAME)
app = FastAPI(title=SERVICE_NAME) app = FastAPI(title=SERVICE_NAME)
FastAPIInstrumentor.instrument_app(
app,
excluded_urls="/healthz",
exclude_spans=["receive", "send"],
)
class ScoreRequest(BaseModel): class ScoreRequest(BaseModel):
account_id: str account_id: str
+9
View File
@@ -7,18 +7,27 @@ from fastapi import FastAPI, Request
from fastapi.responses import FileResponse, JSONResponse from fastapi.responses import FileResponse, JSONResponse
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
from pydantic import BaseModel, field_validator from pydantic import BaseModel, field_validator
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
from common.chaos_flags import ALL_FLAGS, get_flags, set_flag, set_profile from common.chaos_flags import ALL_FLAGS, get_flags, set_flag, set_profile
from common.logging_config import configure_logging from common.logging_config import configure_logging
from common.settings import settings from common.settings import settings
from common.telemetry import setup_telemetry
from . import orchestrator from . import orchestrator
from .orchestrator import UpstreamFailure from .orchestrator import UpstreamFailure
SERVICE_NAME = "gateway" SERVICE_NAME = "gateway"
logger = configure_logging(SERVICE_NAME) logger = configure_logging(SERVICE_NAME)
setup_telemetry(service_name=SERVICE_NAME)
app = FastAPI(title=SERVICE_NAME) app = FastAPI(title=SERVICE_NAME)
FastAPIInstrumentor.instrument_app(
app,
excluded_urls="/healthz",
exclude_spans=["receive", "send"],
)
STATIC_DIR = Path(__file__).parent / "static" STATIC_DIR = Path(__file__).parent / "static"
app.mount("/static", StaticFiles(directory=STATIC_DIR), name="static") app.mount("/static", StaticFiles(directory=STATIC_DIR), name="static")