OpenTelemetry

Bon à savoir : Cette fonctionnalité est expérimentale, vous devez l'activer explicitement en ajoutant experimental.instrumentationHook = true; dans votre next.config.js.

L'observabilité est cruciale pour comprendre et optimiser le comportement et les performances de votre application Next.js.

À mesure que les applications deviennent plus complexes, il devient de plus en plus difficile d'identifier et de diagnostiquer les problèmes qui peuvent survenir. En utilisant des outils d'observabilité, tels que la journalisation et les métriques, les développeurs peuvent obtenir des insights sur le comportement de leur application et identifier des zones d'optimisation. Avec l'observabilité, les développeurs peuvent résoudre les problèmes de manière proactive avant qu'ils ne deviennent majeurs et offrir une meilleure expérience utilisateur. Par conséquent, il est fortement recommandé d'utiliser l'observabilité dans vos applications Next.js pour améliorer les performances, optimiser les ressources et enrichir l'expérience utilisateur.

Nous recommandons d'utiliser OpenTelemetry pour instrumenter vos applications. C'est une manière agnostique de la plateforme d'instrumenter les applications qui vous permet de changer votre fournisseur d'observabilité sans modifier votre code. Consultez la documentation officielle d'OpenTelemetry pour plus d'informations sur OpenTelemetry et son fonctionnement.

Cette documentation utilise des termes comme Span, Trace ou Exporter tout au long de ce document, tous peuvent être trouvés dans le guide d'observabilité OpenTelemetry.

Next.js prend en charge l'instrumentation OpenTelemetry nativement, ce qui signifie que nous avons déjà instrumenté Next.js lui-même. Lorsque vous activez OpenTelemetry, nous encapsulons automatiquement tout votre code comme getStaticProps dans des spans avec des attributs utiles.

Bon à savoir : Nous ne prenons actuellement en charge les bindings OpenTelemetry que dans les fonctions serverless. Nous n'en fournissons aucun pour le code edge ou côté client.

Premiers pas

OpenTelemetry est extensible, mais sa configuration peut être assez verbeuse. C'est pourquoi nous avons préparé un package @vercel/otel qui vous aide à démarrer rapidement. Il n'est pas extensible et vous devriez configurer OpenTelemetry manuellement si vous avez besoin de personnaliser votre configuration.

Utilisation de @vercel/otel

Pour commencer, vous devez installer @vercel/otel :

Terminal
npm install @vercel/otel

Ensuite, créez un fichier personnalisé instrumentation.ts (ou .js) dans le répertoire racine du projet (ou dans le dossier src si vous en utilisez un) :

import { registerOTel } from '@vercel/otel'

export function register() {
  registerOTel('next-app')
}
import { registerOTel } from '@vercel/otel'

export function register() {
  registerOTel('next-app')
}

Bon à savoir

  • Le fichier instrumentation doit être à la racine de votre projet et non dans les répertoires app ou pages. Si vous utilisez le dossier src, placez le fichier dans src à côté de pages et app.
  • Si vous utilisez l'option de configuration pageExtensions pour ajouter un suffixe, vous devrez également mettre à jour le nom du fichier instrumentation pour correspondre.
  • Nous avons créé un exemple basique with-opentelemetry que vous pouvez utiliser.

Configuration manuelle d'OpenTelemetry

Si notre wrapper @vercel/otel ne répond pas à vos besoins, vous pouvez configurer OpenTelemetry manuellement.

Premièrement, vous devez installer les packages OpenTelemetry :

Terminal
npm install @opentelemetry/sdk-node @opentelemetry/resources @opentelemetry/semantic-conventions @opentelemetry/sdk-trace-node @opentelemetry/exporter-trace-otlp-http

Maintenant, vous pouvez initialiser NodeSDK dans votre instrumentation.ts. Les API OpenTelemetry ne sont pas compatibles avec le runtime edge, vous devez donc vous assurer de les importer uniquement lorsque process.env.NEXT_RUNTIME === 'nodejs'. Nous recommandons de créer un nouveau fichier instrumentation.node.ts que vous importez conditionnellement uniquement lors de l'utilisation de node :

export async function register() {
  if (process.env.NEXT_RUNTIME === 'nodejs') {
    await import('./instrumentation.node.ts')
  }
}
export async function register() {
  if (process.env.NEXT_RUNTIME === 'nodejs') {
    await import('./instrumentation.node.js')
  }
}
import { NodeSDK } from '@opentelemetry/sdk-node'
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'
import { Resource } from '@opentelemetry/resources'
import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions'
import { SimpleSpanProcessor } from '@opentelemetry/sdk-trace-node'

const sdk = new NodeSDK({
  resource: new Resource({
    [SemanticResourceAttributes.SERVICE_NAME]: 'next-app',
  }),
  spanProcessor: new SimpleSpanProcessor(new OTLPTraceExporter()),
})
sdk.start()
import { NodeSDK } from '@opentelemetry/sdk-node'
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'
import { Resource } from '@opentelemetry/resources'
import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions'
import { SimpleSpanProcessor } from '@opentelemetry/sdk-trace-node'

const sdk = new NodeSDK({
  resource: new Resource({
    [SemanticResourceAttributes.SERVICE_NAME]: 'next-app',
  }),
  spanProcessor: new SimpleSpanProcessor(new OTLPTraceExporter()),
})
sdk.start()

