Les erreurs de monitoring qui tuent vos applications en production

Les erreurs de monitoring qui tuent vos applications en production

Vous avez mis en place Prometheus, Grafana, des dizaines d'alertes. Pourtant, l'incident de production est passé inaperçu pendant deux heures. Le problème n'est pas vos outils, mais comment vous les utilisez. Voici les erreurs classiques qui sabotent votre monitoring.

Erreur 1 : Trop d'alertes, zéro attention

Le problème

Votre canal Slack monitoring reçoit 200 alertes par jour. L'équipe a désactivé les notifications. Quand une vraie alerte critique arrive, personne ne la voit.

Symptômes

  • Le canal #alerts a plus de 100 messages non lus
  • Les développeurs ont muted le canal
  • Les alertes critiques sont noyées dans le bruit
  • Personne ne sait quelles alertes sont vraiment importantes

La solution

Règle d'or : une alerte doit déclencher une action humaine.

# AVANT : alerte sur CPU > 80%
- alert: HighCPU
  expr: cpu_usage > 80
  for: 1m
  labels:
    severity: warning

# APRÈS : alerte sur impact utilisateur réel
- alert: HighLatency
  expr: |
    histogram_quantile(0.95, sum(rate(http_server_requests_seconds_bucket[5m])) by (le)) > 1
  for: 5m
  labels:
    severity: critical
  annotations:
    summary: "P95 latency exceeded 1s for 5 minutes"
    runbook: "https://wiki.company.com/runbooks/high-latency"

Pyramide d'alertes :

NiveauCritèresNotificationExemple
P1 CriticalImpact utilisateur directPagerDuty + SlackSite down, paiements échouent
P2 WarningDégradation potentielleSlack #alertsLatence élevée, queue saturée
P3 InfoÀ surveillerDashboard uniquementDisk 70%, scaling proche

Action immédiate

Faites un audit de vos alertes actuelles :

# Lister toutes les alertes déclenchées cette semaine
promtool query instant 'ALERTS{alertstate="firing"}' | wc -l

# Ratio alertes actionnées vs ignorées ?

Si moins de 20% de vos alertes ont mené à une action, vous avez un problème.

Erreur 2 : Surveiller les mauvaises métriques

Le problème

Vous surveillez l'utilisation CPU de vos pods, mais pas le temps de réponse vu par l'utilisateur. Quand le CPU est à 90%, votre application fonctionne peut-être très bien. Quand il est à 20%, elle peut être complètement cassée.

Les métriques qui comptent vraiment (USE et RED)

Méthode RED pour les services :

MétriqueQuestionExemple Prometheus
RateCombien de requêtes/s ?rate(http_requests_total[5m])
ErrorsQuel % d'erreurs ?rate(http_requests_total{status=~"5.."}[5m])
DurationCombien de temps ?histogram_quantile(0.95, ...)

Méthode USE pour les ressources :

MétriqueQuestionExemple
UtilizationQuelle proportion utilisée ?CPU usage %
SaturationY a-t-il de la contention ?Queue depth, waiting threads
ErrorsY a-t-il des erreurs système ?Disk errors, network drops

Ce qu'il faut surveiller en priorité

# Groupe 1 : Impact utilisateur (RED)
- record: service:http_requests:rate5m
  expr: sum(rate(http_server_requests_seconds_count[5m])) by (service)

- record: service:http_errors:rate5m
  expr: sum(rate(http_server_requests_seconds_count{status=~"5.."}[5m])) by (service)

- record: service:http_latency:p95
  expr: histogram_quantile(0.95, sum(rate(http_server_requests_seconds_bucket[5m])) by (le, service))

# Groupe 2 : Saturation (avant que ça ne casse)
- record: service:queue_saturation
  expr: sum(queue_length) by (service) / sum(queue_capacity) by (service)

- record: service:connection_pool_saturation
  expr: hikaricp_connections_active / hikaricp_connections_max

