Logging, Monitoring ও Load Testing

Sentry, metrics, Locust

রোডম্যাপের মাস ৬পড়তে প্রায় ১৩ মিনিট

Production এ কিছু ভাঙলে সাধারণত প্রথম খবর আসে user এর কাছ থেকে: "app কাজ করছে না"। তখন তোমার হাতে কিছুই নেই, শুধু অনুমান। এই অধ্যায়ে এমন ব্যবস্থা বানাবো যাতে সমস্যা user এর আগে তুমি জানো, আর জানার পর মিনিটের মধ্যে কারণ খুঁজে পাও। সাথে load test করে আগেই জানবো system কতটা চাপ নিতে পারে।

তিনটা স্তম্ভ (System Design এর Observability অধ্যায়ে বিস্তারিত তত্ত্ব):

প্রশ্ন উদাহরণ
Log ঠিক কী ঘটেছিল? "order 42 এর payment ব্যর্থ, gateway timeout"
Metric সংখ্যায় কেমন চলছে? প্রতি সেকেন্ডে ৩০০ request, ১% error, p95 latency ২৮০ ms
Trace একটা request কোথায় কতক্ষণ কাটালো? API ১০ ms → DB ১৫০ ms → payment API ১.২ সেকেন্ড

#Structured logging

সাধারণ লেখার log মানুষ পড়তে পারে, কিন্তু খোঁজা আর গোনা কঠিন। Structured log (JSON) এ প্রতিটা তথ্য আলাদা field:

Terminal
uv add structlog
Python
# core/logging.py
import logging
import structlog


def setup_logging(json_logs: bool) -> None:
    logging.basicConfig(level=logging.INFO, format="%(message)s")
    structlog.configure(
        processors=[
            structlog.contextvars.merge_contextvars,          # request_id ইত্যাদি নিজে যোগ হবে
            structlog.processors.add_log_level,
            structlog.processors.TimeStamper(fmt="iso", utc=True),
            structlog.processors.format_exc_info,
            structlog.processors.JSONRenderer() if json_logs else structlog.dev.ConsoleRenderer(),
        ],
    )


log = structlog.get_logger()
Python
# middleware: প্রতিটা request এ id আর সময়
import time
import uuid
import structlog

@app.middleware("http")
async def request_context(request, call_next):
    rid = request.headers.get("X-Request-ID") or uuid.uuid4().hex[:16]
    structlog.contextvars.clear_contextvars()
    structlog.contextvars.bind_contextvars(request_id=rid, path=request.url.path, method=request.method)
    start = time.perf_counter()
    try:
        response = await call_next(request)
    except Exception:
        log.exception("request_failed")
        raise
    ms = round((time.perf_counter() - start) * 1000, 1)
    log.info("request_done", status=response.status_code, duration_ms=ms)
    response.headers["X-Request-ID"] = rid
    return response
Python
log.info("order_created", order_id=order.id, total=order.total, restaurant_id=order.restaurant_id)
JSON
{"event": "order_created", "order_id": 1042, "total": 55000, "restaurant_id": 15,
 "request_id": "a8f3c19e0b2d4f11", "path": "/v1/orders", "level": "info", "timestamp": "2026-10-29T06:15:02Z"}

এখন log system এ খোঁজা যায় order_id=1042 বা request_id=a8f3..., আর গোনা যায় "গত ১ ঘণ্টায় কয়টা payment_failed"।

#Error tracking: Sentry

Exception হলে সাথে সাথে খবর, পুরো traceback, কোন user, কোন request, কোন release সহ:

Terminal
uv add "sentry-sdk[fastapi]"
Python
import sentry_sdk

sentry_sdk.init(
    dsn=settings.sentry_dsn,
    environment=settings.environment,
    release=settings.git_sha,                 # কোন deploy এ bug এলো
    traces_sample_rate=0.1,                   # ১০% request এর performance trace
    send_default_pii=False,                   # ব্যক্তিগত তথ্য পাঠাবে না
)

Sentry একই error কে group করে ("এই error ৩৪২ বার, ৮১ জন user এর"), নতুন release এ নতুন error এলে জানায়। Flutter এর জন্যও sentry_flutter আছে, তাই app আর backend এর error এক জায়গায়, একই trace এ যুক্ত। (Firebase Crashlytics ও app এর দিকে ভালো বিকল্প।)

#Metric: Prometheus আর Grafana

কয়েকটা সংখ্যা সারাক্ষণ মাপো। Web service এর জন্য সবচেয়ে কাজের চারটা (golden signals):

  1. Latency: কত সময় লাগছে (গড় না, p95/p99)
  2. Traffic: প্রতি সেকেন্ডে কত request
  3. Errors: কত শতাংশ ব্যর্থ (5xx)
  4. Saturation: কতটা ভরা (CPU, memory, database connection pool, queue এর দৈর্ঘ্য)
