Ilyes Bouzayen
11 juil. 2026

Observabilité en 2026 : Traces, Métriques et Logs en Production

14 min de lecture

Observabilité en 2026 : Traces, Métriques et Logs en Production

Le monitoring vous dit que quelque chose est cassé. L'observabilité vous dit pourquoi.

Les trois piliers

1. Métriques  → "Le CPU est à 95%"
2. Logs       → "Erreur: connection timeout à 14:32"
3. Traces     → "La requête /api/orders a pris 12s au lieu de 200ms"
              └→ Le slow query est sur la table sessions (JOIN manquant)

Stack OpenTelemetry

// Instrumentation automatique Node.js
import { NodeSDK } from '@opentelemetry/sdk-node';
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
import { PrometheusExporter } from '@opentelemetry/exporter-metrics-prometheus';

const sdk = new NodeSDK({
  traceExporter: new OTLPTraceExporter({
    url: 'http://otel-collector:4318/v1/traces'
  }),
  metricReader: new PrometheusExporter({ port: 9464 }),
  instrumentations: [
    getNodeAutoInstrumentations({
      '@opentelemetry/instrumentation-http': { enabled: true },
      '@opentelemetry/instrumentation-express': { enabled: true },
      '@opentelemetry/instrumentation-pg': { enabled: true },
      '@opentelemetry/instrumentation-redis': { enabled: true }
    })
  ]
});

sdk.start();

Traces distribuées

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

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

async function processOrder(orderId: string) {
  return tracer.startActiveSpan('process-order', async (span) => {
    span.setAttribute('order.id', orderId);

    try {
      const order = await db.order.findUnique({ where: { id: orderId } });
      span.setAttribute('order.total', order.total);

      const payment = await chargePayment(order);
      span.setAttribute('payment.id', payment.id);

      await sendConfirmation(order);
      span.setStatus({ code: SpanStatusCode.OK });
    } catch (error) {
      span.setStatus({ code: SpanStatusCode.ERROR, message: error.message });
      span.recordException(error);
      throw error;
    } finally {
      span.end();
    }
  });
}

Structured Logging

// ❌ Mauvais : log non structuré
console.log('User logged in: ' + userId);

// ✅ Bon : log structuré JSON
import pino from 'pino';

const logger = pino({
  formatters: {
    level: (label) => ({ level: label }),
    bindings: (bindings) => ({
      pid: bindings.pid,
      service: 'auth-service',
      version: process.env.APP_VERSION
    })
  },
  timestamp: pino.stdTimeFunctions.isoTime
});

logger.info({ userId, method: 'google', ip: req.ip }, 'User authenticated');
// → {"level":"info","time":"2026-07-12T10:00:00Z","service":"auth-service","userId":"abc","method":"google","ip":"1.2.3.4","msg":"User authenticated"}

Alerting intelligent

# Grafana Alert Rule
groups:
  - name: api-alerts
    rules:
      - alert: HighLatency
        expr: histogram_quantile(0.95, http_request_duration_seconds_bucket) > 2
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "Latence API élevée"
          description: "p95 au-dessus de 2s depuis 5 minutes"

      - alert: ErrorRate
        expr: rate(http_requests_total{status=~"5.."}[5m]) / rate(http_requests_total[5m]) > 0.05
        for: 2m
        labels:
          severity: critical
        annotations:
          summary: "Taux d'erreur > 5%"

Dashboard de base

Panneau Métrique Seuil
Requêtes/s rate(http_requests_total[5m]) > 1000
Latence p95 histogram_quantile(0.95, ...) > 2s
Taux d'erreur 5xx / total > 5%
Mémoire process_resident_memory_bytes > 1GB
CPU rate(process_cpu_seconds_total[1m]) > 80%

Conclusion

Observabilité n'est pas plus de logs. C'est les bons logs, les bonnes métriques, les bonnes traces, au bon moment.