“Pourquoi la prod est lente ?” Sans observability, impossible de répondre. Voici comment implémenter les 3 piliers pour debug efficace en production.

Monitoring vs Observability

Monitoring (approche classique)

Savoir QUAND ça casse
→ Alertes sur métriques connues
→ "CPU > 80%" → Alerte

Limite : Ne répond pas au “Pourquoi ?”

Observability (approche moderne)

Comprendre POURQUOI ça casse
→ Investiguer comportements émergents
→ Corréler métriques + logs + traces

Exemple :

Alerte: API latency increased (+200ms)

Monitoring classique:
- "La latency est haute"
- Restart service ?

Observability:
- Trace montre : DB query lente
- Logs montrent : Lock contention
- Metrics montrent : Connexions DB saturées
→ Root cause: Missing index sur table users

Les 3 piliers

1. Métriques (Metrics)

Qu’est-ce que c’est ?

Valeurs numériques agrégées dans le temps.

http_requests_total{method="GET",status="200"} 45623
http_request_duration_ms{endpoint="/api/users",percentile="p95"} 145

Types de métriques :

# Counter : Toujours croissant
requests_total.inc()

# Gauge : Valeur instantanée
active_connections.set(42)

# Histogram : Distribution
request_duration.observe(0.145)  # seconds

Exemple Prometheus :

from prometheus_client import Counter, Histogram, Gauge

# Counter
requests_total = Counter(
    'http_requests_total',
    'Total HTTP requests',
    ['method', 'endpoint', 'status']
)

# Histogram (auto-génère p50, p95, p99)
request_duration = Histogram(
    'http_request_duration_seconds',
    'HTTP request duration',
    ['endpoint']
)

# Gauge
active_users = Gauge(
    'active_users_count',
    'Number of active users'
)

# Usage
@app.route('/api/users')
def get_users():
    with request_duration.labels(endpoint='/api/users').time():
        requests_total.labels(method='GET', endpoint='/api/users', status='200').inc()
        return fetch_users()

2. Logs

Qu’est-ce que c’est ?

Événements discrets avec contexte.

{
  "timestamp": "2025-11-07T10:15:30Z",
  "level": "error",
  "message": "Database connection failed",
  "user_id": "user_123",
  "request_id": "req_456",
  "error": "ETIMEDOUT",
  "duration_ms": 5000
}

Structured logging (à faire) :

// ❌ Log string (impossible à parser)
console.log('User user_123 created order order_456');

// ✅ Structured log (parsable, searchable)
logger.info({
  event: 'order_created',
  user_id: 'user_123',
  order_id: 'order_456',
  total: 149.99,
  currency: 'EUR'
});

Log levels :

logger.debug('Detailed debugging info');    // Dev only
logger.info('Normal operation');            // Info générale
logger.warn('Something unusual');           // Attention
logger.error('Error occurred');             // Erreur
logger.fatal('System crash');               // Critique

3. Traces distribuées

Qu’est-ce que c’est ?

Suivre une requête à travers plusieurs services.

User request → API Gateway → Auth Service → User Service → Database
     └─────────────────── Trace ──────────────────────┘

Exemple trace :

Trace ID: abc123
├─ Span: api-gateway (120ms)
│  ├─ Span: auth-service (25ms)
│  │  └─ Span: redis-get (2ms)
│  └─ Span: user-service (85ms)
│     ├─ Span: db-query-users (60ms) ⚠️ SLOW
│     └─ Span: cache-set (3ms)

Implémentation OpenTelemetry :

import { trace } from '@opentelemetry/api';

const tracer = trace.getTracer('my-service');

async function getUser(userId: string) {
  // Créer span
  return tracer.startActiveSpan('get-user', async (span) => {
    span.setAttribute('user.id', userId);

    try {
      // DB query (auto-instrumentée)
      const user = await db.query('SELECT * FROM users WHERE id = $1', [userId]);

      span.setAttribute('user.found', !!user);
      span.setStatus({ code: SpanStatusCode.OK });

      return user;
    } catch (error) {
      span.recordException(error);
      span.setStatus({ code: SpanStatusCode.ERROR });
      throw error;
    } finally {
      span.end();
    }
  });
}

Stack Observability moderne

Option 1 : Grafana Stack (Open Source)

Métriques: Prometheus + Grafana
Logs: Loki + Grafana
Traces: Tempo + Grafana

Total: ~$200/mois (self-hosted)

Architecture :

┌────────────┐
│Application │
└──┬──┬──┬───┘
   │  │  │
   │  │  └──────→ Tempo (Traces)
   │  └─────────→ Loki (Logs)
   └────────────→ Prometheus (Metrics)
                      ↓
                  Grafana (Visualisation)

Option 2 : Datadog (SaaS)

All-in-one :
- Metrics
- Logs
- Traces
- APM
- Alerting

Coût: $15-$30/host/mois
Total: ~$500-2000/mois

Option 3 : Hybrid

Metrics: Prometheus (gratuit)
Logs & Traces: Datadog (payant)

Compromis coût/features

Correlation des 3 piliers

Exemple : Debug latency spike

1. Metrics : Identifier le problème

Dashboard Grafana :
http_request_duration_p95{endpoint="/api/orders"} spike 150ms → 800ms

2. Traces : Localiser le bottleneck

Trace pour requête lente :
├─ API Gateway: 5ms
├─ Auth: 3ms
└─ Orders Service: 785ms ⚠️
   ├─ Get user: 8ms
   ├─ Get products: 12ms
   └─ Calculate shipping: 758ms ⚠️ ROOT CAUSE

3. Logs : Comprendre la cause

