Général 17 min de lecture · 3 590 mots

Configuration du Collector OpenTelemetry (otel-collector-config.yaml)

Guide complet de l'observabilité avec OpenTelemetry : implémentation des trois piliers (métriques, traces, logs) pour des systèmes de production résilients.

Estimated reading time: 17 minutes

Introduction à l’observabilité moderne

L’observabilité est la capacité à comprendre l’état interne d’un système en examinant ses sorties. Dans les architectures distribuées modernes, cette capacité n’est plus un luxe mais une nécessité absolue. Contrairement au simple monitoring qui répond à « est-ce que le système fonctionne ? », l’observabilité permet de répondre à « pourquoi le système ne fonctionne pas comme prévu ? »

En 2025, OpenTelemetry a atteint sa pleine maturité avec le statut GA (Generally Available) pour tous les signaux télémétrie – métriques, traces et logs sont maintenant complètement stables. Cette standardisation représente un tournant majeur pour l’industrie, offrant enfin un framework unifié pour instrumenter, collecter et exporter les données de télémétrie indépendamment du vendor.

Les trois piliers de l’observabilité

Métriques : QUOI a changé

Les métriques sont des signaux agrégés qui répondent à la question « QUOI a changé ? » et résument le comportement du système dans le temps. Elles fournissent une vue quantitative des performances et de la santé du système, permettant de détecter rapidement les anomalies et de mesurer les tendances.

Les métriques se déclinent en plusieurs types :

  • Counters : Valeurs qui ne font qu’augmenter (nombre de requêtes, erreurs totales)
  • Gauges : Valeurs qui peuvent monter ou descendre (utilisation CPU, connexions actives)
  • Histograms : Distribution statistique des valeurs (latence des requêtes, taille des réponses)
  • Summaries : Calculs de quantiles et de sommes sur une fenêtre de temps glissante
  • Traces : OÙ se situe le problème

    Les traces représentent des unités de travail ou d’opérations qui suivent les opérations spécifiques effectuées par une requête, peignant une image de ce qui s’est passé pendant l’exécution. Une trace distribuée suit une requête à travers tous les services d’une architecture microservices, révélant les goulots d’étranglement et les dépendances.

    Chaque trace est composée de spans qui représentent une opération individuelle. Les spans contiennent :

  • Nom de l’opération et timestamp de début/fin
  • Attributs (métadonnées clé-valeur)
  • Événements (logs structurés attachés au span)
  • Liens vers d’autres spans
  • Contexte de trace (trace ID et span ID pour la corrélation)
  • Logs : POURQUOI cela s’est produit

    Les logs sont des messages horodatés émis par les services ou d’autres composants qui ne sont pas nécessairement associés à une requête utilisateur particulière ou à une transaction. Ils fournissent le contexte détaillé et les informations de debugging nécessaires pour comprendre les causes racines des problèmes.

    L’intégration des logs avec les traces et métriques permet une observabilité complète : les métriques alertent d’un problème, les traces identifient où il se produit, et les logs expliquent pourquoi.

    OpenTelemetry : Architecture et composants

    Vue d’ensemble du framework

    OpenTelemetry est un projet open source sous la Cloud Native Computing Foundation (CNCF) qui fournit un ensemble d’APIs, de bibliothèques, d’agents et de collecteurs pour capturer les traces distribuées, métriques et logs. C’est le résultat de la fusion de OpenTracing et OpenCensus, créant ainsi un standard unifié pour l’observabilité.

    Les composants principaux d’OpenTelemetry incluent :

  • APIs et SDKs : Interfaces de programmation pour instrumenter le code
  • Auto-instrumentation : Bibliothèques qui instrumentent automatiquement les frameworks populaires
  • Collector : Pipeline de traitement pour recevoir, transformer et exporter les données de télémétrie
  • Exporters : Connecteurs vers différents backends d’observabilité (Prometheus, Jaeger, Grafana, etc.)
  • Le protocole OTLP

    OpenTelemetry Protocol (OTLP) est le protocole natif pour le transport de données de télémétrie. Il supporte gRPC et HTTP/JSON, offrant efficacité et interopérabilité. OTLP est devenu le standard de facto pour l’échange de données d’observabilité en 2025.

    Implémentation pratique avec OpenTelemetry

    Initialisation et configuration du SDK

    L’initialisation d’OpenTelemetry doit se faire avant toute utilisation de bibliothèques instrumentées. Cette configuration au démarrage de l’application est critique pour garantir que toutes les opérations sont correctement tracées.