Faire cela est équivalent à utiliser @vercel/otel, mais il est possible de le modifier et de l'étendre. Par exemple, vous pourriez utiliser @opentelemetry/exporter-trace-otlp-grpc au lieu de @opentelemetry/exporter-trace-otlp-http ou vous pouvez spécifier plus d'attributs de ressource.

Tester votre instrumentation

Vous avez besoin d'un collecteur OpenTelemetry avec un backend compatible pour tester les traces OpenTelemetry localement. Nous recommandons d'utiliser notre environnement de développement OpenTelemetry.

Si tout fonctionne correctement, vous devriez pouvoir voir le span racine du serveur étiqueté comme GET /requested/pathname. Tous les autres spans de cette trace particulière seront imbriqués en dessous.

Next.js trace plus de spans que ceux émis par défaut. Pour voir plus de spans, vous devez définir NEXT_OTEL_VERBOSE=1.

Déploiement

Utilisation d'OpenTelemetry Collector

Lorsque vous déployez avec OpenTelemetry Collector, vous pouvez utiliser @vercel/otel. Il fonctionnera à la fois sur Vercel et en auto-hébergement.

Déploiement sur Vercel

Nous nous sommes assurés qu'OpenTelemetry fonctionne nativement sur Vercel.

Suivez la documentation Vercel pour connecter votre projet à un fournisseur d'observabilité.

Auto-hébergement

Le déploiement sur d'autres plateformes est également simple. Vous devrez déployer votre propre OpenTelemetry Collector pour recevoir et traiter les données de télémétrie de votre application Next.js.

Pour ce faire, suivez le guide de démarrage d'OpenTelemetry Collector, qui vous guidera dans la configuration du collecteur et son paramétrage pour recevoir les données de votre application Next.js.

Une fois que votre collecteur est opérationnel, vous pouvez déployer votre application Next.js sur la plateforme de votre choix en suivant leurs guides de déploiement respectifs.

Exporteurs personnalisés

Nous recommandons d'utiliser OpenTelemetry Collector. Si ce n'est pas possible sur votre plateforme, vous pouvez utiliser un exporteur OpenTelemetry personnalisé avec une configuration manuelle d'OpenTelemetry

Spans personnalisés

Vous pouvez ajouter un span personnalisé avec les API OpenTelemetry.

Terminal
npm install @opentelemetry/api

L'exemple suivant montre une fonction qui récupère les stars GitHub et ajoute un span personnalisé fetchGithubStars pour suivre le résultat de la requête fetch :

import { trace } from '@opentelemetry/api'

export async function fetchGithubStars() {
  return await trace
    .getTracer('nextjs-example')
    .startActiveSpan('fetchGithubStars', async (span) => {
      try {
        return await getValue()
      } finally {
        span.end()
      }
    })
}

La fonction register s'exécutera avant votre code dans un nouvel environnement. Vous pouvez commencer à créer de nouveaux spans, et ils devraient être correctement ajoutés à la trace exportée.

Spans par défaut dans Next.js

Next.js instrumente automatiquement plusieurs spans pour vous fournir des insights utiles sur les performances de votre application.

Les attributs des spans suivent les conventions sémantiques OpenTelemetry. Nous ajoutons également certains attributs personnalisés sous l'espace de noms next :

  • next.span_name - duplique le nom du span
  • next.span_type - chaque type de span a un identifiant unique
  • next.route - Le motif de route de la requête (par exemple, /[param]/user).
  • next.page
    • Il s'agit d'une valeur interne utilisée par le routeur app.
    • Vous pouvez le considérer comme une route vers un fichier spécial (comme page.ts, layout.ts, loading.ts et autres)
    • Il peut être utilisé comme identifiant unique uniquement lorsqu'il est associé à next.route car /layout peut être utilisé pour identifier à la fois /(groupA)/layout.ts et /(groupB)/layout.ts

[http.method] [next.route]

  • next.span_type: BaseServer.handleRequest

Ce span représente le span racine pour chaque requête entrante dans votre application Next.js. Il suit la méthode HTTP, la route, la cible et le code de statut de la requête.

Attributs :

render route (app) [next.route]

  • next.span_type: AppRender.getBodyResult.

Ce span représente le processus de rendu d'une route dans le routeur app.

Attributs :

  • next.span_name
  • next.span_type
  • next.route

fetch [http.method] [http.url]

  • next.span_type: AppRender.fetch

Ce span représente la requête fetch exécutée dans votre code.

Attributs :

executing api route (app) [next.route]

  • next.span_type: AppRouteRouteHandlers.runHandler.

Ce span représente l'exécution d'un gestionnaire de route API dans le routeur app.

Attributs :

  • next.span_name
  • next.span_type
  • next.route

getServerSideProps [next.route]

  • next.span_type: Render.getServerSideProps.

Ce span représente l'exécution de getServerSideProps pour une route spécifique.

Attributs :

  • next.span_name
  • next.span_type
  • next.route

getStaticProps [next.route]

  • next.span_type: Render.getStaticProps.

Ce span représente l'exécution de getStaticProps pour une route spécifique.

Attributs :

  • next.span_name
  • next.span_type
  • next.route

render route (pages) [next.route]

  • next.span_type: Render.renderDocument.

Ce span représente le processus de rendu du document pour une route spécifique.

Attributs :

  • next.span_name
  • next.span_type
  • next.route

generateMetadata [next.page]

  • next.span_type: ResolveMetadata.generateMetadata.

Ce span représente le processus de génération de métadonnées pour une page spécifique (une seule route peut avoir plusieurs de ces spans).

Attributs :

  • next.span_name
  • next.span_type
  • next.page