rmi-backend/app/lifespan.py

104 lines
3.4 KiB
Python

"""RMI Backend — lifespan (startup/shutdown).
Per v4.0 §T01 + ADR-0001, lifespan wires up cross-cutting concerns:
- Structured logging (structlog)
- Typed error handlers (AppError → JSON)
- Long-term memory (M1: fact_store seed)
- Observability (M4: OpenTelemetry + Langfuse)
All initializations are isolated — one failure does not break startup.
Per v3 unfuck rule #7: add_middleware must be at module level, NOT
in lifespan. So this file only does setup() calls and yield.
"""
from __future__ import annotations
import logging
import os
from collections.abc import AsyncIterator
from contextlib import asynccontextmanager
from fastapi import FastAPI
log = logging.getLogger(__name__)
@asynccontextmanager
async def lifespan(_app: FastAPI) -> AsyncIterator[None]:
"""Wire cross-cutting at startup. Failures are logged, never fatal."""
log.info("rmi_backend_starting")
# 1. Structured logging
try:
from app.core.logging import setup_logging
setup_logging(os.getenv("LOG_LEVEL", "INFO"))
log.info("logging_initialized")
except Exception as exc:
log.warning("logging_setup_failed err=%s", exc)
# 2. Typed error handlers
try:
from app.core.errors import register_error_handlers
register_error_handlers(_app, debug=os.getenv("ENVIRONMENT") == "dev")
log.info("error_handlers_initialized")
except Exception as exc:
log.warning("error_handlers_skipped err=%s", exc)
# 3. M1 — long-term memory (fact_store seed)
try:
from app.agents.fact_store import seed_facts
seeded = await seed_facts()
log.info("fact_store_seeded count=%d", seeded)
except Exception as exc:
log.info("fact_store_seed_skipped err=%s", exc)
# 4. M4 — OpenTelemetry tracing
try:
from app.core.tracing import setup_otel
otel_ok = setup_otel()
log.info("otel_init ok=%s", otel_ok)
except Exception as exc:
log.info("otel_init_failed err=%s", exc)
# 5. M4 — Langfuse (LLM tracing)
try:
from app.core.langfuse import init_langfuse
lf_ok = init_langfuse()
log.info("langfuse_init ok=%s", lf_ok)
except Exception as exc:
log.info("langfuse_init_failed err=%s", exc)
# 6. T07 — GlitchTip error tracking
try:
from app.core.observability import setup_sentry
sentry_ok = setup_sentry()
log.info("sentry_init ok=%s", sentry_ok)
except Exception as exc:
log.info("sentry_init_failed err=%s", exc)
# 7. T12 — CertStream phishing domain monitor (background task)
certstream_task = None
try:
from app.domain.threat.certstream_listener import start_listener
certstream_task = await start_listener()
log.info("certstream_started has_task=%s", certstream_task is not None)
except Exception as exc:
log.info("certstream_start_failed err=%s", exc)
yield
# Shutdown
try:
from app.domain.threat.certstream_listener import stop_listener
await stop_listener(certstream_task)
except Exception:
pass
try:
from app.core.langfuse import flush_langfuse
from app.core.observability import flush_sentry
from app.core.tracing import shutdown_otel
flush_sentry(timeout=2.0)
flush_langfuse()
shutdown_otel()
except Exception:
pass
log.info("rmi_backend_shutdown")