Wie Sie Ihre Next.js-App mit OpenTelemetry instrumentieren

Observability ist entscheidend, um das Verhalten und die Leistung Ihrer Next.js-App zu verstehen und zu optimieren.

Da Anwendungen immer komplexer werden, wird es zunehmend schwieriger, auftretende Probleme zu identifizieren und zu diagnostizieren. Durch den Einsatz von Observability-Tools wie Logging und Metriken können Entwickler Einblicke in das Verhalten ihrer Anwendung gewinnen und Bereiche für Optimierungen identifizieren. Mit Observability können Entwickler proaktiv Probleme angehen, bevor sie zu größeren Schwierigkeiten werden, und so eine bessere Benutzererfahrung bieten. Daher wird dringend empfohlen, Observability in Ihren Next.js-Anwendungen zu verwenden, um die Leistung zu verbessern, Ressourcen zu optimieren und die Benutzererfahrung zu verbessern.

Wir empfehlen die Verwendung von OpenTelemetry für die Instrumentierung Ihrer Apps. Es handelt sich um eine plattformunabhängige Methode zur Instrumentierung von Apps, die es Ihnen ermöglicht, Ihren Observability-Provider zu wechseln, ohne Ihren Code ändern zu müssen. Lesen Sie die offiziellen OpenTelemetry-Dokumente für weitere Informationen über OpenTelemetry und seine Funktionsweise.

In dieser Dokumentation werden Begriffe wie Span, Trace oder Exporter verwendet, die alle im OpenTelemetry Observability Primer nachgelesen werden können.

Next.js unterstützt OpenTelemetry-Instrumentierung out-of-the-box, was bedeutet, dass wir Next.js selbst bereits instrumentiert haben.

Wenn Sie OpenTelemetry aktivieren, werden wir automatisch Ihren gesamten Code wie getStaticProps in Spans mit hilfreichen Attributen einbinden.

Erste Schritte

OpenTelemetry ist erweiterbar, aber die korrekte Einrichtung kann recht umfangreich sein. Deshalb haben wir das Paket @vercel/otel vorbereitet, das Ihnen hilft, schnell loszulegen.

Verwendung von @vercel/otel

Um zu beginnen, installieren Sie die folgenden Pakete:

Terminal
npm install @vercel/otel @opentelemetry/sdk-logs @opentelemetry/api-logs @opentelemetry/instrumentation

Erstellen Sie als Nächstes eine benutzerdefinierte instrumentation.ts- (oder .js-) Datei im Stammverzeichnis des Projekts (oder im src-Ordner, falls verwendet):

import { registerOTel } from '@vercel/otel'

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

Weitere Konfigurationsoptionen finden Sie in der @vercel/otel-Dokumentation.

Wissenswert:

  • Die instrumentation-Datei sollte sich im Stammverzeichnis Ihres Projekts befinden und nicht im app- oder pages-Verzeichnis. Wenn Sie den src-Ordner verwenden, platzieren Sie die Datei in src neben pages und app.
  • Wenn Sie die pageExtensions-Konfigurationsoption verwenden, um ein Suffix hinzuzufügen, müssen Sie auch den Dateinamen der instrumentation-Datei entsprechend anpassen.
  • Wir haben ein grundlegendes with-opentelemetry-Beispiel erstellt, das Sie verwenden können.

Manuelle OpenTelemetry-Konfiguration

Das Paket @vercel/otel bietet viele Konfigurationsoptionen und sollte die meisten gängigen Anwendungsfälle abdecken. Falls es Ihren Anforderungen jedoch nicht entspricht, können Sie OpenTelemetry auch manuell konfigurieren.

Zuerst müssen Sie die OpenTelemetry-Pakete installieren:

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

Nun können Sie NodeSDK in Ihrer instrumentation.ts initialisieren. Im Gegensatz zu @vercel/otel ist NodeSDK nicht mit der Edge-Runtime kompatibel, daher müssen Sie sicherstellen, dass Sie sie nur importieren, wenn process.env.NEXT_RUNTIME === 'nodejs'. Wir empfehlen die Erstellung einer neuen Datei instrumentation.node.ts, die Sie nur bedingt importieren, wenn Sie Node verwenden:

export async function register() {
  if (process.env.NEXT_RUNTIME === 'nodejs') {
    await import('./instrumentation.node.ts')
  }
}

Dies entspricht der Verwendung von @vercel/otel, ermöglicht jedoch die Modifikation und Erweiterung einiger Funktionen, die von @vercel/otel nicht bereitgestellt werden. Falls Edge-Runtime-Unterstützung erforderlich ist, müssen Sie @vercel/otel verwenden.

Testen Ihrer Instrumentierung

Sie benötigen einen OpenTelemetry-Collector mit einem kompatiblen Backend, um OpenTelemetry-Traces lokal zu testen. Wir empfehlen die Verwendung unserer OpenTelemetry-Entwicklungsumgebung.

Wenn alles korrekt funktioniert, sollten Sie den Root-Server-Span mit der Bezeichnung GET /requested/pathname sehen können. Alle weiteren Spans dieses spezifischen Traces werden darunter verschachtelt sein.

Next.js erfasst mehr Spans als standardmäßig ausgegeben werden. Um mehr Spans zu sehen, müssen Sie NEXT_OTEL_VERBOSE=1 setzen.

Bereitstellung

Verwendung des OpenTelemetry Collectors

Wenn Sie mit einem OpenTelemetry Collector bereitstellen, können Sie @vercel/otel verwenden. Dies funktioniert sowohl auf Vercel als auch bei Selbsthosting.

Bereitstellung auf Vercel