{
  "timestamp": "...",
  "level": "warn",
  "service": "shipping-calculator",
  "message": "External API timeout",
  "external_api": "shipping-rates-api",
  "timeout_ms": 5000,
  "retries": 3
}

Root cause : API externe de shipping lente.

Fix :

  • Ajouter cache pour shipping rates
  • Réduire timeout 5s → 2s
  • Fallback à rates par défaut

Dashboards essentiels

Dashboard 1 : RED Method (Services)

Rate: Requests/sec
Error: Error rate %
Duration: Latency (p50, p95, p99)

Grafana panel :

┌─────────────────────────────────────┐
│ Service: api-gateway                │
├─────────────────────────────────────┤
│ Rate:     1,250 req/s               │
│ Errors:   0.3% (🟢 < 1%)           │
│ Duration: p95 = 145ms (🟢 < 500ms) │
└─────────────────────────────────────┘

Dashboard 2 : USE Method (Infrastructure)

Utilization: % resource used
Saturation: Queue depth
Errors: Error count

Exemple :

┌──────────────────────────────────┐
│ Host: prod-api-01               │
├──────────────────────────────────┤
│ CPU:    45% (🟢 < 80%)         │
│ Memory: 68% (🟡 warning)       │
│ Disk I/O: Queue depth 2 (🟢)   │
│ Network: 125 Mbps (🟢)         │
└──────────────────────────────────┘

Dashboard 3 : Business Metrics

┌────────────────────────────────────┐
│ E-commerce KPIs                    │
├────────────────────────────────────┤
│ Orders/hour:    142 (🟢 normal)   │
│ Conversion:     3.2% (🟢 +0.1%)  │
│ Cart abandonment: 68% (🟡 high)   │
│ Revenue/hour:   $12,450           │
└────────────────────────────────────┘

Alerting intelligent

Seuils statiques (simple)

# Alertmanager rule
- alert: HighErrorRate
  expr: rate(http_requests_total{status=~"5.."}[5m]) > 0.05
  for: 5m
  annotations:
    summary: "Error rate > 5% for 5 minutes"

Problème : False positives (traffic varie)

Seuils dynamiques (mieux)

# Anomaly detection basique
from statistics import mean, stdev

def is_anomaly(current_value, historical_values):
    avg = mean(historical_values)
    std = stdev(historical_values)

    # Au-delà de 3 deviations standard
    return abs(current_value - avg) > 3 * std

# Alerte si anomalie
if is_anomaly(current_latency, last_7_days_latency):
    alert("Latency anomaly detected")

SLO-based alerting (optimal)

# Service Level Objective
slo:
  target: 99.9%  # 99.9% des requêtes < 500ms

  # Budget d'erreur
  error_budget: 0.1%  # 0.1% peuvent être > 500ms

# Alerte si budget épuisé
- alert: SLOBudgetExhausted
  expr: slo_error_budget_remaining < 0
  annotations:
    summary: "SLO budget exhausted, slow down deployments"

Cas réel : E-commerce Black Friday

Situation

  • Black Friday : 10x traffic habituel
  • Pas d’observability mature

Problème

11h00 : Site ralentit
11h15 : Checkout cassé
11h20 : Panique totale

Sans observability :

  • 2 heures de debug
  • $150k revenue perdu

Après : Stack Observability

Stack implémenté :

  • Grafana Loki (logs)
  • Tempo (traces)
  • Prometheus (metrics)
  • On-call avec PagerDuty

Black Friday suivant :

10h45 : Spike traffic détecté (metrics)
10h47 : Auto-scaling triggered
11h12 : DB slow query alert (trace)
11h15 : Index ajouté en 3 minutes
11h18 : Performance restored

Downtime : 0 minute Revenue : $2.4M (+60% vs année précédente)

Coûts et ROI

Coûts Stack Observability

Self-hosted (Grafana Stack) :

  • Infrastructure : $200/mois
  • Maintenance : 0.2 ETP (~$1k/mois)
  • Total : ~$1,200/mois

SaaS (Datadog) :

  • 20 hosts : $500/mois
  • Logs : $800/mois
  • Traces : $400/mois
  • Total : ~$1,700/mois

ROI

Avant Observability :

  • MTTR : 2-4 heures
  • Incidents/mois : 8
  • Coût incidents : ~$50k/an

Après Observability :

  • MTTR : 15-30 minutes (-85%)
  • Incidents/mois : 2 (-75%)
  • Économie : ~$35k/an

ROI : Payé en 4-6 mois.

Getting Started (2 semaines)

Semaine 1 : Metrics

# 1. Setup Prometheus
docker run -p 9090:9090 prom/prometheus

# 2. Instrumenter app
pip install prometheus-client

# 3. Exposer metrics
from prometheus_client import make_wsgi_app
app.mount("/metrics", make_wsgi_app())

# 4. Dashboard Grafana
docker run -p 3000:3000 grafana/grafana

Semaine 2 : Logs + Traces

# Logs : Loki
docker run -p 3100:3100 grafana/loki

# Traces : Tempo
docker run -p 3200:3200 grafana/tempo

# Instrumenter app
pip install opentelemetry-api opentelemetry-sdk

Conclusion

Observability n’est pas un luxe.

C’est une nécessité :

  • Réduire MTTR de 90%
  • Comprendre comportements prod
  • Anticiper problèmes

3 piliers à implémenter :

  1. Métriques (commencer ici)
  2. Logs (structured!)
  3. Traces (pour microservices)

Commencer simple :

  1. Prometheus + Grafana (semaine 1)
  2. Structured logging (semaine 2)
  3. Tracing si microservices (semaine 3-4)

Et vous, votre stack observability ?