Terminal
uv add prometheus-fastapi-instrumentator
Python
from prometheus_fastapi_instrumentator import Instrumentator

Instrumentator().instrument(app).expose(app, endpoint="/metrics", include_in_schema=False)

নিজের business metric ও:

Python
from prometheus_client import Counter, Histogram

ORDERS = Counter("orders_created_total", "মোট order", ["city"])
PAYMENT_LATENCY = Histogram("payment_gateway_seconds", "Payment gateway এর সময়")

ORDERS.labels(city="dhaka").inc()
with PAYMENT_LATENCY.time():
    await gateway.charge(...)

Prometheus প্রতি ১৫ সেকেন্ডে /metrics পড়ে জমা রাখে, Grafana তে graph আর dashboard। Managed বিকল্প: Grafana Cloud, Datadog, New Relic; ছোট project এ platform এর নিজের metric (Railway/Render) দিয়েও শুরু করা যায়।

#Alert: কখন জাগাবে

সব কিছুতে alert দিলে কেউ আর alert দেখে না। Alert দাও user এর সমস্যা তে:

  • 5xx error rate ৫ মিনিট ধরে ২% এর বেশি
  • p95 latency ১০ মিনিট ধরে ১ সেকেন্ডের বেশি
  • /health/ready ব্যর্থ
  • Queue তে ১০,০০০ এর বেশি job জমে আছে
  • Disk ৮৫% ভরা

CPU ৮০% হলেই alert না, যদি user এর অভিজ্ঞতা ঠিক থাকে।

#Load testing: Locust

Production এ যাওয়ার আগে জানো system কতটা নিতে পারে। Locust দিয়ে Python এ নকল user লেখা যায়:

Terminal
uv add --dev locust
Python
# locustfile.py
import random
import uuid
from locust import HttpUser, task, between


class NoteUser(HttpUser):
    wait_time = between(1, 3)                       # প্রতিটা কাজের মাঝে ১-৩ সেকেন্ড

    def on_start(self):
        email = f"load-{uuid.uuid4().hex[:8]}@test.com"
        self.client.post("/v1/auth/register", json={"email": email, "name": "Load", "password": "secret123"})
        r = self.client.post("/v1/auth/login", json={"email": email, "password": "secret123"})
        self.client.headers["Authorization"] = f"Bearer {r.json()['access_token']}"

    @task(5)                                         # সবচেয়ে বেশি: list দেখা
    def list_notes(self):
        self.client.get("/v1/notes?limit=20", name="/v1/notes")

    @task(2)
    def create_note(self):
        self.client.post("/v1/notes", json={"title": f"নোট {random.randint(1, 9999)}"},
                         headers={"Idempotency-Key": uuid.uuid4().hex})

    @task(1)
    def search(self):
        self.client.get("/v1/notes?search=নোট", name="/v1/notes?search")
Terminal
uv run locust -H http://localhost:8000
# browser এ http://localhost:8089, user সংখ্যা ধীরে ধীরে বাড়াও

যা দেখবে:

  • কত user/RPS পর্যন্ত p95 latency গ্রহণযোগ্য থাকে
  • কোথায় error শুরু হয় (প্রায়ই database connection pool শেষ)
  • সেই সময় CPU, memory, database এর কী অবস্থা
  • Bottleneck কোথায়: তারপর ঠিক করো (index, cache, pool size, worker সংখ্যা), আবার মাপো

#অনুশীলন

#নিজেকে যাচাই করো

Structured (JSON) log এর সুবিধা কী?

প্রতিটা তথ্য আলাদা field, তাই log system এ নির্দিষ্ট field দিয়ে খোঁজা (order_id, request_id), গোনা আর filter করা যায়। সাধারণ লেখা থেকে এগুলো বের করা কঠিন।

Latency এর গড় না দেখে p95/p99 কেন দেখবে?

গড় অল্প কিছু খুব ধীর request কে লুকিয়ে ফেলে। p95/p99 দেখায় ধীরতম ৫% বা ১% user এর অভিজ্ঞতা, যারা আসলে সমস্যায় আছে।

চারটা golden signal কী?

Latency (কত সময়), Traffic (কত request), Errors (কত ব্যর্থ), Saturation (resource কতটা ভরা)।

Load test এর আসল উদ্দেশ্য কী?

System কতটা চাপ নিতে পারে আর প্রথম কোথায় ভাঙে (bottleneck) সেটা আগেভাগে জানা, যাতে সেটা ঠিক করা যায় আর বাস্তব চাপের জন্য পরিকল্পনা করা যায়।

চিহ্ন দিলে sidebar আর রোডম্যাপে এই অধ্যায়ের station ভরে যাবে।