[feat] wire observability into app

This commit is contained in:
Azamat 2026-03-20 21:38:51 +03:00
parent 741e63e978
commit 0c55329769
4 changed files with 20 additions and 19 deletions

View file

@ -4,7 +4,8 @@ from pathlib import Path
from adapter.config.loader import load_config
from adapter.config.model import AppConfig
from adapter.otel.bootstrap import OtelRuntime, setup_otel
from adapter.observability.factory import build_observability
from adapter.observability.runtime import ObservabilityRuntime
from repository.user import InMemoryUserRepository
from usecase.user import GetUser
@ -22,7 +23,7 @@ class AppUsecases:
@dataclass(slots=True)
class AppContainer:
config: AppConfig
observability: OtelRuntime
observability: ObservabilityRuntime
repositories: AppRepositories
usecases: AppUsecases
_is_shutdown: bool = field(default=False, init=False, repr=False)
@ -51,7 +52,7 @@ def build_container(
environ=environ,
)
observability = setup_otel(app_config)
observability = build_observability(app_config)
user_repository = InMemoryUserRepository()
repositories = AppRepositories(user=user_repository)

View file

@ -13,6 +13,6 @@ def create_app(config: AppConfig | None = None) -> FastAPI:
app_config = load_config() if config is None else config
app = FastAPI(title=app_config.app.name, lifespan=app_lifespan)
setattr(app.state, APP_CONFIG_STATE, app_config)
register_middleware(app)
register_middleware(app, app_config)
app.include_router(v1_router, prefix=API_V1_PREFIX)
return app

View file

@ -19,11 +19,13 @@ async def app_lifespan(app: FastAPI) -> AsyncIterator[None]:
setattr(app.state, APP_CONTAINER_STATE, container)
try:
FastAPIInstrumentor.instrument_app(
app,
tracer_provider=container.observability.tracer_provider,
)
instrumented = True
tracer_provider = container.observability.tracer_provider
if tracer_provider is not None:
FastAPIInstrumentor.instrument_app(
app,
tracer_provider=tracer_provider,
)
instrumented = True
yield
finally:
if instrumented:

View file

@ -1,5 +1,6 @@
from time import perf_counter
from adapter.config.model import AppConfig
from adapter.http.fastapi.dependencies import get_container
from fastapi import FastAPI, Request, Response
@ -7,7 +8,7 @@ REQUEST_COUNT = 'http.server.request.count'
REQUEST_DURATION = 'http.server.request.duration'
def register_middleware(app: FastAPI) -> None:
def register_middleware(app: FastAPI, config: AppConfig) -> None:
@app.middleware('http')
async def request_logging_middleware(
request: Request,
@ -33,6 +34,9 @@ def register_middleware(app: FastAPI) -> None:
},
)
if not config.metrics.enabled:
return
@app.middleware('http')
async def metrics_middleware(
request: Request,
@ -48,9 +52,11 @@ def register_middleware(app: FastAPI) -> None:
finally:
duration_ms = (perf_counter() - start) * 1000
container = get_container(request)
route = request.scope.get('route')
path = getattr(route, 'path', None)
attrs: dict[str, str | int] = {
'http.method': request.method,
'http.path': _metric_path(request),
'http.path': path if isinstance(path, str) and path else 'unmatched',
'http.status_code': status_code,
}
container.observability.metrics.increment(REQUEST_COUNT, attrs=attrs)
@ -59,11 +65,3 @@ def register_middleware(app: FastAPI) -> None:
duration_ms,
attrs=attrs,
)
def _metric_path(request: Request) -> str:
route = request.scope.get('route')
path = getattr(route, 'path', None)
if isinstance(path, str) and path:
return path
return 'unmatched'