import logging import marshmallow import os import sentry_sdk from aiohttp import TraceConfig, web from aiohttp_apispec import setup_aiohttp_apispec, validation_middleware from aiohttp_apispec.aiohttp_apispec import resolver from apispec.ext.marshmallow import MarshmallowPlugin from ddtrace import config as dd_config from ddtrace import tracer from ddtrace.contrib.aiohttp import trace_app from typing import List from server import config from server.api import router as routers from server.cache.base import get_cache, init_cache from server.client.session import get_session, init_session from server.middlewares import auth_middleware, error_middleware, headers_middleware, logging_middleware logger = logging.getLogger("app") def init_sentry(app: web.Application) -> None: """Initialize Sentry monitoring.""" if config.SENTRY_DSN: from sentry_sdk.integrations.aiohttp import AioHttpIntegration from sentry_sdk.utils import BadDsn try: sentry_sdk.init(environment=config.SERVICE_ENV, dsn=config.SENTRY_DSN, integrations=[AioHttpIntegration()]) except BadDsn: pass def init_apispec(app: web.Application) -> None: """Initialize swagger documentation.""" plugin = MarshmallowPlugin(schema_name_resolver=resolver) try: """ We extend default apispec DEFAULT_FIELD_MAPPING mapping with fields.Method and field.Fucntion to string representation so that our swagger displayed this fields as "string" as default (by default this fields have no representation and aren`t displayed in swagger) To use different data type representation you can add an example of the response to this fields right inside response schema by using ApispecTypes of apollo_utils.swagger.utils pkg """ plugin.Converter.field_mapping[marshmallow.fields.Method] = ("string", None) plugin.Converter.field_mapping[marshmallow.fields.Function] = ("string", None) except NameError: pass setup_aiohttp_apispec( app=app, title="Apollo Gate Service.", version="1.0.0", swagger_path="/api/doc/", securityDefinitions={ "apiKey": { "type": "apiKey", "name": "Authorization", "in": "header", "description": "For example: Bearer test", } }, schemes=["http", "https"], security=[{"apiKey": []}], ) setup_aiohttp_apispec.plugin = plugin def init_ddog(app: web.Application) -> None: """Initialize DataDog monitoring.""" if os.environ.get("DATADOG_SERVICE_NAME"): dd_config.http.trace_query_string = True dd_config.trace_headers(config.DD_TRACE_HEADERS) trace_app(app, tracer) def get_session_trace_config() -> List[TraceConfig]: """Configure tracing for aiohttp session to track remote calls.""" async def on_request_end(session, trace_config_ctx, params): logger.info(f"Request [{params.response.status}] {params.method} {params.url}") trace_config = TraceConfig() trace_config.on_request_end.append(on_request_end) return [trace_config] async def context(app: web.Application) -> None: trace_configs = get_session_trace_config() if config.DEBUG else None init_session(trace_configs=trace_configs) await init_cache(**config.CACHE_CONFIG) yield await get_cache().cache_client.close() await get_session().close() def create_app() -> web.Application: """Application factory""" logging.basicConfig(level=logging.INFO) app = web.Application( middlewares=[error_middleware, logging_middleware, auth_middleware, validation_middleware, headers_middleware], handler_args={"max_line_size": config.MAX_LINE_SIZE, "max_field_size": config.MAX_FIELD_SIZE}, client_max_size=config.MAX_BODY_SIZE, ) app.cleanup_ctx.append(context) for router in routers: app.add_routes(router) init_sentry(app) init_apispec(app) init_ddog(app) return app async def main() -> web.Application: """Main entry point""" return create_app() if __name__ == "__main__": web.run_app(main())