Guide complet pour configurer OpenTelemetry avec Next.js 15 pour la supervision en production

AI Bot
Par AI Bot ·

Chargement du lecteur de synthèse vocale...

Introduction

Dans le monde des applications modernes, il ne suffit pas que votre application fonctionne — vous devez savoir comment elle fonctionne. Les réponses sont-elles lentes ? Où le serveur passe-t-il la majeure partie de son temps ? Quels sont les goulots d'étranglement ? C'est là qu'intervient OpenTelemetry (OTel) — le standard ouvert pour la supervision et le traçage distribué.

Dans ce guide complet, vous apprendrez à :

  • Configurer OpenTelemetry avec Next.js 15 depuis zéro
  • Tracer automatiquement les requêtes API et les pages
  • Ajouter des spans personnalisés pour mesurer les performances des opérations critiques
  • Exporter les données vers Jaeger pour le traçage et Prometheus pour les métriques
  • Visualiser le tout dans un tableau de bord Grafana intégré
  • Déployer la configuration en production avec Docker Compose

Prérequis

Avant de commencer, assurez-vous de disposer de :

  • Node.js 20+ installé sur votre machine
  • Docker et Docker Compose pour les services locaux
  • Connaissance de base de Next.js et TypeScript
  • Familiarité avec les concepts HTTP et REST API
  • Un éditeur de code (VS Code recommandé)

Ce que vous allez construire

Vous allez construire une application Next.js 15 avec un système de supervision complet incluant :

  1. Traçage automatique de toutes les requêtes HTTP et pages
  2. Spans personnalisés pour les opérations de base de données et services externes
  3. Métriques de performance comme le temps de réponse et le taux d'erreurs
  4. Tableau de bord de supervision intégré dans Grafana
  5. Alertes lorsque les seuils de performance sont dépassés

Étape 1 : Créer un projet Next.js 15

Commencez par créer un nouveau projet Next.js :

npx create-next-app@latest otel-nextjs-demo --typescript --tailwind --app --src-dir
cd otel-nextjs-demo

Choisissez les options par défaut lorsque demandé. Après l'installation, vérifiez que le projet fonctionne :

npm run dev

Étape 2 : Installer les packages OpenTelemetry

Installez les packages OpenTelemetry nécessaires :

npm install @opentelemetry/api \
  @opentelemetry/sdk-node \
  @opentelemetry/sdk-trace-node \
  @opentelemetry/sdk-metrics \
  @opentelemetry/resources \
  @opentelemetry/semantic-conventions \
  @opentelemetry/exporter-trace-otlp-http \
  @opentelemetry/exporter-metrics-otlp-http \
  @opentelemetry/instrumentation-http \
  @opentelemetry/instrumentation-fetch \
  @vercel/otel

Le package @vercel/otel offre une intégration optimisée avec Next.js et simplifie considérablement la configuration initiale. Vous pouvez l'utiliser avec n'importe quel fournisseur OpenTelemetry, pas seulement Vercel.

Étape 3 : Configurer OpenTelemetry dans Next.js

Activer la fonctionnalité expérimentale

D'abord, activez le support OpenTelemetry dans next.config.ts :

// next.config.ts
import type { NextConfig } from "next";
 
const nextConfig: NextConfig = {
  experimental: {
    instrumentationHook: true,
  },
};
 
export default nextConfig;

Créer le fichier d'instrumentation

Créez src/instrumentation.ts — c'est le point d'entrée pour OpenTelemetry :

