From 14938ab36b6114952a268af6e3756765e8cf2340 Mon Sep 17 00:00:00 2001 From: Movses Aslanyan Date: Tue, 13 Oct 2026 11:28:03 +0330 Subject: [PATCH] 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 --- common/telemetry.py | 7 +++++++ docker-compose.yml | 4 ++++ services/fraud/app.py | 9 +++++++++ services/gateway/app.py | 9 +++++++++ 4 files changed, 29 insertions(+) diff --git a/common/telemetry.py b/common/telemetry.py index 9904ef0..6353deb 100644 --- a/common/telemetry.py +++ b/common/telemetry.py @@ -3,6 +3,8 @@ from opentelemetry.sdk.resources import SERVICE_NAME, Resource 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.httpx import HTTPXClientInstrumentor +from opentelemetry.instrumentation.redis import RedisInstrumentor def setup_telemetry(service_name: str): @@ -11,3 +13,8 @@ def setup_telemetry(service_name: str): provider = TracerProvider(resource=resource) provider.add_span_processor(processor) 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() diff --git a/docker-compose.yml b/docker-compose.yml index c0ee3bb..ec6db90 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -86,6 +86,8 @@ services: environment: <<: *app-env SERVICE_NAME: gateway + env_file: + - .env.observability command: uvicorn services.gateway.app:app --host 0.0.0.0 --port 8000 --reload --no-access-log ports: - "8000:8000" @@ -128,6 +130,8 @@ services: environment: <<: *app-env SERVICE_NAME: fraud + env_file: + - .env.observability command: uvicorn services.fraud.app:app --host 0.0.0.0 --port 8002 --reload --no-access-log ports: - "8002:8002" diff --git a/services/fraud/app.py b/services/fraud/app.py index 2ff72ea..4281417 100644 --- a/services/fraud/app.py +++ b/services/fraud/app.py @@ -2,19 +2,28 @@ import httpx from fastapi import FastAPI, Request from fastapi.responses import JSONResponse from pydantic import BaseModel +from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor from common.http import downstream_client from common.logging_config import configure_logging from common.redis_client import redis_client from common.settings import settings +from common.telemetry import setup_telemetry from . import chaos from .scoring import score SERVICE_NAME = "fraud" logger = configure_logging(SERVICE_NAME) +setup_telemetry(service_name=SERVICE_NAME) app = FastAPI(title=SERVICE_NAME) +FastAPIInstrumentor.instrument_app( + app, + excluded_urls="/healthz", + exclude_spans=["receive", "send"], +) + class ScoreRequest(BaseModel): account_id: str diff --git a/services/gateway/app.py b/services/gateway/app.py index 5a6e3f1..c60bb2f 100644 --- a/services/gateway/app.py +++ b/services/gateway/app.py @@ -7,18 +7,27 @@ from fastapi import FastAPI, Request from fastapi.responses import FileResponse, JSONResponse from fastapi.staticfiles import StaticFiles 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.logging_config import configure_logging from common.settings import settings +from common.telemetry import setup_telemetry from . import orchestrator from .orchestrator import UpstreamFailure SERVICE_NAME = "gateway" logger = configure_logging(SERVICE_NAME) +setup_telemetry(service_name=SERVICE_NAME) app = FastAPI(title=SERVICE_NAME) +FastAPIInstrumentor.instrument_app( + app, + excluded_urls="/healthz", + exclude_spans=["receive", "send"], +) + STATIC_DIR = Path(__file__).parent / "static" app.mount("/static", StaticFiles(directory=STATIC_DIR), name="static")