Files
LedgerLine/services/notifier/app.py
T
Movses AslanyanandClaude Opus 5.5 ec6ddb6a26 Instrument SQLAlchemy in every service with a database
sanctions, notifier, ledger, insights and audit now call setup_telemetry
and instrument their engines with one SQLAlchemyInstrumentor call per
process (a second instrument() call is ignored, leaving that engine with
connect spans only). ledger and insights pass both their normal and
starved engines. The FastAPI services also get the server span, with the
same /healthz and receive/send exclusions as sanctions.

Compose: OTEL_SEMCONV_STABILITY_OPT_IN=http,database for the stable
attribute names, the OTLP endpoint moves to the shared env, and the four
newly instrumented services get the auth header from .env.observability.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 10:09:57 +03:30

61 lines
1.6 KiB
Python

from contextlib import asynccontextmanager
import asyncio
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
from opentelemetry.instrumentation.sqlalchemy import SQLAlchemyInstrumentor
from common.db import engine
from common.logging_config import configure_logging
from common.redis_client import redis_client
from common.telemetry import setup_telemetry
from . import consumer
SERVICE_NAME = "notifier"
logger = configure_logging(SERVICE_NAME)
setup_telemetry(service_name=SERVICE_NAME)
@asynccontextmanager
async def lifespan(app: FastAPI):
task = asyncio.create_task(consumer.run())
yield
task.cancel()
app = FastAPI(title=SERVICE_NAME, lifespan=lifespan)
FastAPIInstrumentor.instrument_app(
app,
excluded_urls="/healthz",
exclude_spans=["receive", "send"],
)
SQLAlchemyInstrumentor().instrument(engines=[engine.sync_engine])
@app.get("/")
async def root():
return {"service": SERVICE_NAME, "version": "0.1.0"}
@app.get("/healthz")
async def healthz(request: Request):
if request.headers.get("X-Ledgerline-Probe") == "chatty-health":
logger.info("received chatty-health healthz probe from gateway")
return {"status": "ok", "service": SERVICE_NAME}
@app.get("/readyz")
async def readyz():
try:
async with engine.connect() as conn:
await conn.exec_driver_sql("SELECT 1")
await redis_client.ping()
except Exception as exc:
logger.exception(f"readyz check failed: {exc}")
return JSONResponse(status_code=503, content={"status": "unavailable"})
return {"status": "ok"}