OpenTelemetry

Полезно знать: Эта функция находится в экспериментальном режиме, вам необходимо явно включить её, добавив experimental.instrumentationHook = true; в ваш next.config.js.

Наблюдаемость (observability) крайне важна для понимания и оптимизации поведения и производительности вашего Next.js приложения.

По мере усложнения приложений становится всё труднее выявлять и диагностировать возникающие проблемы. Используя инструменты наблюдаемости, такие как логирование и метрики, разработчики могут получить представление о поведении приложения и определить области для оптимизации. С наблюдаемостью разработчики могут заранее устранять проблемы до того, как они станут серьёзными, и обеспечивать лучший пользовательский опыт. Поэтому настоятельно рекомендуется использовать наблюдаемость в ваших Next.js приложениях для улучшения производительности, оптимизации ресурсов и повышения удобства пользователей.

Мы рекомендуем использовать OpenTelemetry для инструментирования ваших приложений. Это платформонезависимый способ инструментирования, который позволяет менять провайдера наблюдаемости без изменения кода. Подробнее о OpenTelemetry и принципах его работы читайте в официальной документации OpenTelemetry.

В этой документации используются термины вроде Span (Спан), Trace (Трейс) или Exporter (Экспортер), все они описаны в основах наблюдаемости OpenTelemetry.

Next.js поддерживает инструментирование OpenTelemetry "из коробки", что означает, что мы уже инструментировали сам Next.js. При включении OpenTelemetry мы автоматически оборачиваем весь ваш код, такой как getStaticProps, в спаны с полезными атрибутами.

Начало работы

OpenTelemetry расширяем, но его настройка может быть довольно многословной. Поэтому мы подготовили пакет @vercel/otel, который помогает быстро начать работу.

Использование @vercel/otel

Для начала необходимо установить @vercel/otel:

Терминал
npm install @vercel/otel

Затем создайте файл instrumentation.ts (или .js) в корневой директории проекта (или внутри папки src, если она используется):

import { registerOTel } from '@vercel/otel'

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

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

Дополнительные параметры конфигурации смотрите в документации @vercel/otel.

Полезно знать

  • Файл instrumentation должен находиться в корне проекта, а не внутри папок app или pages. Если вы используете папку src, поместите файл внутрь src рядом с pages и app.
  • Если вы используете опцию конфигурации pageExtensions для добавления суффикса, вам также потребуется обновить имя файла instrumentation, чтобы оно соответствовало.
  • Мы создали базовый пример with-opentelemetry, который вы можете использовать.

Ручная настройка OpenTelemetry

Пакет @vercel/otel предоставляет множество параметров конфигурации и должен покрывать большинство распространённых случаев использования. Но если он не подходит для ваших нужд, вы можете настроить OpenTelemetry вручную.

Сначала необходимо установить пакеты OpenTelemetry:

Терминал
npm install @opentelemetry/sdk-node @opentelemetry/resources @opentelemetry/semantic-conventions @opentelemetry/sdk-trace-node @opentelemetry/exporter-trace-otlp-http

