문서
App Router 사용하기
OpenTelemetry

OpenTelemetry

알아두면 좋은 점: 이 기능은 실험적입니다. next.config.js에서 experimental.instrumentationHook = true;를 명시적으로 제공해야 사용할 수 있습니다.

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

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

앱을 계측하기 위해 OpenTelemetry를 사용하는 것을 권장합니다. 이는 관찰 가능성 제공자를 변경할 때 코드를 변경하지 않고도 앱을 계측할 수 있는 플랫폼 독립적인 방법입니다. OpenTelemetry 및 그 작동 방식에 대한 자세한 정보는 공식 OpenTelemetry 문서를 참조하세요.

이 문서에서는 Span, Trace 또는 Exporter와 같은 용어를 사용합니다. 이 모든 용어는 OpenTelemetry 관찰 가능성 입문서에서 찾을 수 있습니다.

Next.js는 OpenTelemetry 계측을 기본적으로 지원합니다. 즉, 우리는 이미 Next.js 자체를 계측했습니다. OpenTelemetry를 활성화하면 getStaticProps와 같은 모든 코드를 유용한 속성이 있는 spans로 자동으로 감싸게 됩니다.

시작하기

OpenTelemetry는 확장 가능하지만 올바르게 설정하는 것은 꽤 복잡할 수 있습니다. 그래서 우리는 빠르게 시작할 수 있도록 @vercel/otel 패키지를 준비했습니다.

@vercel/otel 사용하기

시작하려면 다음 패키지를 설치하세요:

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

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

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 문서를 참조하세요.

알아두면 좋은 점

  • instrumentation 파일은 프로젝트의 루트에 있어야 하며 app 또는 pages 디렉토리 안에 있으면 안 됩니다. src 폴더를 사용하는 경우 pagesapp 옆의 src 안에 파일을 배치하세요.
  • pageExtensions 구성 옵션을 사용하여 접미사를 추가하는 경우, instrumentation 파일 이름도 이에 맞게 업데이트해야 합니다.
  • 사용할 수 있는 기본 with-opentelemetry 예제를 만들었습니다.

수동 OpenTelemetry 구성

@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는 edge 런타임과 호환되지 않으므로 process.env.NEXT_RUNTIME === 'nodejs'일 때만 임포트하도록 해야 합니다. 새 파일 instrumentation.node.ts를 생성하고 node를 사용할 때만 조건부로 임포트하는 것을 권장합니다:

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 { 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();
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();

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

계측 테스트하기

OpenTelemetry 추적을 로컬에서 테스트하려면 호환되는 백엔드가 있는 OpenTelemetry 컬렉터가 필요합니다. OpenTelemetry 개발 환경을 사용하는 것을 권장합니다.

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

Next.js는 기본적으로 방출되는 것보다 더 많은 span을 추적합니다. 더 많은 span을 보려면 NEXT_OTEL_VERBOSE=1을 설정해야 합니다.

배포

OpenTelemetry 컬렉터 사용하기

OpenTelemetry 컬렉터로 배포할 때 @vercel/otel을 사용할 수 있습니다. Vercel과 자체 호스팅 모두에서 작동할 것입니다.

Vercel에 배포하기

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

프로젝트를 관찰 가능성 제공자에 연결하려면 Vercel 문서를 따르세요.

자체 호스팅

다른 플랫폼에 배포하는 것도 간단합니다. Next.js 앱에서 원격 측정 데이터를 수신하고 처리하기 위해 자체 OpenTelemetry 컬렉터를 실행해야 합니다.

이를 위해 OpenTelemetry 컬렉터 시작 가이드를 따르세요. 이 가이드는 컬렉터를 설정하고 Next.js 앱에서 데이터를 수신하도록 구성하는 과정을 안내합니다.

컬렉터를 실행하고 있으면 선택한 플랫폼의 배포 가이드에 따라 Next.js 앱을 배포할 수 있습니다.

사용자 정의 익스포터(Exporter)

OpenTelemetry 컬렉터는 필수가 아닙니다. @vercel/otel 또는 수동 OpenTelemetry 구성과 함께 사용자 정의 OpenTelemetry 익스포터를 사용할 수 있습니다.

사용자 정의 Span

OpenTelemetry API를 사용하여 사용자 정의 span을 추가할 수 있습니다.

Terminal
npm install @opentelemetry/api

다음 예제는 GitHub 스타를 가져오고 fetch 요청의 결과를 추적하기 위해 사용자 정의 fetchGithubStars span을 추가하는 함수를 보여줍니다:

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 함수는 새로운 환경에서 코드가 실행되기 전에 실행됩니다. 새로운 span을 생성할 수 있으며, 이들은 내보내진 trace에 올바르게 추가되어야 합니다.

Next.js의 기본 Span

Next.js는 애플리케이션의 성능에 대한 유용한 통찰력을 제공하기 위해 자동으로 여러 span을 계측합니다.

span의 속성은 OpenTelemetry 의미 규칙을 따릅니다. 또한 next 네임스페이스 아래에 몇 가지 사용자 정의 속성을 추가합니다:

  • next.span_name - span 이름을 복제합니다
  • next.span_type - 각 span 유형에는 고유한 식별자가 있습니다
  • 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

이 span은 Next.js 애플리케이션에 들어오는 각 요청에 대한 루트 span을 나타냅니다. 요청의 HTTP 메서드, 경로, 대상 및 상태 코드를 추적합니다.

속성:

render route (app) [next.route]

  • next.span_type: AppRender.getBodyResult

이 span은 앱 라우터에서 경로를 렌더링하는 과정을 나타냅니다.

속성:

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

fetch [http.method] [http.url]

  • next.span_type: AppRender.fetch

이 span은 코드에서 실행된 fetch 요청을 나타냅니다.

속성:

이 span은 환경에서 NEXT_OTEL_FETCH_DISABLED=1을 설정하여 끌 수 있습니다. 이는 사용자 정의 fetch 계측 라이브러리를 사용하려는 경우에 유용합니다.

executing api route (app) [next.route]

  • next.span_type: AppRouteRouteHandlers.runHandler

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

속성:

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

getServerSideProps [next.route]

  • next.span_type: Render.getServerSideProps

이 span은 특정 경로에 대한 getServerSideProps의 실행을 나타냅니다.

속성:

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

getStaticProps [next.route]

  • next.span_type: Render.getStaticProps

이 span은 특정 경로에 대한 getStaticProps의 실행을 나타냅니다.

속성:

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

render route (pages) [next.route]

  • next.span_type: Render.renderDocument

이 span은 특정 경로에 대한 문서 렌더링 과정을 나타냅니다.

속성:

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

generateMetadata [next.page]

  • next.span_type: ResolveMetadata.generateMetadata

이 span은 특정 페이지에 대한 메타데이터 생성 과정을 나타냅니다(단일 경로에 여러 개의 이러한 span이 있을 수 있습니다).

속성:

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

resolve page components

  • next.span_type: NextNodeServer.findPageComponents

이 span은 특정 페이지에 대한 페이지 컴포넌트 해결 과정을 나타냅니다.

속성:

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

resolve segment modules

  • next.span_type: NextNodeServer.getLayoutOrPageModule

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

속성:

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

start response

  • next.span_type: NextNodeServer.startResponse

이 길이가 0인 span은 응답의 첫 번째 바이트가 전송된 시간을 나타냅니다.