Capítulo 64 de 456

OpenTelemetry

Core Idea

How to instrument a Next.js app with OpenTelemetry for observability (tracing), either via the @vercel/otel helper package or manual SDK configuration, plus reference for the spans Next.js emits by default.

Key Concepts

  • @vercel/otel: official helper package that wraps OpenTelemetry setup; recommended entry point, registered inside instrumentation.ts's register() function.
  • instrumentation.ts/.js: special file at the project root (or inside src/) where OpenTelemetry (or other instrumentation) is registered; must match pageExtensions config if customized.
  • Manual NodeSDK configuration: alternative to @vercel/otel for advanced cases; not edge-runtime compatible, so it must be conditionally imported only when process.env.NEXT_RUNTIME === 'nodejs'.
  • NEXT_OTEL_VERBOSE=1: env var that surfaces additional spans Next.js traces internally but doesn't emit by default.
  • NEXT_OTEL_FETCH_DISABLED=1: env var to turn off the automatic fetch span, useful when using a custom fetch instrumentation library.
  • Custom spans: created with @opentelemetry/api's trace.getTracer(name).startActiveSpan(...), callable from any code that runs after register().
  • next.* custom span attributes: next.span_name, next.span_type, next.route, next.rsc, next.page — the framework's semantic-convention extensions.

Code Examples

// instrumentation.ts — quick setup with @vercel/otel
import { registerOTel } from '@vercel/otel'

export function register() {
  registerOTel({ serviceName: 'next-app' })
}
  • O que demonstra: setup mínimo recomendado de OpenTelemetry via @vercel/otel.
// instrumentation.ts — conditional import for manual Node-only SDK
export async function register() {
  if (process.env.NEXT_RUNTIME === 'nodejs') {
    await import('./instrumentation.node.ts')
  }
}
// instrumentation.node.ts — manual NodeSDK configuration
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'
import { resourceFromAttributes } from '@opentelemetry/resources'
import { NodeSDK } from '@opentelemetry/sdk-node'
import { SimpleSpanProcessor } from '@opentelemetry/sdk-trace-node'
import { ATTR_SERVICE_NAME } from '@opentelemetry/semantic-conventions'

const sdk = new NodeSDK({
  resource: resourceFromAttributes({
    [ATTR_SERVICE_NAME]: 'next-app',
  }),
  spanProcessor: new SimpleSpanProcessor(new OTLPTraceExporter()),
})
sdk.start()
  • O que demonstra: NodeSDK não roda em edge runtime, por isso precisa de import condicional isolado num arquivo separado.
// Custom span example
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()
      }
    })
}
  • O que demonstra: padrão para criar um span customizado em torno de uma operação assíncrona, sempre finalizando com span.end() no finally.

Reference Tables

Span namenext.span_typeDescrição
[http.method] [next.route]BaseServer.handleRequestSpan raiz de cada request recebido
render route (app) [next.route]AppRender.getBodyResultRenderização de uma rota no app router
fetch [http.method] [http.url]AppRender.fetchRequisição fetch executada no código (desativável com NEXT_OTEL_FETCH_DISABLED=1)
executing api route (app) [next.route]AppRouteRouteHandlers.runHandlerExecução de um Route Handler no app router
getServerSideProps [next.route]Render.getServerSidePropsExecução de getServerSideProps (pages router)
getStaticProps [next.route]Render.getStaticPropsExecução de getStaticProps (pages router)
render route (pages) [next.route]Render.renderDocumentRenderização do documento (pages router)
generateMetadata [next.page]ResolveMetadata.generateMetadataGeração de metadata (pode ocorrer múltiplas vezes por rota)
resolve page componentsNextNodeServer.findPageComponentsResolução de componentes de página
resolve segment modulesNextNodeServer.getLayoutOrPageModuleCarregamento de módulos de layout/página
start responseNextNodeServer.startResponseSpan de tamanho zero, marca o primeiro byte enviado

Anti-patterns

  • Colocar instrumentation.ts dentro de app/ ou pages/: precisa estar na raiz do projeto (ou em src/ se usado).
  • Importar NodeSDK incondicionalmente: quebra em edge runtime; sempre isolar atrás de NEXT_RUNTIME === 'nodejs'.
  • Usar NodeSDK quando precisa de suporte a edge runtime: NodeSDK não é compatível; @vercel/otel é obrigatório nesse caso.

Key Takeaways

  1. Next.js já vem instrumentado internamente; só falta registrar um exporter via instrumentation.ts.
  2. @vercel/otel cobre a maioria dos casos e funciona tanto na Vercel quanto self-hosted; configuração manual só é necessária para customizações não expostas pelo pacote.
  3. NEXT_OTEL_VERBOSE=1 revela spans adicionais não emitidos por padrão; útil para debug fino.
  4. Spans customizados usam a API padrão do OpenTelemetry (@opentelemetry/api), sem necessidade de pacote proprietário da Vercel.
  5. Deploy requer um OpenTelemetry Collector próprio quando self-hosted (fora da Vercel), seguindo o guia oficial do Collector.

Connects To

  • Instrumentation file conventions: instrumentation.ts é o mesmo arquivo usado para outros hooks de inicialização do servidor, não exclusivo de OpenTelemetry.