// Configuration complète d'OpenTelemetry pour Node.js
import { NodeSDK } from '@opentelemetry/sdk-node';
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-http';
import { PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics';
import { Resource } from '@opentelemetry/resources';
import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions';
import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base';
import { diag, DiagConsoleLogger, DiagLogLevel } from '@opentelemetry/api';

// Configuration du niveau de log pour debugging
diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.INFO);

// Définition des ressources (métadonnées du service)
const resource = new Resource({
  [SemanticResourceAttributes.SERVICENAME]: 'api-gateway',
  [SemanticResourceAttributes.SERVICEVERSION]: '1.3.0',
  [SemanticResourceAttributes.DEPLOYMENTENVIRONMENT]: process.env.NODEENV || 'development',
  'service.namespace': 'ecommerce',
  'service.instance.id': process.env.HOSTNAME || 'local'
});

// Configuration des exporters
const traceExporter = new OTLPTraceExporter({
  url: 'http://otel-collector:4318/v1/traces',
  headers: {},
});

const metricExporter = new OTLPMetricExporter({
  url: 'http://otel-collector:4318/v1/metrics',
  headers: {},
});

// Initialisation du SDK
const sdk = new NodeSDK({
  resource: resource,
  traceExporter: traceExporter,
  metricReader: new PeriodicExportingMetricReader({
    exporter: metricExporter,
    exportIntervalMillis: 60000, // Export toutes les 60 secondes
  }),
  instrumentations: [
    getNodeAutoInstrumentations({
      // Configuration fine de l'auto-instrumentation
      '@opentelemetry/instrumentation-http': {
        ignoreIncomingPaths: ['/health', '/metrics'],
        headersToSpanAttributes: {
          server: {
            requestHeaders: ['x-request-id', 'x-correlation-id'],
            responseHeaders: ['x-response-time']
          }
        }
      },
      '@opentelemetry/instrumentation-express': {
        enabled: true
      },
      '@opentelemetry/instrumentation-mongodb': {
        enabled: true
      },
      '@opentelemetry/instrumentation-redis': {
        enabled: true
      }
    })
  ],
  spanProcessor: new BatchSpanProcessor(traceExporter, {
    maxQueueSize: 2048,
    maxExportBatchSize: 512,
    scheduledDelayMillis: 5000,
    exportTimeoutMillis: 30000
  })
});

// Démarrage du SDK
sdk.start();

// Arrêt propre lors de la terminaison du processus
process.on('SIGTERM', () => {
  sdk.shutdown()
    .then(() => console.log('OpenTelemetry SDK shut down successfully'))
    .catch((error) => console.error('Error shutting down OpenTelemetry SDK', error))
    .finally(() => process.exit(0));
});

Instrumentation manuelle : Traces personnalisées

Bien que l’auto-instrumentation couvre les cas courants, l’instrumentation manuelle est essentielle pour tracer la logique métier spécifique et ajouter du contexte pertinent.

import { trace, context, SpanStatusCode, SpanKind } from '@opentelemetry/api';
import { SemanticAttributes } from '@opentelemetry/semantic-conventions';

// Récupération du tracer
const tracer = trace.getTracer('ecommerce-service', '1.0.0');

class OrderService {
  async processOrder(orderData: OrderData): Promise {
    // Création d'un span parent pour toute l'opération
    return await tracer.startActiveSpan(
      'processOrder',
      {
        kind: SpanKind.INTERNAL,
        attributes: {
          'order.id': orderData.id,
          'order.total': orderData.totalAmount,
          'customer.id': orderData.customerId,
          'order.items.count': orderData.items.length
        }
      },
      async (span) => {
        try {
          // Validation
          await this.validateOrder(orderData);

          // Traitement de la commande
          const order = await this.createOrder(orderData);

          // Enrichissement du span avec les résultats
          span.setAttributes({
            'order.status': order.status,
            'order.createdat': order.createdAt.toISOString()
          });

          // Ajout d'un événement pour marquer une étape importante
          span.addEvent('ordervalidated', {
            'validation.ruleschecked': 15,
            'validation.passed': true
          });

          span.setStatus({ code: SpanStatusCode.OK });
          return order;

        } catch (error) {
          // Enregistrement de l'erreur dans le span
          span.recordException(error);
          span.setStatus({
            code: SpanStatusCode.ERROR,
            message: error.message
          });
          throw error;
        } finally {
          span.end();
        }
      }
    );
  }