Métriques business à ne pas oublier

// Ne surveillez pas que la technique
@Component
public class BusinessMetrics {

    private final MeterRegistry registry;

    @EventListener
    public void onOrderCompleted(OrderCompletedEvent event) {
        // Métrique business : conversions
        registry.counter("orders.completed",
            "payment_method", event.getPaymentMethod(),
            "plan", event.getPlan()
        ).increment();

        // Métrique business : revenu
        registry.summary("orders.revenue",
            "currency", event.getCurrency()
        ).record(event.getAmount());
    }

    @EventListener
    public void onOrderFailed(OrderFailedEvent event) {
        // Métrique business : pertes potentielles
        registry.counter("orders.failed",
            "reason", event.getReason()
        ).increment();
    }
}

Erreur 3 : Dashboards illisibles

Le problème

Votre dashboard principal affiche 47 graphiques. Personne ne sait où regarder en cas d'incident. Le temps de compréhension est de 10 minutes au lieu de 10 secondes.

Avant/Après

Dashboard chaotique :

  • 47 panneaux sans organisation
  • Métriques techniques et business mélangées
  • Pas de hiérarchie visuelle
  • Timeframes différents sur chaque panel

Dashboard efficace :

  • 6-8 panneaux maximum
  • Organisation hiérarchique (haut = critique)
  • Code couleur cohérent (vert = OK, rouge = problème)
  • Même timeframe partout

Structure recommandée

┌────────────────────────────────────────────────┐
│ SANTÉ GLOBALE                                   │
│ [🟢 API] [🟢 DB] [🟢 Cache] [🟡 Queue]          │
└────────────────────────────────────────────────┘

┌─────────────────────┬──────────────────────────┐
│ TRAFFIC             │ ERREURS                   │
│ [Requêtes/s graph]  │ [Error rate graph]       │
│ 1,250 req/s         │ 0.02%                    │
└─────────────────────┴──────────────────────────┘

┌─────────────────────┬──────────────────────────┐
│ LATENCE P95         │ SATURATION               │
│ [Latency graph]     │ [CPU/Mem/Queue graphs]   │
│ 45ms                │ CPU: 35% | Mem: 60%      │
└─────────────────────┴──────────────────────────┘

Dashboard par niveau

DashboardAudienceContenu
ExecutiveDirectionKPIs business, disponibilité, coûts
OperationsSRE/OpsSanté services, alertes actives, capacité
DebugDéveloppeursMétriques détaillées, logs, traces

Grafana best practices

{
  "dashboard": {
    "title": "Production - Vue Opérationnelle",
    "refresh": "30s",
    "time": {
      "from": "now-1h",
      "to": "now"
    },
    "panels": [
      {
        "title": "Santé Services",
        "type": "stat",
        "gridPos": { "h": 4, "w": 24, "x": 0, "y": 0 },
        "options": {
          "colorMode": "background",
          "graphMode": "none"
        }
      }
    ]
  }
}

Erreur 4 : Pas de runbooks

Le problème

L'alerte "Database connection pool exhausted" se déclenche à 3h du matin. Le développeur d'astreinte ne connaît pas ce système. Il passe 45 minutes à comprendre quoi faire.

Ce qu'un runbook doit contenir

# Alerte : Database Connection Pool Exhausted

## Symptômes
- Alerte `HikariPoolExhausted` déclenchée
- Requêtes en timeout
- Erreurs 503 sur l'API

## Impact
- P1 : Les utilisateurs ne peuvent pas se connecter
- Services affectés : auth-service, user-service

## Diagnostic rapide (< 2 min)
1. Vérifier le dashboard connexions :
   https://grafana.company.com/d/db-connections

2. Vérifier les requêtes lentes :
   ```sql
   SELECT pid, query, state, wait_event
   FROM pg_stat_activity
   WHERE state != 'idle'
   ORDER BY query_start;
   ```

