OpenTelemetry

알아두면 좋은 점: 이 기능은 실험적입니다. next.config.jsexperimental.instrumentationHook = true;를 제공하여 명시적으로 옵트인해야 합니다.

관찰 가능성은 Next.js 앱의 동작과 성능을 이해하고 최적화하는 데 매우 중요합니다.

애플리케이션이 점점 더 복잡해짐에 따라 발생할 수 있는 문제를 식별하고 진단하는 것이 점점 더 어려워집니다. 로깅 및 메트릭과 같은 관찰 가능성 도구를 활용하면 개발자는 애플리케이션의 동작에 대한 인사이트를 얻고 최적화할 영역을 식별할 수 있습니다. 관찰 가능성을 통해 개발자는 주요 문제가 되기 전에 문제를 사전에 해결하고 더 나은 사용자 경험을 제공할 수 있습니다. 따라서 Next.js 애플리케이션에서 성능을 개선하고 리소스를 최적화하며 사용자 경험을 향상시키기 위해 관찰 가능성을 사용하는 것이 매우 권장됩니다.

앱을 계측하기 위해 OpenTelemetry를 사용하는 것이 좋습니다. 이는 관찰 가능성 공급자를 변경하더라도 코드를 변경할 필요가 없는 플랫폼에 구애받지 않는 계측 방법입니다. OpenTelemetry에 대한 자세한 내용과 작동 방식을 확인하려면 공식 OpenTelemetry 문서 (opens in a new tab)를 참조하세요.

이 문서에서는 Span, Trace 또는 Exporter와 같은 용어를 사용하며, 이는 모두 OpenTelemetry Observability Primer (opens in a new tab)에서 찾을 수 있습니다.

Next.js는 기본적으로 OpenTelemetry 계측을 지원하므로 Next.js 자체도 이미 계측되었습니다. OpenTelemetry를 활성화하면 getStaticProps와 같은 모든 코드를 유용한 속성이 포함된 spans로 자동 래핑합니다.

Getting Started

OpenTelemetry는 확장 가능하지만 적절히 설정하는 데 상당히 번거로울 수 있습니다. 그래서 우리는 빠르게 시작할 수 있도록 도와주는 @vercel/otel 패키지를 준비했습니다.

Using @vercel/otel

시작하려면 @vercel/otel을 설치해야 합니다:

Terminal
npm install @vercel/otel

다음으로, 프로젝트의 루트 디렉토리에 사용자 정의 instrumentation.ts (또는 .js) 파일을 만듭니다(src 폴더를 사용하는 경우 해당 폴더 내에 만듭니다):

your-project/instrumentation.ts
import { registerOTel } from '@vercel/otel'
 
export function register() {
  registerOTel({ serviceName: 'next-app' })
}
your-project/instrumentation.js
import { registerOTel } from '@vercel/otel'
 
export function register() {
  registerOTel({ serviceName: 'next-app' })
}

추가 구성 옵션은 @vercel/otel 문서 (opens in a new tab)를 참조하세요.

알아두면 좋은 점

  • instrumentation 파일은 프로젝트의 루트에 있어야 하며, app 또는 pages 디렉토리 내부에 있어서는 안 됩니다. src 폴더를 사용하는 경우 pagesapp과 함께 src 내부에 파일을 배치하세요.
  • pageExtensions 구성 옵션을 사용하여 접미사를 추가한 경우, instrumentation 파일 이름도 일치하도록 업데이트해야 합니다.
  • 사용할 수 있는 기본 with-opentelemetry (opens in a new tab) 예제가 있습니다.

Manual OpenTelemetry configuration

@vercel/otel 패키지는 많은 구성 옵션을 제공하며 대부분의 일반적인 사용 사례에 적합합니다. 그러나 필요에 맞지 않는 경우 OpenTelemetry를 수동으로 구성할 수 있습니다.

먼저 OpenTelemetry 패키지를 설치해야 합니다:

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

이제 instrumentation.ts에서 NodeSDK를 초기화할 수 있습니다. @vercel/otel과 달리 NodeSDK는 엣지 런타임과 호환되지 않으므로 process.env.NEXT_RUNTIME === 'nodejs'일 때만 가져오는지 확인해야 합니다. 우리는 노드에서 사용할 때만 조건부로 가져오는 새 파일 instrumentation.node.ts를 만드는 것을 권장합니다:

instrumentation.ts
export async function register() {
  if (process.env.NEXT_RUNTIME === 'nodejs') {
    await import('./instrumentation.node.ts')
  }
}
instrumentation.js
export async function register() {
  if (process.env.NEXT_RUNTIME === 'nodejs') {
    await import('./instrumentation.node.js')
  }
}
instrumentation.node.ts
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()
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()

