From ca8287cd8bd6614e867508c3a197ecc35991a891 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Trkan?= Date: Sun, 9 Nov 2025 12:43:27 +0100 Subject: [PATCH] feat(prometheus): add custom metrics --- 7project/backend/app/app.py | 5 +++ 7project/backend/app/services/prometheus.py | 48 +++++++++++++++++++++ 7project/meetings/2025-10-30-meeting.md | 4 +- 3 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 7project/backend/app/services/prometheus.py diff --git a/7project/backend/app/app.py b/7project/backend/app/app.py index 98b2c66..6bb0e5d 100644 --- a/7project/backend/app/app.py +++ b/7project/backend/app/app.py @@ -9,6 +9,8 @@ from fastapi.middleware.cors import CORSMiddleware from prometheus_fastapi_instrumentator import Instrumentator, metrics from starlette.requests import Request +from app.services.prometheus import number_of_users, number_of_transactions + from app.services import bank_scraper from app.workers.celery_tasks import load_transactions, load_all_transactions from app.models.user import User, OAuthAccount @@ -50,6 +52,9 @@ fastApi.add_middleware( prometheus = Instrumentator().instrument(fastApi) +# Register custom metrics +prometheus.add(number_of_users()).add(number_of_transactions()) + prometheus.expose( fastApi, endpoint="/metrics", diff --git a/7project/backend/app/services/prometheus.py b/7project/backend/app/services/prometheus.py new file mode 100644 index 0000000..0f9380a --- /dev/null +++ b/7project/backend/app/services/prometheus.py @@ -0,0 +1,48 @@ +from typing import Callable +from prometheus_fastapi_instrumentator.metrics import Info +from prometheus_client import Gauge +from sqlalchemy import select, func + +from app.core.db import async_session_maker +from app.models.transaction import Transaction +from app.models.user import User + + +def number_of_users() -> Callable[[Info], None]: + METRIC = Gauge( + "number_of_users_total", + "Number of registered users.", + labelnames=("users",) + ) + + async def instrumentation(info: Info) -> None: + try: + async with async_session_maker() as session: + result = await session.execute(select(func.count(User.id))) + user_count = result.scalar_one() or 0 + except Exception: + # In case of DB errors, avoid crashing metrics endpoint + user_count = 0 + METRIC.labels(users="total").set(user_count) + + return instrumentation + + +def number_of_transactions() -> Callable[[Info], None]: + METRIC = Gauge( + "number_of_transactions_total", + "Number of transactions stored.", + labelnames=("transactions",) + ) + + async def instrumentation(info: Info) -> None: + try: + async with async_session_maker() as session: + result = await session.execute(select(func.count()).select_from(Transaction)) + transaction_count = result.scalar_one() or 0 + except Exception: + # In case of DB errors, avoid crashing metrics endpoint + transaction_count = 0 + METRIC.labels(transactions="total").set(transaction_count) + + return instrumentation diff --git a/7project/meetings/2025-10-30-meeting.md b/7project/meetings/2025-10-30-meeting.md index aa3c380..503b366 100644 --- a/7project/meetings/2025-10-30-meeting.md +++ b/7project/meetings/2025-10-30-meeting.md @@ -43,8 +43,8 @@ The tracker should not store the transactions in the database - security vulnera Last 3 minutes of the meeting, summarize action items. -- [ ] Change the name on frontend from 7project -- [ ] Finalize the funcionality and everyting in the code part +- [x] Change the name on frontend from 7project +- [x] Finalize the funcionality and everyting in the code part - [ ] Try to finalize report with focus on reproducibility - [ ] More high level explanation of the workflow in the report