import asyncio from contextlib import asynccontextmanager from fastapi import FastAPI from fastapi.responses import Response from . import metrics from .api import agents, checks, events, health, heartbeat, incidents, outbox from .db import dispose_engine, get_engine, get_sessionmaker from .errors import install_error_handlers from .logging_config import configure_logging, get_logger from .middleware.auth import BearerAuthMiddleware from .middleware.body_limit import BodyLimitMiddleware from .middleware.request_id import RequestIdMiddleware from .services.detector import run_detector from .services.notifier_worker import run_notifier_worker from .settings import get_settings log = get_logger("monlet.main") @asynccontextmanager async def lifespan(app: FastAPI): s = get_settings() configure_logging(s.log_level) get_engine() sm = get_sessionmaker() stop_event = asyncio.Event() tasks: list[asyncio.Task] = [] if s.enable_detector: tasks.append(asyncio.create_task(run_detector(sm, stop_event), name="detector")) if s.enable_notifier_worker: tasks.append( asyncio.create_task(run_notifier_worker(sm, stop_event), name="notifier_worker") ) log.info("server_started", host=s.host, port=s.port) try: yield finally: stop_event.set() for t in tasks: try: await asyncio.wait_for(t, timeout=5.0) except TimeoutError: t.cancel() await dispose_engine() def create_app() -> FastAPI: app = FastAPI(title="Monlet Server", version="1.0.0", lifespan=lifespan) app.add_middleware(BearerAuthMiddleware) app.add_middleware(BodyLimitMiddleware) app.add_middleware(RequestIdMiddleware) install_error_handlers(app) app.include_router(health.router, prefix="/api/v1") app.include_router(heartbeat.router, prefix="/api/v1") app.include_router(events.router, prefix="/api/v1") app.include_router(agents.router, prefix="/api/v1") app.include_router(checks.router, prefix="/api/v1") app.include_router(incidents.router, prefix="/api/v1") app.include_router(outbox.router, prefix="/api/v1") @app.get("/metrics") async def metrics_endpoint() -> Response: body, ct = metrics.render() return Response(content=body, media_type=ct) return app app = create_app()