Commit 4b22a8a2 authored by Dimitrios Gogos's avatar Dimitrios Gogos
Browse files

feat: implement health check endpoint and request ID middleware with logging

parent fbbfc096
Loading
Loading
Loading
Loading
+0 −0

Empty file added.

+28 −0
Changes for src/portal_service/api/exception_handlers.py: 28 added lines, 0 removed lines.
Original line number Diff line number Diff line
"""
RFC 7807 Problem Details exception handlers.

Domain errors raised by framework-free layers are translated to
application/problem+json here. This module introduces the `_problem` helper and
the registration seam; concrete handlers are added alongside the errors that
need them (see MR-08).

Auth/authorization failures are handled inline in the api/auth layer via HTTPException, so they need no domain-error handler.
"""

from fastapi.responses import JSONResponse

_PROBLEM_MEDIA_TYPE = "application/problem+json"


def _problem(status: int, title: str, detail: str, instance: str) -> JSONResponse:
    return JSONResponse(
        status_code=status,
        media_type=_PROBLEM_MEDIA_TYPE,
        content={
            "type": "about:blank",
            "title": title,
            "status": status,
            "detail": detail,
            "instance": instance,
        },
    )
+15 −0
Changes for src/portal_service/api/health.py: 15 added lines, 0 removed lines.
Original line number Diff line number Diff line
"""
Health probes.

  GET /health/live   — process is up (liveness)

"""

from fastapi import APIRouter

health_router = APIRouter(tags=["health"])


@health_router.get("/health/live")
async def live() -> dict[str, str]:
    return {"status": "alive"}
+10 −0
Changes for src/portal_service/api/middlewares/__init__.py: 10 added lines, 0 removed lines.
Original line number Diff line number Diff line
from fastapi import FastAPI

from portal_service.api.middlewares.logging import LoggingMiddleware
from portal_service.api.middlewares.request_id import RequestIdMiddleware


def register_middlewares(app: FastAPI) -> None:
    # RequestId must be added after Logging so it runs first (LIFO middleware stack).
    app.add_middleware(LoggingMiddleware)
    app.add_middleware(RequestIdMiddleware)
+34 −0
Changes for src/portal_service/api/middlewares/logging.py: 34 added lines, 0 removed lines.
Original line number Diff line number Diff line
from time import time

import structlog
from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint
from starlette.requests import Request
from starlette.responses import Response
from structlog.contextvars import bind_contextvars, clear_contextvars

from portal_service.application.context import originating_request_id

logger: structlog.BoundLogger = structlog.get_logger(__name__)


class LoggingMiddleware(BaseHTTPMiddleware):
    async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) -> Response:
        clear_contextvars()
        bind_contextvars(
            request_id=originating_request_id.get(),
            http_method=request.method,
            path=request.url.path,
            client=request.client.host if request.client else "unknown",
        )

        logger.info("request received")
        t0 = time()

        response = await call_next(request)

        logger.info(
            "request completed",
            status=response.status_code,
            duration_ms=(time() - t0) * 1000,
        )
        return response
Loading