3. Vérifier les locks :
   ```sql
   SELECT * FROM pg_locks WHERE NOT granted;
   ```

## Actions immédiates
### Si requêtes lentes identifiées
1. Killer les requêtes problématiques :
   ```sql
   SELECT pg_terminate_backend(pid) FROM pg_stat_activity
   WHERE query LIKE '%problematic_query%';
   ```

### Si charge excessive
1. Scale horizontalement :
   ```bash
   kubectl scale deployment api --replicas=10
   ```

2. Activer le circuit breaker :
   ```bash
   kubectl set env deployment/api CIRCUIT_BREAKER_ENABLED=true
   ```

## Escalade
- Si non résolu en 15 min : contacter @dba-team
- Si P1 prolongé : incident call avec @sre-lead

## Post-incident
- Créer un ticket d'analyse
- Mettre à jour ce runbook si nécessaire

Automatiser les runbooks

# Prometheus alert avec lien runbook
- alert: DatabasePoolExhausted
  expr: hikaricp_connections_active >= hikaricp_connections_max * 0.9
  for: 5m
  labels:
    severity: critical
  annotations:
    summary: "Connection pool at {{ $value }}% capacity"
    runbook_url: "https://wiki.company.com/runbooks/db-pool-exhausted"
    dashboard_url: "https://grafana.company.com/d/db-connections"

Erreur 5 : Ignorer les logs structurés

Le problème

Vos logs ressemblent à ça :

2026-01-16 10:30:00 ERROR Something went wrong processing request
2026-01-16 10:30:01 INFO Request completed

Impossible de filtrer, d'agréger, de corréler.

La solution : logs JSON structurés

{
  "@timestamp": "2026-01-16T10:30:00.000Z",
  "level": "ERROR",
  "logger": "com.company.OrderService",
  "message": "Failed to process order",
  "trace_id": "abc123def456",
  "span_id": "789xyz",
  "order_id": "ORD-12345",
  "customer_id": "CUST-67890",
  "error_type": "PaymentDeclinedException",
  "error_message": "Card declined",
  "service": "order-service",
  "environment": "production",
  "kubernetes": {
    "pod": "order-service-7d9f8b6c5-x2k4n",
    "namespace": "production"
  }
}

Configuration Spring Boot

<!-- logback-spring.xml -->
<configuration>
    <appender name="JSON" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="net.logstash.logback.encoder.LogstashEncoder">
            <includeMdcKeyName>trace_id</includeMdcKeyName>
            <includeMdcKeyName>span_id</includeMdcKeyName>
            <includeMdcKeyName>order_id</includeMdcKeyName>
            <customFields>
                {
                    "service": "${SPRING_APPLICATION_NAME:-unknown}",
                    "environment": "${ENVIRONMENT:-dev}"
                }
            </customFields>
        </encoder>
    </appender>

    <root level="INFO">
        <appender-ref ref="JSON"/>
    </root>
</configuration>

Requêtes Loki efficaces

# Toutes les erreurs d'un utilisateur spécifique
{service="order-service"} | json | customer_id="CUST-67890" | level="ERROR"

# Erreurs groupées par type
sum by (error_type) (count_over_time({service="order-service"} | json | level="ERROR" [1h]))

# Latence des requêtes par endpoint
avg by (endpoint) (
  {service="order-service"} | json | unwrap duration_ms
)

Erreur 6 : Pas de corrélation logs/traces/métriques

Le problème

Vous voyez un spike de latence dans Grafana. Vous cherchez dans les logs. Impossible de trouver les requêtes correspondantes. Vous perdez 30 minutes à faire le lien manuellement.

La solution : Trace ID partout

@RestController
public class OrderController {

    private static final Logger log = LoggerFactory.getLogger(OrderController.class);