Wir haben sichergestellt, dass OpenTelemetry auf Vercel out-of-the-box funktioniert.

Folgen Sie der Vercel-Dokumentation, um Ihr Projekt mit einem Observability-Provider zu verbinden.

Selbsthosting

Die Bereitstellung auf anderen Plattformen ist ebenfalls unkompliziert. Sie müssen Ihren eigenen OpenTelemetry-Collector einrichten, um die Telemetriedaten Ihrer Next.js-App zu empfangen und zu verarbeiten.

Folgen Sie dazu dem OpenTelemetry Collector Getting Started Guide, der Sie durch die Einrichtung des Collectors und die Konfiguration für den Empfang von Daten Ihrer Next.js-App führt.

Sobald Ihr Collector läuft, können Sie Ihre Next.js-App auf Ihrer gewählten Plattform gemäß deren jeweiligen Bereitstellungsanleitungen deployen.

Benutzerdefinierte Exporter

Ein OpenTelemetry Collector ist nicht zwingend erforderlich. Sie können einen benutzerdefinierten OpenTelemetry-Exporter mit @vercel/otel oder manueller OpenTelemetry-Konfiguration verwenden.

Benutzerdefinierte Spans

Sie können benutzerdefinierte Spans mit den OpenTelemetry APIs hinzufügen.

Terminal
npm install @opentelemetry/api

Das folgende Beispiel zeigt eine Funktion, die GitHub-Sterne abruft und einen benutzerdefinierten fetchGithubStars-Span hinzufügt, um das Ergebnis der Fetch-Anfrage zu verfolgen:

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()
      }
    })
}

Die register-Funktion wird ausgeführt, bevor Ihr Code in einer neuen Umgebung läuft. Sie können beginnen, neue Spans zu erstellen, die korrekt zum exportierten Trace hinzugefügt werden sollten.

Standard-Spans in Next.js

Next.js instrumentiert automatisch mehrere Spans, um nützliche Einblicke in die Leistung Ihrer Anwendung zu bieten.

Attribute in Spans folgen den OpenTelemetry Semantic Conventions. Wir fügen auch einige benutzerdefinierte Attribute unter dem next-Namespace hinzu:

  • next.span_name - dupliziert den Span-Namen
  • next.span_type - jeder Span-Typ hat eine eindeutige Kennung
  • next.route - Das Routenmuster der Anfrage (z.B. /[param]/user).
  • next.rsc (true/false) - Gibt an, ob es sich um eine RSC-Anfrage handelt, wie z.B. Prefetch.
  • next.page
    • Dies ist ein interner Wert, der vom App-Router verwendet wird.
    • Man kann ihn sich als Route zu einer speziellen Datei vorstellen (wie page.ts, layout.ts, loading.ts und andere)
    • Er kann nur in Kombination mit next.route als eindeutiger Identifikator verwendet werden, da /layout sowohl /(groupA)/layout.ts als auch /(groupB)/layout.ts identifizieren kann

[http.method] [next.route]

  • next.span_type: BaseServer.handleRequest

Dieser Span repräsentiert den Root-Span für jede eingehende Anfrage an Ihre Next.js-Anwendung. Er verfolgt die HTTP-Methode, Route, Ziel und Statuscode der Anfrage.

Attribute:

render route (app) [next.route]

  • next.span_type: AppRender.getBodyResult.

Dieser Span repräsentiert den Prozess des Renderns einer Route im App-Router.

Attribute:

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

fetch [http.method] [http.url]

  • next.span_type: AppRender.fetch.

Dieser Span repräsentiert die in Ihrem Code ausgeführte Fetch-Anfrage.

Attribute:

Dieser Span kann durch Setzen von NEXT_OTEL_FETCH_DISABLED=1 in Ihrer Umgebung deaktiviert werden. Dies ist nützlich, wenn Sie eine benutzerdefinierte Fetch-Instrumentierungsbibliothek verwenden möchten.

executing api route (app) [next.route]

  • next.span_type: AppRouteRouteHandlers.runHandler.

Dieser Span repräsentiert die Ausführung eines API-Route-Handlers im App-Router.

Attribute:

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

getServerSideProps [next.route]

  • next.span_type: Render.getServerSideProps.

Dieser Span repräsentiert die Ausführung von getServerSideProps für eine bestimmte Route.

Attribute:

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

getStaticProps [next.route]

  • next.span_type: Render.getStaticProps.

Dieser Span repräsentiert die Ausführung von getStaticProps für eine bestimmte Route.

Attribute:

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

render route (pages) [next.route]

  • next.span_type: Render.renderDocument.

Dieser Span repräsentiert den Prozess des Renderns des Dokuments für eine bestimmte Route.

Attribute:

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

generateMetadata [next.page]

  • next.span_type: ResolveMetadata.generateMetadata.

Dieser Span repräsentiert den Prozess der Generierung von Metadaten für eine bestimmte Seite (eine einzelne Route kann mehrere dieser Spans haben).

Attribute:

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

resolve page components

  • next.span_type: NextNodeServer.findPageComponents.

Dieser Span repräsentiert den Prozess der Auflösung von Seitenkomponenten für eine bestimmte Seite.

Attribute:

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

resolve segment modules

  • next.span_type: NextNodeServer.getLayoutOrPageModule.

Dieser Span repräsentiert das Laden von Codemodulen für ein Layout oder eine Seite.

Attribute:

  • next.span_name
  • next.span_type
  • next.segment

start response

  • next.span_type: NextNodeServer.startResponse.

Dieser Span ohne Länge repräsentiert den Zeitpunkt, zu dem das erste Byte in der Antwort gesendet wurde.