이렇게 하면 @vercel/otel을 사용하는 것과 동일하지만, @vercel/otel에서 노출하지 않는 일부 기능을 수정하고 확장할 수 있습니다. 엣지 런타임 지원이 필요한 경우 @vercel/otel을 사용해야 합니다.

Testing your instrumentation

OpenTelemetry 추적을 로컬에서 테스트하려면 호환 가능한 백엔드를 갖춘 OpenTelemetry 수집기가 필요합니다. OpenTelemetry dev environment (opens in a new tab)를 사용하는 것이 좋습니다.

모든 것이 잘 작동하면 GET /requested/pathname으로 표시된 루트 서버 스팬을 볼 수 있어야 합니다. 해당 특정 추적의 모든 다른 스팬은 그 아래에 중첩됩니다.

Next.js는 기본적으로 내보내지는 스팬보다 더 많은 스팬을 추적합니다. 더 많은 스팬을 보려면 NEXT_OTEL_VERBOSE=1을 설정해야 합니다.

Deployment

Using OpenTelemetry Collector

OpenTelemetry Collector를 사용하여 배포할 때 @vercel/otel을 사용할 수 있습니다. 이는 Vercel과 자체 호스팅 모두에서 작동합니다.

Deploying on Verc

el

Vercel에서는 OpenTelemetry가 기본적으로 작동하도록 했습니다.

프로젝트를 관찰 가능성 공급자에 연결하는 방법은 Vercel 문서 (opens in a new tab)를 참조하세요.

Self-hosting

다른 플랫폼에 배포하는 것도 간단합니다. Next.js 앱에서 전송된 텔레메트리 데이터를 수신하고 처리하기 위해 OpenTelemetry Collector를 직접 설정해야 합니다.

이를 위해 OpenTelemetry Collector Getting Started guide (opens in a new tab)를 따라 수집기를 설정하고 Next.js 앱에서 데이터를 수신하도록 구성하세요.

수집기를 설정한 후에는 각 플랫폼의 배포 가이드를 따라 Next.js 앱을 선택한 플랫폼에 배포할 수 있습니다.

Custom Exporters

OpenTelemetry Collector는 필수는 아닙니다. @vercel/otel 또는 수동 OpenTelemetry 구성으로 사용자 지정 OpenTelemetry 익스포터를 사용할 수 있습니다.

Custom Spans

OpenTelemetry APIs (opens in a new tab)를 사용하여 사용자 정의 스팬을 추가할 수 있습니다.

Terminal
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 함수는 새로운 환경에서 코드가 실행되기 전에 실행됩니다. 새 스팬을 만들기 시작할 수 있으며, 이는 올바르게 내보낸 추적에 추가되어야 합니다.

Default Spans in Next.js

Next.js는 애플리케이션 성능에 대한 유용한 인사이트를 제공하기 위해 여러 스팬을 자동으로 계측합니다.

스팬의 속성은 OpenTelemetry semantic conventions (opens in a new tab)을 따릅니다. 또한 next 네임스페이스 아래에 일부 사용자 정의 속성을 추가합니다:

  • next.span_name - 스팬 이름을 중복
  • next.span_type - 각 스팬 유형에는 고유한 식별자가 있음
  • next.route - 요청의 라우트 패턴 (예: /[param]/user)
  • next.rsc (true/false) - 요청이 사전 로드와 같은 RSC 요청인지 여부
  • next.page
    • 이는 앱 라우터에서 사용되는 내부 값
    • 특별한 파일(예: 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.

이 스팬은 앱 라우터에서 라우트를 렌더링하는 프로세스를 나타냅니다.

속성:

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

fetch [http.method] [http.url]

  • next.span_type: AppRender.fetch

이 스팬은 코드에서 실행된 가져오기 요청을 나타냅니다.

속성:

이 스팬은 환경에서 NEXT_OTEL_FETCH_DISABLED=1을 설정하여 비활성화할 수 있습니다. 이는 사용자 정의 가져오기 계측 라이브러리를 사용하려는 경우에 유용합니다.

executing api route (app) [next.route]

  • next.span_type: AppRouteRouteHandlers.runHandler.

이 스팬은 앱 라우터에서 API 라우트 핸들러의 실행을 나타냅니다.

속성:

  • 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.

이 스팬은 레이아웃 또는 페이지에 대한 코드 모듈을 로드하는 것을 나타냅니다.

속성:

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

start response

  • next.span_type: NextNodeServer.startResponse.

이 제로 길이 스팬은 응답에서 첫 번째 바이트가 전송된 시간을 나타냅니다.