"""Application initialisation. Creates a FastAPI application. """ import asyncio from typing import Awaitable from typing import Callable from fastapi import FastAPI from fastapi import Request from fastapi import Response from owslogger import constants as logger_constants from owslogger import logger import sentry_sdk from sentry_sdk.integrations.asgi import SentryAsgiMiddleware from starlette.middleware.base import BaseHTTPMiddleware from dataexport.conf import config from dataexport.handlers.dataexport import api_router from dataexport.helpers.logging_helpers import app_logger from dataexport.logic import consume_kafka_msgs # Typing utility MiddlewareNextFunc = Callable[[Request], Awaitable[Response]] # Global exception handling middleware async def exception_handler(request: Request, call_next: MiddlewareNextFunc) -> Response: """Handle error when uncaught exception is raised. Default exception handler. Note: Exception will also be sent to Sentry if config.SENTRY is set. Returns: fastapi.Response: A 500 response with JSON 'code' & 'message' payload. """ try: return await call_next(request) except Exception: message = ( "The server encountered an internal error " "and was unable to complete your request." ) return Response(message, status_code=500) # Logging middleware async def log_responses(request: Request, call_next: MiddlewareNextFunc) -> Response: """Middleware for logging the responses. Args: request (Request): Request to log call_next (Callable): Function to call next Returns: Response """ # TODO Exclude certain paths (like health check) correlation_id = request.headers.get(logger_constants.CORRELATION_ID_HEADER) current_logger = logger.OwsLoggingAdapter( app_logger, {"correlation_id": correlation_id} ) response = await call_next(request) resources = { "account_id": request.headers.get(logger_constants.GRASS_ACCOUNT_ID_HEADER), "account_type": request.headers.get(logger_constants.GRASS_ACCOUNT_TYPE_HEADER), "user_id": request.headers.get(logger_constants.USER_ID_HEADER), } # TODO Different log methods based on response code current_logger.info( logger_constants.AUTOLOG_MESSAGE.format( status=response.status_code, verb=request.method, resource=request.url.path ), resources=resources, ) if correlation_id: response.headers[logger_constants.CORRELATION_ID_HEADER] = correlation_id return response # Setup FastAPI application def get_application() -> FastAPI: if config.ENVIRONMENT == config.PROD_ENVIRONMENT: app = FastAPI( title=config.SERVICE_NAME, version=config.SERVICE_VERSION, redoc_url=None, docs_url=None, ) else: app = FastAPI( title=config.SERVICE_NAME, version=config.SERVICE_VERSION, redoc_url=None ) app.add_middleware(BaseHTTPMiddleware, dispatch=log_responses) app.add_middleware(BaseHTTPMiddleware, dispatch=exception_handler) if config.SENTRY: sentry_sdk.init(dsn=config.SENTRY) app.add_middleware(SentryAsgiMiddleware) app.include_router(api_router, prefix="") return app app = get_application() @app.on_event("startup") async def startup_event(): loop = asyncio.get_running_loop() loop.create_task(consume_kafka_msgs())