// src/instrumentation.ts
export async function register() {
  if (process.env.NEXT_RUNTIME === "nodejs") {
    const { NodeSDK } = await import("@opentelemetry/sdk-node");
    const { OTLPTraceExporter } = await import(
      "@opentelemetry/exporter-trace-otlp-http"
    );
    const { OTLPMetricExporter } = await import(
      "@opentelemetry/exporter-metrics-otlp-http"
    );
    const { PeriodicExportingMetricReader } = await import(
      "@opentelemetry/sdk-metrics"
    );
    const { Resource } = await import("@opentelemetry/resources");
    const {
      ATTR_SERVICE_NAME,
      ATTR_SERVICE_VERSION,
      ATTR_DEPLOYMENT_ENVIRONMENT_NAME,
    } = await import("@opentelemetry/semantic-conventions");
    const { HttpInstrumentation } = await import(
      "@opentelemetry/instrumentation-http"
    );
 
    const resource = new Resource({
      [ATTR_SERVICE_NAME]: "nextjs-otel-demo",
      [ATTR_SERVICE_VERSION]: "1.0.0",
      [ATTR_DEPLOYMENT_ENVIRONMENT_NAME]:
        process.env.NODE_ENV || "development",
    });
 
    const traceExporter = new OTLPTraceExporter({
      url:
        process.env.OTEL_EXPORTER_OTLP_ENDPOINT ||
        "http://localhost:4318/v1/traces",
    });
 
    const metricExporter = new OTLPMetricExporter({
      url:
        process.env.OTEL_EXPORTER_OTLP_ENDPOINT ||
        "http://localhost:4318/v1/metrics",
    });
 
    const sdk = new NodeSDK({
      resource,
      traceExporter,
      metricReader: new PeriodicExportingMetricReader({
        exporter: metricExporter,
        exportIntervalMillis: 10000,
      }),
      instrumentations: [new HttpInstrumentation()],
    });
 
    sdk.start();
 
    process.on("SIGTERM", () => {
      sdk.shutdown().then(
        () => console.log("OpenTelemetry SDK shut down successfully"),
        (err) => console.error("Error shutting down OpenTelemetry SDK", err)
      );
    });
  }
}

La vérification process.env.NEXT_RUNTIME === "nodejs" est essentielle car le SDK OpenTelemetry fonctionne uniquement dans le runtime Node.js et ne supporte pas Edge Runtime. Sans cette vérification, votre application échouera lors de l'utilisation de Middleware ou de Edge Functions.

Étape 4 : Configurer les services de supervision avec Docker Compose

Créez un fichier docker-compose.yml à la racine du projet pour exécuter Jaeger, Prometheus, Grafana et le collecteur OpenTelemetry :

# docker-compose.yml
services:
  otel-collector:
    image: otel/opentelemetry-collector-contrib:latest
    command: ["--config=/etc/otel-collector-config.yml"]
    volumes:
      - ./otel-collector-config.yml:/etc/otel-collector-config.yml
    ports:
      - "4317:4317"   # gRPC
      - "4318:4318"   # HTTP
      - "8889:8889"   # Métriques Prometheus
    depends_on:
      - jaeger
 
  jaeger:
    image: jaegertracing/all-in-one:latest
    environment:
      - COLLECTOR_OTLP_ENABLED=true
    ports:
      - "16686:16686" # Interface Jaeger
      - "14268:14268" # Collecteur Jaeger
 
  prometheus:
    image: prom/prometheus:latest
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
    ports:
      - "9090:9090"
 
  grafana:
    image: grafana/grafana:latest
    environment:
      - GF_SECURITY_ADMIN_USER=admin
      - GF_SECURITY_ADMIN_PASSWORD=admin123
    volumes:
      - grafana-data:/var/lib/grafana
    ports:
      - "3001:3000"
    depends_on:
      - prometheus
      - jaeger
 
volumes:
  grafana-data:

Configurer le collecteur OpenTelemetry

Créez le fichier otel-collector-config.yml :

# otel-collector-config.yml
receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318
 
processors:
  batch:
    timeout: 5s
    send_batch_size: 1024
  memory_limiter:
    check_interval: 1s
    limit_mib: 512
 
exporters:
  otlp/jaeger:
    endpoint: jaeger:4317
    tls:
      insecure: true
  prometheus:
    endpoint: "0.0.0.0:8889"
    namespace: "nextjs"
 
service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [memory_limiter, batch]
      exporters: [otlp/jaeger]
    metrics:
      receivers: [otlp]
      processors: [memory_limiter, batch]
      exporters: [prometheus]

Configurer Prometheus

Créez le fichier prometheus.yml :

# prometheus.yml
global:
  scrape_interval: 15s
  evaluation_interval: 15s
 