    @PostMapping("/orders")
    public ResponseEntity<Order> createOrder(@RequestBody OrderRequest request) {
        // Le trace ID est automatiquement ajouté via MDC par OpenTelemetry
        String traceId = Span.current().getSpanContext().getTraceId();

        log.info("Creating order for customer {}", request.getCustomerId());

        try {
            Order order = orderService.create(request);
            log.info("Order {} created successfully", order.getId());
            return ResponseEntity.ok(order);

        } catch (Exception e) {
            log.error("Failed to create order: {}", e.getMessage());
            throw e;
        }
    }
}

Dashboard avec liens

{
  "panels": [
    {
      "title": "Error Rate",
      "type": "graph",
      "options": {
        "dataLinks": [
          {
            "title": "View traces in Jaeger",
            "url": "https://jaeger.company.com/trace/${__data.fields.trace_id}"
          },
          {
            "title": "View logs in Loki",
            "url": "https://grafana.company.com/explore?expr={trace_id=\"${__data.fields.trace_id}\"}"
          }
        ]
      }
    }
  ]
}

Erreur 7 : Oublier la baseline

Le problème

Vous recevez une alerte "CPU à 85%". Est-ce un problème ? Impossible de savoir si c'est normal à cette heure-ci ou si c'est anormal.

La solution : comparer à l'historique

# Alerte basée sur la déviation par rapport à la normale
- alert: AnomalyHighLatency
  expr: |
    http_request_duration_seconds:p95 >
    avg_over_time(http_request_duration_seconds:p95[7d] offset 1h) * 1.5
  for: 10m
  annotations:
    summary: "Latency 50% higher than usual for this time"

Métriques de baseline

# P95 latence moyenne sur 7 jours, même heure
avg_over_time(
  http_request_duration_seconds:p95[7d] offset 1h
)

# Traffic attendu (comparaison semaine précédente)
http_requests_total / http_requests_total offset 7d

# Comparaison jour précédent
increase(http_requests_total[1h]) / increase(http_requests_total[1h] offset 1d)

Checklist monitoring efficace

Avant de considérer votre monitoring comme "prêt pour la production" :

  • ☐ Moins de 10 alertes par jour en moyenne
  • ☐ Chaque alerte a un runbook associé
  • ☐ Métriques RED/USE implémentées
  • ☐ Logs structurés en JSON
  • ☐ Corrélation trace_id dans logs/métriques/traces
  • ☐ Dashboard principal lisible en < 10 secondes
  • ☐ Baselines définies pour les alertes
  • ☐ Équipe formée aux runbooks
  • ☐ Tests réguliers des alertes (chaos engineering)
  • ☐ Revue mensuelle des alertes ignorées

Plan d'action immédiat

  1. Semaine 1 : Audit des alertes actuelles

- Désactiver les alertes ignorées depuis > 1 mois

- Classifier les alertes restantes (P1/P2/P3)

  1. Semaine 2 : Restructurer les dashboards

- Créer un dashboard opérationnel avec 6-8 panneaux

- Ajouter les métriques RED manquantes

  1. Semaine 3 : Documentation

- Écrire un runbook pour chaque alerte P1

- Lier les runbooks aux alertes

  1. Semaine 4 : Corrélation

- Implémenter les logs structurés

- Ajouter le trace_id partout

- Créer les liens entre dashboards/logs/traces

Conclusion

Un bon monitoring n'est pas une question d'outils, mais de discipline. Moins d'alertes mais plus pertinentes. Des dashboards lisibles en quelques secondes. Des runbooks pour chaque scénario. Et surtout, une corrélation entre toutes vos sources de données.

Commencez par auditer vos alertes actuelles. Si plus de 50% sont ignorées, vous avez un problème à résoudre avant d'ajouter quoi que ce soit.


Pour mettre en place l'observabilité : Observabilité en production : logs, métriques et traces avec OpenTelemetry

Pour comprendre pourquoi vos projets échouent : Pourquoi votre RAG échoue (et comment le corriger)