Add business spans and silence the frontend health poll

Manual INTERNAL spans for the business steps the auto-instrumentation
can't name: fraud.score_transaction, sanctions.lookup_watchlist,
ledger.settle and insights.aggregate_120d, with ledgerline.* attributes
(account, amount, risk score, match outcome). holder_name stays off the
spans. Each span starts after its handler's chaos flag reads and
injection points, and no chaos-derived values become attributes.

The gateway's /api/health/all fan-out now runs under
suppress_instrumentation: with the route excluded, its five downstream
/healthz calls were each a parentless root trace.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Movses Aslanyan
2026-10-13 18:11:38 +03:30
co-authored by Claude Opus 5.5
parent d1c158c835
commit d52a19965a
5 changed files with 88 additions and 48 deletions
+15 -7
View File
@@ -1,6 +1,7 @@
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from pydantic import BaseModel, field_validator
from opentelemetry import trace
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
from opentelemetry.instrumentation.sqlalchemy import SQLAlchemyInstrumentor
@@ -13,6 +14,7 @@ from . import repository
SERVICE_NAME = "ledger"
logger = configure_logging(SERVICE_NAME)
tracer = trace.get_tracer(__name__)
setup_telemetry(service_name=SERVICE_NAME)
app = FastAPI(title=SERVICE_NAME)
@@ -133,13 +135,19 @@ async def settle(tx_id: str, body: SettleRequest):
starved = await chaos.pool_starvation_enabled()
row_lock = await chaos.row_lock_contention_enabled()
async with session_factory(starved=starved) as session:
tx = await repository.get_transaction(session, tx_id)
if tx is None:
return JSONResponse(status_code=404, content={"error": "transaction not found"})
account = await repository.get_account(session, tx.account_id)
use_lock = row_lock and bool(account.is_hot)
balance = await repository.settle_transaction(session, tx=tx, risk_score=body.risk_score, use_row_lock=use_lock)
with tracer.start_as_current_span(
"ledger.settle",
attributes={"ledgerline.tx.id": tx_id, "ledgerline.risk.score": body.risk_score},
) as span:
async with session_factory(starved=starved) as session:
tx = await repository.get_transaction(session, tx_id)
if tx is None:
return JSONResponse(status_code=404, content={"error": "transaction not found"})
span.set_attribute("ledgerline.account.id", str(tx.account_id))
span.set_attribute("ledgerline.tx.amount_minor", tx.amount_minor)
account = await repository.get_account(session, tx.account_id)
use_lock = row_lock and bool(account.is_hot)
balance = await repository.settle_transaction(session, tx=tx, risk_score=body.risk_score, use_row_lock=use_lock)
logger.info(f"settled transaction {tx_id} request_id={body.request_id}")
return {"status": "settled", "balance_minor": balance}