scrape_configs:
  - job_name: "otel-collector"
    static_configs:
      - targets: ["otel-collector:8889"]
  - job_name: "nextjs-app"
    static_configs:
      - targets: ["host.docker.internal:3000"]

Démarrez tous les services :

docker compose up -d

Étape 5 : Ajouter des Spans personnalisés

Créer un utilitaire de traçage

Créez src/lib/tracing.ts pour fournir des utilitaires de traçage faciles à utiliser :

// src/lib/tracing.ts
import { trace, SpanStatusCode, Span, context } from "@opentelemetry/api";
 
const tracer = trace.getTracer("nextjs-app", "1.0.0");
 
export function withSpan<T>(
  name: string,
  fn: (span: Span) => Promise<T>,
  attributes?: Record<string, string | number | boolean>
): Promise<T> {
  return tracer.startActiveSpan(name, async (span) => {
    try {
      if (attributes) {
        Object.entries(attributes).forEach(([key, value]) => {
          span.setAttribute(key, value);
        });
      }
      const result = await fn(span);
      span.setStatus({ code: SpanStatusCode.OK });
      return result;
    } catch (error) {
      span.setStatus({
        code: SpanStatusCode.ERROR,
        message: error instanceof Error ? error.message : "Unknown error",
      });
      span.recordException(error as Error);
      throw error;
    } finally {
      span.end();
    }
  });
}
 
export function createDbSpan<T>(
  operation: string,
  table: string,
  fn: (span: Span) => Promise<T>
): Promise<T> {
  return withSpan(`db.${operation}`, fn, {
    "db.system": "postgresql",
    "db.operation": operation,
    "db.sql.table": table,
  });
}
 
export function createExternalApiSpan<T>(
  service: string,
  endpoint: string,
  fn: (span: Span) => Promise<T>
): Promise<T> {
  return withSpan(`external.${service}`, fn, {
    "http.url": endpoint,
    "peer.service": service,
  });
}
 
export { tracer };

Utiliser les Spans dans les routes API

Créez une route API qui utilise le traçage personnalisé :

// src/app/api/users/route.ts
import { NextResponse } from "next/server";
import { withSpan, createDbSpan } from "@/lib/tracing";
 
// Base de données simulée
async function fetchUsersFromDb() {
  return createDbSpan("SELECT", "users", async (span) => {
    // Simuler la latence de la base de données
    await new Promise((resolve) => setTimeout(resolve, 50));
 
    span.setAttribute("db.rows_affected", 10);
 
    return [
      { id: 1, name: "Ahmed", email: "ahmed@example.com" },
      { id: 2, name: "Sara", email: "sara@example.com" },
      { id: 3, name: "Mohamed", email: "mohamed@example.com" },
    ];
  });
}
 
async function enrichUserData(users: any[]) {
  return withSpan(
    "enrichUserData",
    async (span) => {
      span.setAttribute("users.count", users.length);
 
      // Simuler l'enrichissement depuis un service externe
      await new Promise((resolve) => setTimeout(resolve, 30));
 
      return users.map((user) => ({
        ...user,
        avatar: `https://api.dicebear.com/7.x/initials/svg?seed=${user.name}`,
      }));
    },
    { "enrichment.source": "dicebear" }
  );
}
 
export async function GET() {
  return withSpan("GET /api/users", async (span) => {
    span.setAttribute("http.method", "GET");
    span.setAttribute("http.route", "/api/users");
 
    const users = await fetchUsersFromDb();
    const enrichedUsers = await enrichUserData(users);
 
    span.setAttribute("response.count", enrichedUsers.length);
 
    return NextResponse.json({
      users: enrichedUsers,
      total: enrichedUsers.length,
    });
  });
}

Étape 6 : Ajouter des métriques personnalisées

Créez src/lib/metrics.ts pour définir les métriques :

// src/lib/metrics.ts
import { metrics } from "@opentelemetry/api";
 
const meter = metrics.getMeter("nextjs-app", "1.0.0");
 
// Compteur de requêtes
export const requestCounter = meter.createCounter("http_requests_total", {
  description: "Nombre total de requêtes HTTP",
  unit: "requests",
});
 
