Observability
Observability یعنی توانایی فهم وضعیت داخلی سیستم از روی خروجیهایش. در میکروسرویس، به دلیل توزیعشدگی، observability از monitoring فراتر میرود.
۱۲.۱ مقدمه
Observability یعنی توانایی فهم وضعیت داخلی سیستم از روی خروجیهایش. در میکروسرویس، به دلیل توزیعشدگی، observability از monitoring فراتر میرود.
۱۲.۲ سه ستون Observability
📝 Logs
رویدادهای discrete که در طول زمان اتفاق میافتند. «چه چیزی اتفاق افتاد؟»
📊 Metrics
اعداد aggregate شده در طول زمان. «وضعیت سیستم چطور است؟»
🔍 Traces
مسیر یک request در سراسر سرویسها. «request کجا زمان از دست داد؟»
۱۲.۳ Logging
Structured Logging
به جای string ساده، JSON با فیلدهای مشخص استفاده کنید.
import structlog
import logging
# تنظیم structlog
structlog.configure(
processors=[
structlog.contextvars.merge_contextvars,
structlog.processors.add_log_level,
structlog.processors.TimeStamper(fmt="iso"),
structlog.processors.StackInfoRenderer(),
structlog.processors.format_exc_info,
structlog.processors.JSONRenderer(),
],
wrapper_class=structlog.make_filtering_bound_logger(logging.INFO),
logger_factory=structlog.PrintLoggerFactory(),
)
logger = structlog.get_logger()
# استفاده
logger.info(
"order_placed",
order_id=123,
user_id=456,
total=1500000,
items_count=3,
)
# خروجی JSON:
# {"event": "order_placed", "order_id": 123, "user_id": 456,
# "total": 1500000, "items_count": 3, "timestamp": "2026-05-01T10:30:00Z",
# "level": "info"}
Correlation ID
ID مشترک برای trace کردن یک request در سرویسهای مختلف.
from contextvars import ContextVar
from fastapi import FastAPI, Request
import uuid
correlation_id: ContextVar[str] = ContextVar("correlation_id")
app = FastAPI()
@app.middleware("http")
async def add_correlation_id(request: Request, call_next):
cid = request.headers.get("X-Correlation-ID") or str(uuid.uuid4())
correlation_id.set(cid)
response = await call_next(request)
response.headers["X-Correlation-ID"] = cid
return response
# در هر log
logger = structlog.get_logger()
logger.info("processing", correlation_id=correlation_id.get())
ELK Stack
- Elasticsearch: ذخیره و search لاگها
- Logstash: جمعآوری و پردازش
- Kibana: visualization و query
- Filebeat: ارسال لاگ از سرور
Loki — جایگزین سبک
Grafana Loki — ELK سبکتر، فقط label-based.
Best Practices
- Log به stdout/stderr (نه فایل)
- JSON structured logging
- سطحبندی صحیح (DEBUG، INFO، WARN، ERROR)
- هرگز credentials در log!
- Correlation ID در همه لاگها
- Log rotation برای disk
- Sampling در high-volume services
۱۲.۴ Metrics با Prometheus
Prometheus استاندارد industry برای metrics است.
چهار نوع Metric
- Counter: فقط افزایش (تعداد requests)
- Gauge: بالا/پایین (تعداد active connections)
- Histogram: توزیع مقادیر (request duration)
- Summary: مثل Histogram با pre-calculated quantiles
Instrumentation در FastAPI
from prometheus_client import Counter, Histogram, Gauge, generate_latest
from fastapi import FastAPI, Response
import time
# Metrics
requests_total = Counter(
"http_requests_total",
"Total HTTP requests",
["method", "endpoint", "status"]
)
request_duration = Histogram(
"http_request_duration_seconds",
"HTTP request duration",
["method", "endpoint"],
buckets=(0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0)
)
active_users = Gauge("active_users", "Currently active users")
app = FastAPI()
@app.middleware("http")
async def metrics_middleware(request, call_next):
start = time.time()
response = await call_next(request)
duration = time.time() - start
requests_total.labels(
method=request.method,
endpoint=request.url.path,
status=response.status_code,
).inc()
request_duration.labels(
method=request.method,
endpoint=request.url.path,
).observe(duration)
return response
@app.get("/metrics")
async def metrics():
return Response(content=generate_latest(), media_type="text/plain")
RED Method (طلایی)
سه metric اصلی برای request-driven services:
- Rate: تعداد requests در ثانیه
- Errors: تعداد errors
- Duration: latency distribution
USE Method
برای resources (CPU، Memory، Disk):
- Utilization: درصد مصرف
- Saturation: کارهای منتظر
- Errors: خطاها
PromQL Queries مفید
# Rate of errors
rate(http_requests_total{status=~"5.."}[5m])
# 95th percentile latency
histogram_quantile(0.95,
rate(http_request_duration_seconds_bucket[5m])
)
# Error rate %
sum(rate(http_requests_total{status=~"5.."}[5m]))
/ sum(rate(http_requests_total[5m])) * 100
# CPU usage
rate(container_cpu_usage_seconds_total[5m]) * 100
۱۲.۵ Grafana — Dashboard
Grafana برای visualization metrics از Prometheus، Loki، Elasticsearch و… استفاده میشود.
# docker-compose.yml
services:
prometheus:
image: prom/prometheus
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
ports:
- "9090:9090"
grafana:
image: grafana/grafana
ports:
- "3000:3000"
environment:
GF_SECURITY_ADMIN_PASSWORD: admin
volumes:
- grafana_data:/var/lib/grafana
- ./dashboards:/etc/grafana/provisioning/dashboards
# prometheus.yml
global:
scrape_interval: 15s
scrape_configs:
- job_name: "user-service"
static_configs:
- targets: ["user-service:8000"]
- job_name: "product-service"
static_configs:
- targets: ["product-service:8000"]
- job_name: "kubernetes-pods"
kubernetes_sd_configs:
- role: pod
relabel_configs:
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
action: keep
regex: true
۱۲.۶ Distributed Tracing
برای فهم اینکه یک request در کدام سرویس چقدر زمان برد و کجا fail شد.
OpenTelemetry — استاندارد جدید
from opentelemetry import trace
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.sdk.resources import Resource
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
from opentelemetry.instrumentation.httpx import HTTPXClientInstrumentor
from opentelemetry.instrumentation.sqlalchemy import SQLAlchemyInstrumentor
# تنظیم
resource = Resource(attributes={
"service.name": "order-service",
"service.version": "1.2.3",
"deployment.environment": "production",
})
provider = TracerProvider(resource=resource)
processor = BatchSpanProcessor(
OTLPSpanExporter(endpoint="http://jaeger:4317", insecure=True)
)
provider.add_span_processor(processor)
trace.set_tracer_provider(provider)
# Auto-instrumentation
FastAPIInstrumentor.instrument_app(app)
HTTPXClientInstrumentor().instrument()
SQLAlchemyInstrumentor().instrument(engine=db_engine)
# Manual span
tracer = trace.get_tracer(__name__)
@app.post("/orders")
async def create_order(data):
with tracer.start_as_current_span("create_order") as span:
span.set_attribute("user_id", data.user_id)
span.set_attribute("items_count", len(data.items))
with tracer.start_as_current_span("validate_inventory"):
await validate_inventory(data.items)
with tracer.start_as_current_span("save_order"):
order = await save_order(data)
span.set_attribute("order_id", order.id)
with tracer.start_as_current_span("publish_event"):
await publish_event("order.placed", order)
return order
Jaeger — UI برای trace ها
services:
jaeger:
image: jaegertracing/all-in-one:latest
ports:
- "16686:16686" # UI
- "4317:4317" # OTLP gRPC
- "4318:4318" # OTLP HTTP
environment:
COLLECTOR_OTLP_ENABLED: "true"
پس از اجرا، در http://localhost:16686 trace ها قابل مشاهده هستند.
۱۲.۷ Alerting
Prometheus Alertmanager برای اعلام مشکلات.
# alerts.yml
groups:
- name: service_alerts
rules:
- alert: HighErrorRate
expr: |
sum(rate(http_requests_total{status=~"5.."}[5m]))
/ sum(rate(http_requests_total[5m])) > 0.05
for: 5m
labels:
severity: critical
annotations:
summary: "High error rate ({{ $value | humanizePercentage }})"
- alert: HighLatency
expr: |
histogram_quantile(0.95,
rate(http_request_duration_seconds_bucket[5m])
) > 1
for: 5m
labels:
severity: warning
annotations:
summary: "P95 latency > 1s"
- alert: PodCrashLooping
expr: rate(kube_pod_container_status_restarts_total[15m]) > 0
for: 5m
labels:
severity: critical
Alertmanager Routing
route:
group_by: ["alertname", "service"]
group_wait: 30s
group_interval: 5m
repeat_interval: 4h
receiver: default
routes:
- match:
severity: critical
receiver: pagerduty
- match:
severity: warning
receiver: slack
receivers:
- name: default
email_configs:
- to: alerts@shop.com
- name: slack
slack_configs:
- api_url: https://hooks.slack.com/...
channel: "#alerts"
- name: pagerduty
pagerduty_configs:
- service_key: ...
۱۲.۸ SLI، SLO، SLA
- SLI (Service Level Indicator): یک metric که اندازه میگیریم (مثلاً availability)
- SLO (Service Level Objective): هدف داخلی (مثلاً 99.9% availability)
- SLA (Service Level Agreement): قرارداد با مشتری (با penalty)
Error Budget
اگر SLO 99.9% است، error budget = 0.1% = ~43 دقیقه downtime در ماه. اگر این budget مصرف شود، deploy های جدید متوقف میشوند تا stability برگردد.
۱۲.۹ بهترین تجربیات
- سه ستون با هم. logs، metrics، traces مکمل یکدیگرند.
- OpenTelemetry standard. برای آیندهنگری.
- Correlation ID همیشه. در logs و traces.
- RED/USE Method. برای metrics.
- Dashboard هر سرویس. RED metrics + business metrics.
- Alert فقط actionable. alert fatigue واقعی است.
- SLO-based alerting. نه threshold دلخواه.
- Sampling در tracing. 100% trace هزینهبر است.
- Log اطلاعات حساس را scrub کنید.
- Cardinality کنترل شده. labels زیاد metric را منفجر میکند.
۱۲.۱۰ خلاصه فصل
آنچه آموختیم:
- سه ستون Observability: Logs، Metrics، Traces
- Structured logging با structlog و ELK
- Prometheus metrics با ۴ نوع: Counter، Gauge، Histogram، Summary
- RED Method و USE Method
- Distributed Tracing با OpenTelemetry و Jaeger
- Grafana برای dashboard
- Alerting با Alertmanager
- SLI، SLO، SLA و Error Budget