Теперь вы можете инициализировать NodeSDK в вашем instrumentation.ts. В отличие от @vercel/otel, NodeSDK не совместим с edge-рантаймом, поэтому вам нужно убедиться, что вы импортируете их только когда process.env.NEXT_RUNTIME === 'nodejs'. Мы рекомендуем создать новый файл instrumentation.node.ts, который вы будете условно импортировать только при использовании 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 { 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()

Это эквивалентно использованию @vercel/otel, но позволяет изменять и расширять некоторые функции, которые не представлены в @vercel/otel. Если необходима поддержка edge-рантайма, вам придётся использовать @vercel/otel.

Тестирование инструментирования

Для тестирования трейсов OpenTelemetry локально вам потребуется OpenTelemetry collector с совместимым бэкендом. Мы рекомендуем использовать нашу среду разработки OpenTelemetry.

Если всё работает правильно, вы должны увидеть корневой серверный спан с меткой GET /requested/pathname. Все остальные спаны из этого конкретного трейса будут вложены под ним.

Next.js трейсит больше спанов, чем выводится по умолчанию. Чтобы увидеть больше спанов, вы должны установить NEXT_OTEL_VERBOSE=1.

Развёртывание

Использование OpenTelemetry Collector

При развёртывании с OpenTelemetry Collector вы можете использовать @vercel/otel. Это будет работать как на Vercel, так и при самостоятельном хостинге.

Развёртывание на Vercel

Мы позаботились о том, чтобы OpenTelemetry работал "из коробки" на Vercel.

Следуйте документации Vercel, чтобы подключить ваш проект к провайдеру наблюдаемости.

Самостоятельный хостинг

Развёртывание на других платформах также просто. Вам потребуется запустить собственный OpenTelemetry Collector для приёма и обработки данных телеметрии из вашего Next.js приложения.

Для этого следуйте руководству по началу работы с OpenTelemetry Collector, которое поможет вам настроить коллектор и сконфигурировать его для приёма данных из вашего Next.js приложения.

После запуска коллектора вы можете развернуть ваше Next.js приложение на выбранной платформе, следуя соответствующим руководствам по развёртыванию.

Пользовательские экспортеры

OpenTelemetry Collector не является обязательным. Вы можете использовать пользовательский экспортер OpenTelemetry с @vercel/otel или ручной конфигурацией OpenTelemetry.

Пользовательские спаны

Вы можете добавить пользовательский спан с помощью API OpenTelemetry.

Терминал
npm install @opentelemetry/api

Следующий пример демонстрирует функцию, которая получает звёзды GitHub и добавляет пользовательский спан fetchGithubStars для отслеживания результата запроса:

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

Функция register будет выполнена перед запуском вашего кода в новой среде. Вы можете начать создавать новые спаны, и они должны быть корректно добавлены в экспортируемый трейс.

Стандартные спаны в Next.js

Next.js автоматически инструментирует несколько спанов, чтобы предоставить полезную информацию о производительности вашего приложения.

Атрибуты спанов следуют семантическим соглашениям OpenTelemetry. Мы также добавляем некоторые пользовательские атрибуты в пространстве имён next:

  • next.span_name - дублирует имя спана
  • next.span_type - каждый тип спана имеет уникальный идентификатор
  • next.route - Шаблон маршрута запроса (например, /[param]/user).
  • next.rsc (true/false) - Является ли запрос RSC-запросом, например, предварительной загрузкой.
  • next.page
    • Это внутреннее значение, используемое маршрутизатором app.
    • Можно рассматривать его как маршрут к специальному файлу (например, page.ts, layout.ts, loading.ts и другим)
    • Может использоваться как уникальный идентификатор только в сочетании с next.route, так как /layout может идентифицировать как /(groupA)/layout.ts, так и /(groupB)/layout.ts

[http.method] [next.route]

  • next.span_type: BaseServer.handleRequest

Этот спан представляет корневой спан для каждого входящего запроса к вашему Next.js приложению. Он отслеживает HTTP-метод, маршрут, цель и статус-код запроса.

Атрибуты:

render route (app) [next.route]

  • next.span_type: AppRender.getBodyResult.

Этот спан представляет процесс рендеринга маршрута в маршрутизаторе app.

Атрибуты:

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

fetch [http.method] [http.url]

  • next.span_type: AppRender.fetch.

Этот спан представляет fetch-запрос, выполненный в вашем коде.

Атрибуты:

Этот спан можно отключить, установив NEXT_OTEL_FETCH_DISABLED=1 в вашем окружении. Это полезно, когда вы хотите использовать пользовательскую библиотеку инструментирования fetch.

executing api route (app) [next.route]

  • next.span_type: AppRouteRouteHandlers.runHandler.

Этот спан представляет выполнение обработчика API-маршрута в маршрутизаторе app.

Атрибуты:

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

getServerSideProps [next.route]

  • next.span_type: Render.getServerSideProps.

Этот спан представляет выполнение getServerSideProps для конкретного маршрута.

Атрибуты:

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

getStaticProps [next.route]

  • next.span_type: Render.getStaticProps.

Этот спан представляет выполнение getStaticProps для конкретного маршрута.

Атрибуты:

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

render route (pages) [next.route]

  • next.span_type: Render.renderDocument.

Этот спан представляет процесс рендеринга документа для конкретного маршрута.

Атрибуты:

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

generateMetadata [next.page]

  • next.span_type: ResolveMetadata.generateMetadata.

Этот спан представляет процесс генерации метаданных для конкретной страницы (один маршрут может иметь несколько таких спанов).

Атрибуты:

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

resolve page components

  • next.span_type: NextNodeServer.findPageComponents.

Этот спан представляет процесс разрешения компонентов страницы для конкретной страницы.

Атрибуты:

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

resolve segment modules

  • next.span_type: NextNodeServer.getLayoutOrPageModule.

Этот спан представляет загрузку модулей кода для layout или страницы.

Атрибуты:

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

start response

  • next.span_type: NextNodeServer.startResponse.

Этот спан нулевой длительности представляет момент, когда был отправлен первый байт ответа.