// Histogramme du temps de réponse
export const responseTimeHistogram = meter.createHistogram(
  "http_response_duration_ms",
  {
    description: "Durée de réponse HTTP en millisecondes",
    unit: "ms",
  }
);
 
// Compteur d'erreurs
export const errorCounter = meter.createCounter("http_errors_total", {
  description: "Nombre total d'erreurs HTTP",
  unit: "errors",
});
 
// Jauge des connexions actives
export const activeConnections = meter.createUpDownCounter(
  "active_connections",
  {
    description: "Nombre de connexions actuellement actives",
    unit: "connections",
  }
);

Intégrer les métriques dans le Middleware

Créez un middleware pour enregistrer automatiquement les métriques :

// src/middleware.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
 
export function middleware(request: NextRequest) {
  const start = Date.now();
  const response = NextResponse.next();
 
  // Ajouter des en-têtes de traçage
  response.headers.set("X-Request-Start", start.toString());
  response.headers.set(
    "X-Request-Id",
    crypto.randomUUID()
  );
 
  return response;
}
 
export const config = {
  matcher: ["/api/:path*", "/((?!_next/static|_next/image|favicon.ico).*)"],
};

Créer une route API avec métriques complètes

// src/app/api/health/route.ts
import { NextResponse } from "next/server";
import {
  requestCounter,
  responseTimeHistogram,
  errorCounter,
} from "@/lib/metrics";
import { withSpan } from "@/lib/tracing";
 
export async function GET() {
  const start = Date.now();
 
  return withSpan("GET /api/health", async (span) => {
    try {
      // Enregistrer la requête
      requestCounter.add(1, {
        method: "GET",
        route: "/api/health",
      });
 
      // Simuler les vérifications de santé
      const checks = {
        database: "healthy",
        cache: "healthy",
        external_api: "healthy",
        uptime: process.uptime(),
        timestamp: new Date().toISOString(),
      };
 
      // Enregistrer le temps de réponse
      const duration = Date.now() - start;
      responseTimeHistogram.record(duration, {
        method: "GET",
        route: "/api/health",
        status: "200",
      });
 
      span.setAttribute("health.status", "healthy");
      span.setAttribute("health.duration_ms", duration);
 
      return NextResponse.json(checks);
    } catch (error) {
      errorCounter.add(1, {
        method: "GET",
        route: "/api/health",
        error_type: "health_check_failed",
      });
      throw error;
    }
  });
}

Étape 7 : Créer une page de tableau de bord

Créez une page simple pour afficher l'état de la supervision :

// src/app/dashboard/page.tsx
"use client";
 
import { useEffect, useState } from "react";
 
interface HealthStatus {
  database: string;
  cache: string;
  external_api: string;
  uptime: number;
  timestamp: string;
}
 
export default function DashboardPage() {
  const [health, setHealth] = useState<HealthStatus | null>(null);
  const [loading, setLoading] = useState(true);
 
  useEffect(() => {
    const fetchHealth = async () => {
      try {
        const res = await fetch("/api/health");
        const data = await res.json();
        setHealth(data);
      } catch (err) {
        console.error("Failed to fetch health:", err);
      } finally {
        setLoading(false);
      }
    };
 
    fetchHealth();
    const interval = setInterval(fetchHealth, 5000);
    return () => clearInterval(interval);
  }, []);
 
  if (loading) {
    return (
      <div className="flex items-center justify-center min-h-screen">
        <div className="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-blue-500" />
      </div>
    );
  }
 
  return (
    <div className="max-w-4xl mx-auto p-8">
      <h1 className="text-3xl font-bold mb-8">
        Tableau de bord de supervision
      </h1>
 
      <div className="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
        {health && (
          <>
            <StatusCard
              title="Base de données"
              status={health.database}
            />
            <StatusCard
              title="Cache"
              status={health.cache}
            />
            <StatusCard
              title="API externe"
              status={health.external_api}
            />
          </>
        )}
      </div>
 
      <div className="bg-gray-900 rounded-lg p-6">
        <h2 className="text-xl font-semibold mb-4">
          Liens rapides
        </h2>
        <div className="space-y-3">
          <ExternalLink
            href="http://localhost:16686"
            label="Jaeger UI — Voir les traces"
          />
          <ExternalLink
            href="http://localhost:9090"
            label="Prometheus — Requêter les métriques"
          />
          <ExternalLink
            href="http://localhost:3001"
            label="Grafana — Tableaux de bord"
          />
        </div>
      </div>
    </div>
  );
}
 
