Version 0.4.0
All checks were successful
Run linters on applied template / Python 3.13 lint and build (push) Successful in 1m40s

Changes:
- put ObservabilityMiddleware before ExceptionHandlerMiddleware to avoid repetative code
- add application startup and last metrics update metrics along with CPU usage metric and threads count
- move host and port to new uvicorn section at config along with new reload and forwarded_allow_ips
- add request_id and remove trace_id/span_id generation if tracing is disabled
- move logging logic from utils to observability
- pass trace_id/span_id in HEX form
This commit is contained in:
2026-01-03 11:01:43 +03:00
parent b8acb017fd
commit 53f14a8624
26 changed files with 901 additions and 730 deletions

View File

@@ -2,8 +2,9 @@
import os
from contextlib import asynccontextmanager
from typing import NoReturn
from fastapi import FastAPI
from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.middleware.gzip import GZipMiddleware
from fastapi.openapi.docs import get_swagger_ui_html
@@ -14,30 +15,17 @@ from {{project_slug}}.db.connection.manager import PostgresConnectionManager
from {{project_slug}}.dependencies import connection_manager_dep, logger_dep, metrics_dep
from {{project_slug}}.exceptions.mapper import ExceptionMapper
from {{project_slug}}.handlers.debug import DebugException, DebugExceptionWithParams
from {{project_slug}}.middlewares.exception_handler import ExceptionHandlerMiddleware
from {{project_slug}}.middlewares.exception_handler import ExceptionHandlerMiddleware, HandlerNotFoundError
from {{project_slug}}.middlewares.observability import ObservabilityMiddleware
from {{project_slug}}.observability.metrics import init_metrics
from {{project_slug}}.observability.logging import configure_logging
from {{project_slug}}.observability.metrics import setup_metrics
from {{project_slug}}.observability.otel_agent import OpenTelemetryAgent
from {{project_slug}}.utils.observability import URLsMapper, configure_logging
from {{project_slug}}.observability.utils import URLsMapper
from .handlers import list_of_routers
from .version import LAST_UPDATE, VERSION
def _get_exception_mapper(debug: bool) -> ExceptionMapper:
mapper = ExceptionMapper()
if debug:
mapper.register_simple(DebugException, 506, "That's how a debug exception look like")
mapper.register_func(
DebugExceptionWithParams,
lambda exc: JSONResponse(
{"error": "That's how a debug exception with params look like", "message": exc.message},
status_code=exc.status_code,
),
)
return mapper
def bind_routes(application: FastAPI, prefix: str, debug: bool) -> None:
"""Bind all routes to application."""
for router in list_of_routers:
@@ -84,6 +72,10 @@ def get_app(prefix: str = "/api") -> FastAPI:
swagger_css_url="https://unpkg.com/swagger-ui-dist@5.11.7/swagger-ui.css",
)
@application.exception_handler(404)
async def handle_404(request: Request, exc: Exception) -> NoReturn:
raise HandlerNotFoundError() from exc
application.add_middleware(
CORSMiddleware,
allow_origins=app_config.app.cors.allow_origins,
@@ -99,30 +91,34 @@ def get_app(prefix: str = "/api") -> FastAPI:
app_config.observability.logging,
tracing_enabled=app_config.observability.jaeger is not None,
)
metrics = init_metrics()
exception_mapper = _get_exception_mapper(app_config.app.debug)
metrics = setup_metrics()
exception_mapper = ExceptionMapper()
_register_exceptions(exception_mapper, debug=app_config.app.debug)
connection_manager = PostgresConnectionManager(
master=app_config.db.master,
replicas=app_config.db.replicas,
logger=logger,
application_name=f"{{project_slug}}_{VERSION}",
)
urls_mapper = URLsMapper(app_config.observability.prometheus.urls_mapping)
urls_mapper = URLsMapper()
urls_mapper.add_routes(application.routes)
connection_manager_dep.init_dispencer(application, connection_manager)
metrics_dep.init_dispencer(application, metrics)
logger_dep.init_dispencer(application, logger)
application.add_middleware(
ObservabilityMiddleware,
ExceptionHandlerMiddleware,
debug=app_config.app.debug,
exception_mapper=exception_mapper,
metrics=metrics,
urls_mapper=urls_mapper,
errors_metric=metrics.http.errors,
)
application.add_middleware(
ExceptionHandlerMiddleware,
debug=[app_config.app.debug],
exception_mapper=exception_mapper,
ObservabilityMiddleware,
metrics=metrics,
urls_mapper=urls_mapper,
)
return application
@@ -135,13 +131,9 @@ async def lifespan(application: FastAPI):
Initializes database connection in pass_services_dependencies middleware.
"""
app_config: {{ProjectName}}Config = application.state.config
logger = logger_dep.obtain(application)
logger = logger_dep.from_app(application)
await logger.ainfo("application is being configured", config=app_config.to_order_dict())
for middleware in application.user_middleware:
if middleware.cls == ExceptionHandlerMiddleware:
middleware.kwargs["debug"][0] = app_config.app.debug
await logger.ainfo("application is starting", config=app_config.to_order_dict())
otel_agent = OpenTelemetryAgent(
app_config.observability.prometheus,
@@ -153,4 +145,17 @@ async def lifespan(application: FastAPI):
otel_agent.shutdown()
def _register_exceptions(mapper: ExceptionMapper, debug: bool) -> None:
if debug:
mapper.register_simple(DebugException, 506, "That's how a debug exception look like")
mapper.register_func(
DebugExceptionWithParams,
lambda exc: JSONResponse(
{"error": "That's how a debug exception with params look like", "message": exc.message},
status_code=exc.status_code,
),
)
return mapper
app = get_app()