OpenTelemetry

Gut zu wissen: Dieses Feature ist experimentell, Sie müssen explizit zustimmen, indem Sie experimental.instrumentationHook = true; in Ihrer next.config.js angeben.

Observability ist entscheidend, um das Verhalten und die Performance 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 Optimierungspotenziale erkennen. 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 nutzen, um die Performance zu verbessern, Ressourcen zu optimieren und die Benutzererfahrung zu verbessern.

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

Diese Dokumentation verwendet Begriffe wie Span, Trace oder Exporter, die alle im OpenTelemetry Observability Primer erklärt werden.

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 all Ihren Code wie getStaticProps in Spans mit hilfreichen Attributen einbinden.

Erste Schritte

OpenTelemetry ist erweiterbar, aber die Einrichtung kann recht umfangreich sein. Deshalb haben wir das Paket @vercel/otel vorbereitet, das Ihnen den schnellen Einstieg erleichtert.

Verwendung von @vercel/otel

Für den Einstieg müssen Sie @vercel/otel installieren:

Terminal
npm install @vercel/otel

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' })
}
import { registerOTel } from '@vercel/otel'

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

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

Gut zu wissen

  • Die instrumentation-Datei sollte im Stammverzeichnis Ihres Projekts liegen und nicht im app- oder pages-Verzeichnis. Wenn Sie den src-Ordner verwenden, legen Sie die Datei in src neben pages und app ab.
  • 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 einfaches with-opentelemetry-Beispiel erstellt, das Sie verwenden können.

Manuelle OpenTelemetry-Konfiguration

Das @vercel/otel-Paket bietet viele Konfigurationsoptionen und sollte die meisten gängigen Anwendungsfälle abdecken. Falls es Ihren Anforderungen 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 dem Edge-Runtime kompatibel, daher müssen Sie sicherstellen, dass Sie sie nur importieren, wenn process.env.NEXT_RUNTIME === 'nodejs'. Wir empfehlen, eine neue Datei instrumentation.node.ts zu erstellen, die Sie nur bei Verwendung von Node bedingt importieren:

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 { SEMRESATTRS_SERVICE_NAME } from '@opentelemetry/semantic-conventions'
import { SimpleSpanProcessor } from '@opentelemetry/sdk-trace-node'

const sdk = new NodeSDK({
  resource: new Resource({
    [SEMRESATTRS_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 { SEMRESATTRS_SERVICE_NAME } from '@opentelemetry/semantic-conventions'
import { SimpleSpanProcessor } from '@opentelemetry/sdk-trace-node'

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

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.

Deployment

Verwendung des OpenTelemetry Collectors

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

Deployment 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 Deployment-Anleitungen bereitstellen.

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 mit den OpenTelemetry APIs benutzerdefinierte Spans 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 neue Spans erstellen, die korrekt zum exportierten Trace hinzugefügt werden.

Standard-Spans in Next.js

Next.js instrumentiert automatisch mehrere Spans, um Ihnen nützliche Einblicke in die Performance 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 - wiederholt den Span-Namen
  • next.span_type - jeder Span-Typ hat eine eindeutige ID
  • 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.
    • Sie können sich das als eine Route zu einer speziellen Datei vorstellen (wie page.ts, layout.ts, loading.ts und andere)
    • Es 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 Code-Modulen 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.