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.