function StatusCard({
  title,
  status,
}: {
  title: string;
  status: string;
}) {
  const isHealthy = status === "healthy";
  return (
    <div className="bg-gray-800 rounded-lg p-6">
      <h3 className="text-sm font-medium text-gray-400 mb-2">
        {title}
      </h3>
      <div className="flex items-center gap-2">
        <div
          className={`w-3 h-3 rounded-full ${
            isHealthy ? "bg-green-500" : "bg-red-500"
          }`}
        />
        <span
          className={
            isHealthy ? "text-green-400" : "text-red-400"
          }
        >
          {isHealthy ? "Sain" : "Défaillant"}
        </span>
      </div>
    </div>
  );
}
 
function ExternalLink({
  href,
  label,
}: {
  href: string;
  label: string;
}) {
  return (
    <a
      href={href}
      target="_blank"
      rel="noopener noreferrer"
      className="flex items-center gap-2 text-blue-400 hover:text-blue-300 transition-colors"
    >
      <span>&rarr;</span>
      {label}
    </a>
  );
}

Étape 8 : Configurer le tableau de bord Grafana

Après avoir démarré les services avec docker compose up -d, ouvrez Grafana à http://localhost:3001 et connectez-vous avec admin / admin123.

Ajouter les sources de données

  1. Allez dans Configuration puis Data Sources
  2. Ajoutez Prometheus avec l'URL : http://prometheus:9090
  3. Ajoutez Jaeger avec l'URL : http://jaeger:16686

Créer un tableau de bord personnalisé

Créez un fichier grafana-dashboard.json pour l'importer dans Grafana :

{
  "dashboard": {
    "title": "Next.js Application Monitoring",
    "panels": [
      {
        "title": "Request Rate",
        "type": "timeseries",
        "targets": [
          {
            "expr": "rate(nextjs_http_requests_total[5m])",
            "legendFormat": "Requests/sec"
          }
        ]
      },
      {
        "title": "Response Time (p95)",
        "type": "gauge",
        "targets": [
          {
            "expr": "histogram_quantile(0.95, rate(nextjs_http_response_duration_ms_bucket[5m]))",
            "legendFormat": "p95 Latency"
          }
        ]
      },
      {
        "title": "Error Rate",
        "type": "stat",
        "targets": [
          {
            "expr": "rate(nextjs_http_errors_total[5m])",
            "legendFormat": "Errors/sec"
          }
        ]
      }
    ]
  }
}

Vous pouvez importer ce fichier via l'interface Grafana en allant dans Dashboards, puis Import, puis en collant le contenu JSON. Vous obtiendrez un tableau de bord prêt à l'emploi avec trois panneaux.

Étape 9 : Traçage avancé — Relier le Frontend au Backend

Pour corréler les traces du navigateur avec celles du serveur, créez un utilitaire côté client :

// src/lib/client-tracing.ts
export function createTracedFetch(
  originalFetch: typeof fetch
): typeof fetch {
  return async (input, init) => {
    const requestId = crypto.randomUUID();
    const start = performance.now();
 
    const headers = new Headers(init?.headers);
    headers.set("X-Request-Id", requestId);
    headers.set("X-Client-Start", start.toString());
 
    try {
      const response = await originalFetch(input, {
        ...init,
        headers,
      });
 
      const duration = performance.now() - start;
      console.debug(
        `[Trace] ${
          typeof input === "string" ? input : input.url
        } - ${duration.toFixed(1)}ms`
      );
 
      return response;
    } catch (error) {
      const duration = performance.now() - start;
      console.error(
        `[Trace Error] ${
          typeof input === "string" ? input : input.url
        } - ${duration.toFixed(1)}ms`,
        error
      );
      throw error;
    }
  };
}

Utiliser le Fetch tracé dans les composants

// src/hooks/useTracedFetch.ts
"use client";
 