  private async validateOrder(orderData: OrderData): Promise {
    return await tracer.startActiveSpan('validateOrder', async (span) => {
      try {
        // Vérification de l'inventaire
        const inventoryCheck = await tracer.startActiveSpan(
          'checkInventory',
          { kind: SpanKind.CLIENT },
          async (inventorySpan) => {
            try {
              const available = await this.inventoryService.checkAvailability(
                orderData.items
              );
              inventorySpan.setAttribute('inventory.available', available);
              return available;
            } finally {
              inventorySpan.end();
            }
          }
        );

        if (!inventoryCheck) {
          throw new Error('Insufficient inventory');
        }

        // Vérification du crédit client
        await tracer.startActiveSpan(
          'checkCustomerCredit',
          { kind: SpanKind.CLIENT },
          async (creditSpan) => {
            try {
              const creditOk = await this.creditService.checkCredit(
                orderData.customerId,
                orderData.totalAmount
              );
              creditSpan.setAttribute('credit.approved', creditOk);

              if (!creditOk) {
                throw new Error('Credit limit exceeded');
              }
            } finally {
              creditSpan.end();
            }
          }
        );

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

  private async createOrder(orderData: OrderData): Promise {
    return await tracer.startActiveSpan(
      'db.createOrder',
      {
        kind: SpanKind.CLIENT,
        attributes: {
          [SemanticAttributes.DBSYSTEM]: 'postgresql',
          [SemanticAttributes.DBNAME]: 'orders',
          [SemanticAttributes.DBOPERATION]: 'INSERT'
        }
      },
      async (span) => {
        try {
          const startTime = Date.now();
          const order = await this.orderRepository.create(orderData);
          const duration = Date.now() - startTime;

          span.setAttributes({
            'db.durationms': duration,
            'db.rowsaffected': 1
          });

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

Métriques personnalisées

Les métriques permettent d’agréger des données pour surveiller les tendances et déclencher des alertes. OpenTelemetry fournit une API de métriques qui évite les allocations mémoire sur le hot path grâce à des stratégies de pré-allocation.

import { metrics, ValueType } from '@opentelemetry/api';
import { MeterProvider, PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics';

// Récupération du meter
const meter = metrics.getMeter('ecommerce-metrics', '1.0.0');

// Définition des métriques
class OrderMetrics {
  private orderCounter;
  private orderValueHistogram;
  private activeOrdersGauge;
  private processingDuration;
  private activeOrders = 0;

  constructor() {
    // Counter : nombre total de commandes
    this.orderCounter = meter.createCounter('orders.total', {
      description: 'Total number of orders processed',
      unit: '1',
      valueType: ValueType.INT
    });

    // Histogram : distribution des montants de commandes
    this.orderValueHistogram = meter.createHistogram('orders.value', {
      description: 'Distribution of order values',
      unit: 'USD',
      valueType: ValueType.DOUBLE
    });

    // Gauge observable : nombre de commandes en cours de traitement
    this.activeOrdersGauge = meter.createObservableGauge(
      'orders.active',
      {
        description: 'Number of orders currently being processed',
        unit: '1',
        valueType: ValueType.INT
      }
    );

    // Callback pour le gauge
    this.activeOrdersGauge.addCallback((observableResult) => {
      observableResult.observe(this.activeOrders, {
        'service.name': 'order-service'
      });
    });

    // Histogram : durée de traitement des commandes
    this.processingDuration = meter.createHistogram('orders.processing.duration', {
      description: 'Order processing duration',
      unit: 'ms',
      valueType: ValueType.DOUBLE
    });
  }

  recordOrder(order: Order, duration: number): void {
    const attributes = {
      'order.status': order.status,
      'customer.tier': order.customerTier,
      'payment.method': order.paymentMethod
    };

    // Incrémenter le counter
    this.orderCounter.add(1, attributes);

    // Enregistrer la valeur dans l'histogram
    this.orderValueHistogram.record(order.totalAmount, attributes);

    // Enregistrer la durée de traitement
    this.processingDuration.record(duration, attributes);
  }

  incrementActiveOrders(): void {
    this.activeOrders++;
  }

  decrementActiveOrders(): void {
    this.activeOrders--;
  }
}

// Utilisation dans le service
class OrderServiceWithMetrics {
  private metrics = new OrderMetrics();

  async processOrder(orderData: OrderData): Promise {
    const startTime = Date.now();
    this.metrics.incrementActiveOrders();

    try {
      const order = await this.executeOrderProcessing(orderData);
      const duration = Date.now() - startTime;

      this.metrics.recordOrder(order, duration);
      return order;
    } finally {
      this.metrics.decrementActiveOrders();
    }
  }

  private async executeOrderProcessing(orderData: OrderData): Promise {
    // Logique métier...
    return null; // Placeholder
  }
}

// Métriques système avancées
class SystemMetrics {
  constructor() {
    const meter = metrics.getMeter('system-metrics', '1.0.0');

    // Utilisation mémoire
    const memoryGauge = meter.createObservableGauge('process.memory.usage', {
      description: 'Process memory usage',
      unit: 'bytes'
    });

    memoryGauge.addCallback((observableResult) => {
      const usage = process.memoryUsage();
      observableResult.observe(usage.heapUsed, { type: 'heapused' });
      observableResult.observe(usage.heapTotal, { type: 'heaptotal' });
      observableResult.observe(usage.rss, { type: 'rss' });
      observableResult.observe(usage.external, { type: 'external' });
    });

    // Event loop lag
    const eventLoopLag = meter.createObservableGauge('nodejs.eventloop.lag', {
      description: 'Event loop lag',
      unit: 'ms'
    });

    let lastCheck = Date.now();
    setInterval(() => {
      const now = Date.now();
      const lag = now - lastCheck - 100; // 100ms est l'intervalle attendu
      lastCheck = now;

      eventLoopLag.addCallback((observableResult) => {
        observableResult.observe(lag);
      });
    }, 100);
  }
}

Corrélation logs, traces et métriques

L’intégration des logs avec les traces permet de naviguer facilement du contexte global (trace) aux détails spécifiques (logs). Cette corrélation est essentielle pour le debugging efficace.

import { trace, context } from '@opentelemetry/api';
import winston from 'winston';
import { WinstonInstrumentation } from '@opentelemetry/instrumentation-winston';

// Configuration de Winston avec OpenTelemetry
const logger = winston.createLogger({
  level: 'info',
  format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.errors({ stack: true }),
    winston.format.json()
  ),
  defaultMeta: {
    service: 'order-service',
    environment: process.env.NODEENV
  },
  transports: [
    new winston.transports.Console({
      format: winston.format.combine(
        winston.format.colorize(),
        winston.format.simple()
      )
    }),
    new winston.transports.File({
      filename: 'error.log',
      level: 'error'
    }),
    new winston.transports.File({
      filename: 'combined.log'
    })
  ]
});

// Middleware pour enrichir les logs avec le contexte de trace
const enrichLogWithTrace = winston.format((info) => {
  const span = trace.getActiveSpan();
  if (span) {
    const spanContext = span.spanContext();
    info.traceid = spanContext.traceId;
    info.spanid = spanContext.spanId;
    info.traceflags = spanContext.traceFlags;
  }
  return info;
})();

logger.format = winston.format.combine(
  enrichLogWithTrace,
  logger.format
);

// Utilisation dans le code métier
class OrderServiceWithLogging {
  async processOrder(orderData: OrderData): Promise {
    return await tracer.startActiveSpan('processOrder', async (span) => {
      logger.info('Starting order processing', {
        orderid: orderData.id,
        customerid: orderData.customerId,
        totalamount: orderData.totalAmount
      });

      try {
        // Validation
        logger.debug('Validating order', { orderid: orderData.id });
        await this.validateOrder(orderData);

        // Traitement
        logger.debug('Creating order in database', { orderid: orderData.id });
        const order = await this.createOrder(orderData);

        logger.info('Order processed successfully', {
          orderid: order.id,
          status: order.status,
          processingtimems: span.duration
        });

        return order;
      } catch (error) {
        logger.error('Order processing failed', {
          orderid: orderData.id,
          error: error.message,
          stack: error.stack
        });
        throw error;
      } finally {
        span.end();
      }
    });
  }
}

// Structured logging pour les événements métier
class BusinessEventLogger {
  logOrderPlaced(order: Order): void {
    logger.info('business.event.orderplaced', {
      eventtype: 'OrderPlaced',
      aggregateid: order.id,
      aggregatetype: 'Order',
      customerid: order.customerId,
      totalamount: order.totalAmount,
      itemscount: order.items.length,
      timestamp: new Date().toISOString()
    });
  }

  logPaymentProcessed(orderId: string, paymentId: string, amount: number): void {
    logger.info('business.event.paymentprocessed', {
      eventtype: 'PaymentProcessed',
      orderid: orderId,
      paymentid: paymentId,
      amount: amount,
      timestamp: new Date().toISOString()
    });
  }
}

Déploiement du Collector OpenTelemetry

Architecture du Collector

Le Collector OpenTelemetry est une application standalone qui reçoit, traite et exporte les données de télémétrie. Utiliser le Collector en production est une best practice recommandée, car il découple l’instrumentation de l’exportation et offre des capacités de transformation, de filtrage et de routage.

receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318
        cors:
          allowedorigins:
            - "*"

  # Receiver Prometheus pour scraper les métriques
  prometheus:
    config:
      scrapeconfigs:
        - jobname: 'otel-collector'
          scrapeinterval: 30s
          staticconfigs:
            - targets: ['localhost:8888']

processors:
  # Batching pour optimiser les performances
  batch:
    timeout: 10s
    sendbatchsize: 1024
    sendbatchmaxsize: 2048

  # Filtrage des spans health check
  filter:
    spans:
      exclude:
        matchtype: regexp
        attributes:
          - key: http.target
            value: /health|/metrics

  # Enrichissement avec des attributs de ressource
  resource:
    attributes:
      - key: deployment.environment
        value: production
        action: upsert
      - key: cluster.name
        value: k8s-prod-01
        action: upsert

  # Sampling pour réduire le volume de traces
  probabilisticsampler:
    samplingpercentage: 10

  # Tail sampling : garder toutes les traces avec erreurs
  tailsampling:
    decisionwait: 10s
    numtraces: 100000
    expectednewtracespersec: 1000
    policies:
      - name: errors-policy
        type: statuscode
        statuscode:
          statuscodes:
            - ERROR
      - name: slow-requests
        type: latency
        latency:
          thresholdms: 1000
      - name: sample-10-percent
        type: probabilistic
        probabilistic:
          samplingpercentage: 10

  # Limitation de mémoire
  memorylimiter:
    checkinterval: 1s
    limitmib: 512
    spikelimitmib: 128

exporters:
  # Export vers Prometheus
  prometheus:
    endpoint: "0.0.0.0:8889"
    namespace: "otel"

  # Export vers Jaeger pour les traces
  jaeger:
    endpoint: jaeger-collector:14250
    tls:
      insecure: true

  # Export vers Loki pour les logs
  loki:
    endpoint: http://loki:3100/loki/api/v1/push
    labels:
      attributes:
        service.name: "servicename"
        deployment.environment: "env"

  # Export vers Grafana Cloud (optionnel)
  otlphttp:
    endpoint: https://otlp-gateway.grafana.net/otlp
    headers:
      Authorization: "Bearer ${GRAFANACLOUDAPIKEY}"

  # Logging pour debugging
  logging:
    loglevel: info
    samplinginitial: 5
    samplingthereafter: 200

connectors:
  # Convertir les spans en métriques
  spanmetrics:
    histogram:
      explicit:
        buckets: [10ms, 50ms, 100ms, 250ms, 500ms, 1s, 2s, 5s, 10s]
    dimensions:
      - name: http.method
      - name: http.statuscode
      - name: service.name

extensions:
  healthcheck:
    endpoint: 0.0.0.0:13133
  pprof:
    endpoint: 0.0.0.0:1777
  zpages:
    endpoint: 0.0.0.0:55679

service:
  extensions: [healthcheck, pprof, zpages]
  pipelines:
    traces:
      receivers: [otlp]
      processors: [memorylimiter, batch, filter, resource, tailsampling]
      exporters: [jaeger, spanmetrics, logging]

    metrics:
      receivers: [otlp, prometheus, spanmetrics]
      processors: [memorylimiter, batch, resource]
      exporters: [prometheus, logging]

    logs:
      receivers: [otlp]
      processors: [memorylimiter, batch, resource]
      exporters: [loki, logging]

  telemetry:
    logs:
      level: info
    metrics:
      level: detailed
      address: 0.0.0.0:8888

Déploiement Kubernetes

# Déploiement du Collector en mode Gateway
apiVersion: apps/v1
kind: Deployment
metadata:
  name: otel-collector
  namespace: observability
spec:
  replicas: 3
  selector:
    matchLabels:
      app: otel-collector
  template:
    metadata:
      labels:
        app: otel-collector
    spec:
      containers:
      - name: otel-collector
        image: otel/opentelemetry-collector-contrib:0.91.0
        args:
          - "--config=/conf/otel-collector-config.yaml"
        volumeMounts:
        - name: config
          mountPath: /conf
        ports:
        - containerPort: 4317 # OTLP gRPC
          name: otlp-grpc
        - containerPort: 4318 # OTLP HTTP
          name: otlp-http
        - containerPort: 8888 # Metrics
          name: metrics
        - containerPort: 13133 # Health check
          name: health
        livenessProbe:
          httpGet:
            path: /
            port: 13133
          initialDelaySeconds: 10
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /
            port: 13133
          initialDelaySeconds: 5
          periodSeconds: 5
        resources:
          limits:
            cpu: 1000m
            memory: 2Gi
          requests:
            cpu: 500m
            memory: 1Gi
      volumes:
      - name: config
        configMap:
          name: otel-collector-config
---
apiVersion: v1
kind: Service
metadata:
  name: otel-collector
  namespace: observability
spec:
  type: ClusterIP
  selector:
    app: otel-collector
  ports:
  - name: otlp-grpc
    port: 4317
    targetPort: 4317
  - name: otlp-http
    port: 4318
    targetPort: 4318
  - name: metrics
    port: 8888
    targetPort: 8888

Stratégies de production et optimisations

Sampling intelligent

Le sampling est crucial pour gérer le volume de traces dans les systèmes à fort trafic. Le tail sampling permet de prendre des décisions intelligentes basées sur les caractéristiques complètes de la trace.

  • Head sampling : Décision prise au début de la trace (rapide mais moins précis)
  • Tail sampling : Décision prise après avoir vu toute la trace (plus intelligent mais nécessite un buffer)
  • Sampling adaptatif : Ajustement dynamique du taux de sampling selon la charge
  • Gestion des coûts

    L’observabilité peut générer des volumes de données considérables. Stratégies pour optimiser les coûts :

  • Implémenter un sampling agressif pour les opérations fréquentes et non critiques
  • Conserver 100% des traces avec erreurs
  • Utiliser des périodes de rétention différenciées (7 jours pour les traces, 90 jours pour les métriques agrégées)
  • Filtrer les endpoints health check et metrics avant l’export
  • Agréger les métriques au niveau du Collector pour réduire la cardinalité
  • Performances et overhead

    L’instrumentation OpenTelemetry est conçue pour minimiser l’impact sur les performances. Le SDK .NET, par exemple, vise à éviter les allocations mémoire sur le hot code path grâce à la pré-allocation de mémoire lors de l’initialisation du SDK.

    Best practices pour minimiser l’overhead :

  • Utiliser le batching pour regrouper les exports
  • Configurer des buffers appropriés pour éviter les backpressure
  • Privilégier l’instrumentation automatique quand c’est suffisant
  • Éviter de créer des spans pour chaque petite opération
  • Utiliser des attributs à cardinalité limitée
  • Stack d’observabilité complète

    Intégration Grafana Stack

    OpenTelemetry et Grafana 11 forment ensemble une stack d’observabilité puissante, avec OpenTelemetry pour la collecte de données et Grafana 11 pour la visualisation. Cette combinaison est devenue le standard de facto en 2025.

    # Docker Compose pour stack complète d'observabilité
    version: '3.8'
    
    services:
      # OpenTelemetry Collector
      otel-collector:
        image: otel/opentelemetry-collector-contrib:0.91.0
        command: ["--config=/etc/otel-collector-config.yaml"]
        volumes:
          - ./otel-collector-config.yaml:/etc/otel-collector-config.yaml
        ports:
          - "4317:4317"   # OTLP gRPC
          - "4318:4318"   # OTLP HTTP
          - "8888:8888"   # Metrics
          - "13133:13133" # Health check
    
      # Jaeger pour les traces
      jaeger:
        image: jaegertracing/all-in-one:1.51
        environment:
          - COLLECTOROTLPENABLED=true
        ports:
          - "16686:16686" # UI
          - "14250:14250" # gRPC
    
      # Prometheus pour les métriques
      prometheus:
        image: prom/prometheus:v2.48.0
        command:
          - '--config.file=/etc/prometheus/prometheus.yml'
          - '--storage.tsdb.path=/prometheus'
          - '--storage.tsdb.retention.time=30d'
        volumes:
          - ./prometheus.yml:/etc/prometheus/prometheus.yml
          - prometheus-data:/prometheus
        ports:
          - "9090:9090"
    
      # Loki pour les logs
      loki:
        image: grafana/loki:2.9.3
        command: -config.file=/etc/loki/local-config.yaml
        ports:
          - "3100:3100"
        volumes:
          - loki-data:/loki
    
      # Tempo pour les traces (alternative à Jaeger)
      tempo:
        image: grafana/tempo:2.3.1
        command: [ "-config.file=/etc/tempo.yaml" ]
        volumes:
          - ./tempo.yaml:/etc/tempo.yaml
          - tempo-data:/var/tempo
        ports:
          - "3200:3200"   # Tempo
          - "4317"        # OTLP gRPC
    
      # Grafana pour la visualisation
      grafana:
        image: grafana/grafana:10.2.3
        environment:
          - GFAUTHANONYMOUSENABLED=true
          - GFAUTHANONYMOUSORGROLE=Admin
          - GFFEATURETOGGLESENABLE=traceqlEditor
        volumes:
          - grafana-data:/var/lib/grafana
          - ./grafana/provisioning:/etc/grafana/provisioning
        ports:
          - "3000:3000"
        dependson:
          - prometheus
          - loki
          - tempo
          - jaeger
    
    volumes:
      prometheus-data:
      loki-data:
      tempo-data:
      grafana-data:
    

    Observabilité pour AI Agents

    Une tendance émergente en 2025 est l’observabilité des AI Agents. Les agents IA devenant le prochain grand bond en intelligence artificielle, l’observabilité des AI agents devient un besoin critique pour comprendre leur comportement, leurs décisions et leurs performances.

    OpenTelemetry étend ses capacités pour capturer les traces spécifiques aux opérations d’IA : appels aux LLMs, embeddings, récupération de contexte RAG, et chaînes de raisonnement. Cette évolution positionne OpenTelemetry comme le standard pour l’observabilité des systèmes d’IA modernes.

    Conclusion

    L’observabilité moderne avec OpenTelemetry représente une évolution majeure dans la façon dont nous comprenons et opérons les systèmes distribués. La convergence des trois piliers – métriques, traces et logs – dans un framework unifié et standardisé simplifie considérablement l’implémentation de l’observabilité tout en évitant le vendor lock-in.

    La maturité atteinte par OpenTelemetry en 2025, avec le statut GA pour tous les signaux, marque un tournant décisif. Les organisations peuvent maintenant adopter OpenTelemetry avec confiance pour leurs systèmes de production critiques, sachant que le framework est stable, performant et supporté par l’ensemble de l’écosystème cloud native.

    L’investissement dans une stratégie d’observabilité robuste basée sur OpenTelemetry et une stack moderne comme Grafana permet aux équipes de réduire drastiquement le MTTR (Mean Time To Resolution), d’améliorer la fiabilité des systèmes et d’obtenir des insights profonds sur le comportement des applications en production.

    Une remarque, un retour ?

    Cet article est vivant — corrections, contre-arguments et retours de production sont les bienvenus. Trois canaux, choisissez celui qui vous convient.