import { useCallback } from "react";
import { createTracedFetch } from "@/lib/client-tracing";
 
const tracedFetch = createTracedFetch(fetch);
 
export function useTracedFetch() {
  const fetchWithTrace = useCallback(
    async <T>(url: string, options?: RequestInit): Promise<T> => {
      const response = await tracedFetch(url, options);
      if (!response.ok) {
        throw new Error(`HTTP ${response.status}: ${response.statusText}`);
      }
      return response.json();
    },
    []
  );
 
  return { fetch: fetchWithTrace };
}

Étape 10 : Configurer les alertes

Règles d'alerte Prometheus

Créez le fichier alert-rules.yml :

# alert-rules.yml
groups:
  - name: nextjs-alerts
    rules:
      - alert: HighErrorRate
        expr: rate(nextjs_http_errors_total[5m]) > 0.1
        for: 2m
        labels:
          severity: critical
        annotations:
          summary: "Taux d'erreurs élevé dans l'application Next.js"
          description: "Le taux d'erreurs a dépassé 10% au cours des 5 dernières minutes"
 
      - alert: SlowResponseTime
        expr: histogram_quantile(0.95, rate(nextjs_http_response_duration_ms_bucket[5m])) > 2000
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "Temps de réponse lent détecté"
          description: "Le temps de réponse p95 a dépassé 2000ms"
 
      - alert: HighMemoryUsage
        expr: process_resident_memory_bytes > 512 * 1024 * 1024
        for: 10m
        labels:
          severity: warning
        annotations:
          summary: "Utilisation mémoire élevée"
          description: "L'utilisation mémoire a dépassé 512 Mo"

Mettez à jour prometheus.yml pour inclure les règles d'alerte :

# prometheus.yml (mis à jour)
global:
  scrape_interval: 15s
  evaluation_interval: 15s
 
rule_files:
  - "alert-rules.yml"
 
scrape_configs:
  - job_name: "otel-collector"
    static_configs:
      - targets: ["otel-collector:8889"]

Étape 11 : Configurer les variables d'environnement

Créez .env.local pour le développement local :

# .env.local
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
OTEL_SERVICE_NAME=nextjs-otel-demo
OTEL_LOG_LEVEL=info
NODE_ENV=development

Et .env.production pour la production :

# .env.production
OTEL_EXPORTER_OTLP_ENDPOINT=https://otel-collector.yourdomain.com
OTEL_SERVICE_NAME=nextjs-otel-production
OTEL_LOG_LEVEL=warn
NODE_ENV=production

Étape 12 : Tester le système complet

Démarrer les services

# Démarrer les services de supervision
docker compose up -d
 
# Démarrer l'application Next.js
npm run dev

Générer des données de traçage

Utilisez curl ou le navigateur pour envoyer des requêtes :

# Requête unique
curl http://localhost:3000/api/users
 
# Vérification de santé
curl http://localhost:3000/api/health
 
# Générer une charge légère (10 requêtes)
for i in $(seq 1 10); do
  curl -s http://localhost:3000/api/users > /dev/null
  echo "Request $i sent"
done

Vérifier les résultats

  1. Jaeger — Ouvrez http://localhost:16686

    • Sélectionnez le service nextjs-otel-demo
    • Vous verrez les traces pour chaque requête avec le timing détaillé
    • Cliquez sur une trace pour voir l'arbre des spans
  2. Prometheus — Ouvrez http://localhost:9090

    • Essayez la requête : nextjs_http_requests_total
    • Essayez : histogram_quantile(0.95, rate(nextjs_http_response_duration_ms_bucket[5m]))
  3. Grafana — Ouvrez http://localhost:3001

    • Importez le tableau de bord depuis le fichier JSON
    • Observez les métriques en temps réel

Dépannage

Pas de traces dans Jaeger

  • Vérifiez que le collecteur OpenTelemetry tourne : docker compose logs otel-collector
  • Vérifiez que OTEL_EXPORTER_OTLP_ENDPOINT pointe vers la bonne adresse
  • Assurez-vous que instrumentationHook: true est activé dans next.config.ts

Métriques absentes dans Prometheus

  • Vérifiez que le collecteur exporte vers Prometheus : curl http://localhost:8889/metrics
  • Vérifiez que scrape_configs est correctement configuré dans prometheus.yml

Erreurs avec Edge Runtime

  • Le SDK OpenTelemetry ne supporte pas Edge Runtime
  • Utilisez la condition NEXT_RUNTIME === "nodejs" dans votre fichier instrumentation
  • N'importez pas les packages OTel dans le Middleware ou les Edge API Routes

Consommation mémoire élevée

  • Réduisez send_batch_size dans les paramètres du collecteur
  • Augmentez exportIntervalMillis pour réduire la fréquence d'export
  • Utilisez le sampling pour réduire le volume de données en production

Bonnes pratiques pour la production

1. Utiliser le sampling

En production, vous n'avez pas besoin de tracer chaque requête. Utilisez le tail-based sampling :

// Dans instrumentation.ts
import { TraceIdRatioBasedSampler } from "@opentelemetry/sdk-trace-node";
 
const sdk = new NodeSDK({
  // Ne tracer que 10% des requêtes
  sampler: new TraceIdRatioBasedSampler(0.1),
  // ... reste de la configuration
});

2. Éviter le sur-traçage

  • N'ajoutez pas de spans pour des opérations très rapides (moins de 1ms)
  • Évitez d'enregistrer des données sensibles dans les attributs
  • Utilisez des niveaux de log appropriés pour chaque environnement

3. Sécuriser les points de terminaison

# En production, utilisez TLS et l'authentification
exporters:
  otlp/secure:
    endpoint: https://otel.yourdomain.com:4317
    tls:
      cert_file: /etc/ssl/certs/client.crt
      key_file: /etc/ssl/private/client.key
    headers:
      Authorization: "Bearer YOUR_TOKEN"

4. Superviser le collecteur OpenTelemetry lui-même

# Ajouter la surveillance de santé du collecteur
extensions:
  health_check:
    endpoint: 0.0.0.0:13133
  zpages:
    endpoint: 0.0.0.0:55679
 
service:
  extensions: [health_check, zpages]

Prochaines étapes

Après avoir terminé ce guide, vous pouvez approfondir :

  • Traçage distribué — Corréler les traces entre plusieurs microservices
  • Métriques personnalisées — Ajouter des métriques métier comme le nombre d'utilisateurs actifs
  • Corrélation des logs — Lier les logs aux traces via trace_id
  • Real User Monitoring — Suivre les performances du navigateur avec OpenTelemetry Browser SDK
  • Intégration CI/CD — Ajouter le traçage à votre pipeline de déploiement

Conclusion

Dans ce guide, vous avez appris à :

  1. Configurer OpenTelemetry avec Next.js 15 en utilisant le hook d'instrumentation
  2. Tracer les requêtes automatiquement et ajouter des spans personnalisés pour des opérations spécifiques
  3. Collecter des métriques comme le taux de requêtes, le temps de réponse et le taux d'erreurs
  4. Déployer un système de supervision complet avec Jaeger, Prometheus et Grafana
  5. Configurer les alertes pour détecter les problèmes avant que les utilisateurs ne les remarquent

Une bonne supervision n'est pas un luxe — c'est une nécessité pour toute application en production. Avec OpenTelemetry, vous disposez maintenant d'une visibilité complète sur ce qui se passe à l'intérieur de votre application.


Vous voulez lire plus de tutoriels? Découvrez notre dernier tutoriel sur Construire des API REST avec Rust et Axum : guide pratique pour débutants.

Discutez de votre projet avec nous

Nous sommes ici pour vous aider avec vos besoins en développement Web. Planifiez un appel pour discuter de votre projet et comment nous pouvons vous aider.

Trouvons les meilleures solutions pour vos besoins.

Articles connexes

Docker Compose pour développeurs Full-Stack : Next.js, PostgreSQL et Redis

Apprenez à conteneuriser une application Next.js full-stack avec PostgreSQL et Redis en utilisant Docker Compose. Ce tutoriel pratique couvre l'orchestration multi-services, les workflows de développement, le rechargement à chaud, les health checks et les configurations prêtes pour la production.

28 min read·