How to set up instrumentation with OpenTelemetry

김동현·2026년 3월 5일

next.js 공식문서 번역

목록 보기
55/79

title: OpenTelemetry로 계측(Instrumentation) 설정하는 방법
description: Next.js 앱에 OpenTelemetry를 설정하고 계측하는 방법을 배워봅니다.
url: "https://nextjs.org/docs/app/guides/open-telemetry"
version: 16.1.6
lastUpdated: 2026-02-27
prerequisites:


안녕하세요 여러분! 오늘은 Next.js 애플리케이션의 상태를 샅샅이 살펴볼 수 있게 해주는 OpenTelemetry 계측(Instrumentation) 에 대해 배워보겠습니다.

애플리케이션의 동작과 성능을 이해하고 최적화하려면 옵저버빌리티(Observability, 관측 가능성) 가 정말 중요해요.

애플리케이션이 점점 복잡해질수록, 발생할 수 있는 문제들을 찾아내고 진단하는 게 갈수록 어려워지죠. 개발자들은 로깅(logging)이나 메트릭(metrics) 같은 옵저버빌리티 도구들을 활용해서 애플리케이션이 어떻게 동작하고 있는지 통찰력을 얻고, 최적화가 필요한 부분을 찾아낼 수 있습니다. 옵저버빌리티가 갖춰져 있으면, 큰 장애로 번지기 전에 선제적으로 문제를 해결하고 사용자에게 훨씬 더 나은 경험을 제공할 수 있게 됩니다. 따라서 성능을 개선하고, 리소스를 최적화하며, 사용자 경험을 향상시키기 위해 Next.js 애플리케이션에 옵저버빌리티를 적용하는 것을 강력히 권장합니다.

💡 강사의 팁:
프론트엔드 개발자라고 해서 브라우저 단의 에러만 잡던 시대는 지났습니다! Next.js는 서버사이드 렌더링(SSR), 서버 컴포넌트(RSC), API 라우트 등 서버 환경에서 실행되는 코드가 많죠. 에러가 났을 때 "이게 프론트 렌더링 문제인가, 아니면 데이터를 가져오는 백엔드 문제인가?" 헷갈리기 쉽습니다. 이럴 때 전체 흐름을 한눈에 보여주는 것이 바로 옵저버빌리티 도구입니다. 실무에서는 정말 필수적인 기술이에요!

우리는 여러분의 앱을 계측하기 위해 OpenTelemetry를 사용하는 것을 추천해요.
이것은 플랫폼에 종속되지 않는(platform-agnostic) 계측 방법이라서, 코드를 변경하지 않고도 옵저버빌리티 제공자(예: Datadog, New Relic, Sentry 등)를 마음대로 바꿀 수 있다는 엄청난 장점이 있죠.
OpenTelemetry가 무엇이고 어떻게 작동하는지에 대한 더 자세한 정보는 공식 OpenTelemetry 문서(Official OpenTelemetry docs)를 꼭 읽어보시길 바랄게요.

이번 문서에서는 Span(스팬), Trace(트레이스), Exporter(익스포터) 같은 용어들이 계속 등장할 텐데요, 이 개념들은 모두 OpenTelemetry 옵저버빌리티 입문서(the OpenTelemetry Observability Primer)에서 찾아보실 수 있습니다.

📚 부연 설명: Trace와 Span이 뭔가요?

  • Trace(트레이스): 사용자가 요청을 보낸 순간부터 응답을 받을 때까지의 전체 여정을 말해요. (예: "로그인 요청부터 완료까지")
  • Span(스팬): 그 여정 속에서 일어나는 각각의 작업 단위입니다. (예: "DB에서 유저 조회하기", "비밀번호 해시 비교하기" 등). 여러 개의 Span이 모여 하나의 Trace를 이룹니다.
  • Exporter(익스포터): 이렇게 수집된 데이터를 시각화 도구(모니터링 대시보드)로 보내주는 역할을 합니다.

Next.js는 기본적으로 OpenTelemetry 계측을 지원합니다. 이 말은 즉, Next.js 프레임워크 자체에 이미 우리가 계측 코드를 다 심어두었다는 뜻이에요! 정말 편리하죠?

시작하기 (Getting Started)

OpenTelemetry는 확장성이 아주 뛰어나지만, 제대로 설정하려면 코드가 꽤 길고 복잡해질 수 있어요.
그래서 여러분이 빠르게 시작할 수 있도록 저희가 @vercel/otel 이라는 패키지를 준비했답니다.

@vercel/otel 사용하기

자, 시작해볼까요? 먼저 다음 패키지들을 설치해주세요:

pnpm add @vercel/otel @opentelemetry/sdk-logs @opentelemetry/api-logs @opentelemetry/instrumentation
npm install @vercel/otel @opentelemetry/sdk-logs @opentelemetry/api-logs @opentelemetry/instrumentation
yarn add @vercel/otel @opentelemetry/sdk-logs @opentelemetry/api-logs @opentelemetry/instrumentation
bun add @vercel/otel @opentelemetry/sdk-logs @opentelemetry/api-logs @opentelemetry/instrumentation

그 다음, 프로젝트의 루트 디렉토리 (또는 src 폴더를 사용 중이라면 그 안)에 커스텀 instrumentation.ts (또는 .js) 파일을 만들어주세요:

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 문서를 확인해보세요.

알아두면 좋은 점 (Good to know):

  • instrumentation 파일은 app이나 pages 디렉토리 내부가 아니라 프로젝트의 최상위(root)에 있어야 합니다. 만약 src 폴더를 사용하고 있다면, pagesapp 폴더와 같은 위치인 src 내부에 파일을 배치하세요.
  • pageExtensions 설정 옵션을 사용해서 파일 확장자에 접미사를 추가했다면, instrumentation 파일의 이름도 그에 맞춰서 업데이트해야 합니다.
  • 여러분이 참고할 수 있도록 아주 기본적인 with-opentelemetry 예제를 만들어 두었으니 꼭 확인해보세요!

수동 OpenTelemetry 설정 (Manual OpenTelemetry configuration)

@vercel/otel 패키지는 정말 많은 설정 옵션을 제공하고 일반적인 상황에서는 대부분 이걸로 충분할 거예요. 하지만 여러분의 특수한 요구사항에 맞지 않는다면, OpenTelemetry를 직접 수동으로 설정할 수도 있습니다.

우선 OpenTelemetry 관련 패키지들을 직접 설치해야 합니다:

pnpm add @opentelemetry/sdk-node @opentelemetry/resources @opentelemetry/semantic-conventions @opentelemetry/sdk-trace-node @opentelemetry/exporter-trace-otlp-http
npm install @opentelemetry/sdk-node @opentelemetry/resources @opentelemetry/semantic-conventions @opentelemetry/sdk-trace-node @opentelemetry/exporter-trace-otlp-http
yarn add @opentelemetry/sdk-node @opentelemetry/resources @opentelemetry/semantic-conventions @opentelemetry/sdk-trace-node @opentelemetry/exporter-trace-otlp-http
bun add @opentelemetry/sdk-node @opentelemetry/resources @opentelemetry/semantic-conventions @opentelemetry/sdk-trace-node @opentelemetry/exporter-trace-otlp-http

이제 instrumentation.ts 파일에서 NodeSDK를 초기화할 수 있어요.
주의할 점은 @vercel/otel과 달리 NodeSDK는 엣지 런타임(Edge runtime)과 호환되지 않는다는 거예요. 그래서 반드시 process.env.NEXT_RUNTIME === 'nodejs' 일 때만 임포트(import)하도록 만들어야 합니다. Node 환경에서만 조건부로 불러올 수 있도록 instrumentation.node.ts라는 새로운 파일을 만드는 방식을 추천합니다:

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

이렇게 설정하는 것은 @vercel/otel을 사용하는 것과 동일한 효과를 냅니다. 하지만 @vercel/otel이 밖으로 드러내지 않은 일부 기능들을 직접 수정하거나 확장할 수 있다는 장점이 있죠. 만약 엣지 런타임 지원이 꼭 필요하다면, 그때는 무조건 @vercel/otel을 사용해야 합니다.

💡 강사의 팁:
처음 도입할 때는 무조건 @vercel/otel로 시작하는 걸 강력히 추천해요. 나중에 회사 인프라 팀에서 "우리만의 커스텀 Exporter를 달아주세요!"라고 요청할 때, 그때 이 수동 설정 방식을 떠올리시면 됩니다.

계측 테스트하기 (Testing your instrumentation)

로컬에서 OpenTelemetry 트레이스를 테스트해보려면, 이를 받아줄 수 있는 백엔드와 호환되는 OpenTelemetry Collector(수집기)가 필요합니다.
우리가 만들어둔 OpenTelemetry 개발 환경(OpenTelemetry dev environment)을 사용하는 것을 추천해 드려요.

모든 것이 잘 작동한다면 GET /requested/pathname 이라는 라벨이 붙은 루트 서버 스팬(root server span)을 볼 수 있을 거예요.
해당 트레이스에서 발생하는 모든 다른 스팬들은 그 루트 스팬 아래에 중첩되어(nested) 나타나게 됩니다.

Next.js는 기본적으로 출력되는 것보다 더 많은 스팬들을 내부적으로 추적하고 있어요.
더 많은 상세 스팬들을 확인하고 싶다면 환경 변수에 NEXT_OTEL_VERBOSE=1을 설정해주시면 됩니다.

💡 강사의 팁:
배포하기 전에 로컬 환경에서 트레이싱이 잘 넘어가는지 눈으로 직접 확인하는 습관을 들이세요! 막상 운영 서버에 배포해 놓고 데이터가 안 들어오면, 인프라 문제인지 내 코드 문제인지 디버깅하기 정말 난감해집니다.

배포하기 (Deployment)

OpenTelemetry Collector 사용하기

OpenTelemetry Collector를 사용하여 배포할 때는, @vercel/otel을 사용하면 됩니다.
이는 Vercel에 배포할 때나 직접 서버를 호스팅(self-hosted)할 때 모두 잘 작동합니다.

Vercel에 배포하기

우리는 OpenTelemetry가 Vercel 환경에서 별다른 설정 없이(out of the box) 완벽하게 작동하도록 만들었습니다.

프로젝트를 옵저버빌리티 제공자와 연결하려면 Vercel 문서(Vercel documentation)를 따라주세요.

직접 호스팅하기 (Self-hosting)

다른 플랫폼에 배포하는 것도 아주 직관적입니다. Next.js 앱에서 쏘아 보내는 텔레메트리 데이터를 받아서 처리해 줄 여러분만의 OpenTelemetry Collector를 띄우기만 하면 됩니다.

이를 위해서는 OpenTelemetry Collector 시작하기 가이드(Getting Started guide)를 따라 해보세요. 수집기를 설정하고 Next.js 앱의 데이터를 받도록 구성하는 과정을 차근차근 알려줄 거예요.

수집기가 정상적으로 실행되었다면, 선택한 플랫폼의 배포 가이드에 따라 여러분의 Next.js 앱을 배포하시면 됩니다.

커스텀 Exporters (Custom Exporters)

반드시 OpenTelemetry Collector가 필요한 것은 아니에요. @vercel/otel을 사용할 때수동 OpenTelemetry 설정을 할 때, 여러분만의 커스텀 OpenTelemetry 익스포터를 연결해서 사용할 수도 있습니다.

커스텀 스팬 만들기 (Custom Spans)

OpenTelemetry API를 활용하면 여러분이 원하는 커스텀 스팬을 직접 추가할 수 있어요.

pnpm add @opentelemetry/api
npm install @opentelemetry/api
yarn add @opentelemetry/api
bun add @opentelemetry/api

다음 예제는 GitHub 스타(stars)를 가져오는 함수에 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 함수는 새로운 환경에서 코드가 실행되기 전에 제일 먼저 실행될 거예요.
이후에 이렇게 새로운 스팬을 생성하기 시작하면, 내보내지는 트레이스에 아주 잘 추가되는 것을 확인할 수 있을 겁니다.

💡 강사의 팁:
커스텀 스팬은 정말 병목(bottleneck)이 의심되는 곳에만 전략적으로 추가하세요! 외부 결제 모듈 API를 호출하거나, 복잡한 데이터베이스 쿼리를 실행하는 함수 같은 곳 말이죠. 너무 남발하면 모니터링 비용도 늘어나고 데이터가 지저분해져서 분석하기 힘들어져요.

Next.js의 기본 스팬들 (Default Spans in Next.js)

Next.js는 애플리케이션의 성능에 대한 유용한 통찰력을 제공하기 위해 자동으로 여러 가지 스팬들을 계측해 줍니다. 일일이 코드를 짜지 않아도 알아서 해주는 거죠.

이 스팬들에 붙는 속성(Attributes)들은 OpenTelemetry 시맨틱 컨벤션(semantic conventions)을 따르고 있어요. 게다가 우리는 next 네임스페이스 아래에 몇 가지 고유한 속성들도 추가해 두었답니다:

  • next.span_name - 스팬의 이름과 중복되는 값입니다.
  • next.span_type - 각 스팬 타입이 가지는 고유한 식별자예요.
  • next.route - 요청의 라우트 패턴을 뜻합니다 (예: /[param]/user).
  • next.rsc (true/false) - prefetch 등과 같이 이 요청이 RSC(React Server Components) 요청인지 여부를 알려줍니다.
  • next.page
    • 이건 앱 라우터(App Router)가 내부적으로 사용하는 값이에요.
    • 특수한 파일들(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 애플리케이션으로 들어오는 모든 요청에 대한 최상위 루트 스팬(root span) 역할을 합니다. 요청의 HTTP 메서드, 라우트, 타겟(target), 상태 코드를 추적해요.

속성(Attributes):

render route (app) [next.route]

  • next.span_type: AppRender.getBodyResult.

이 스팬은 앱 라우터(App Router) 환경에서 하나의 라우트를 렌더링하는 과정을 나타냅니다.

속성(Attributes):

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

fetch [http.method] [http.url]

  • next.span_type: AppRender.fetch

이 스팬은 여러분의 코드에서 실행된 fetch 요청을 나타냅니다. (서버 사이드에서 외부 API를 호출할 때 아주 유용하겠죠!)

속성(Attributes):

환경 변수에서 NEXT_OTEL_FETCH_DISABLED=1로 설정하면 이 스팬을 끌 수 있어요. 커스텀 fetch 계측 라이브러리를 따로 사용하고 싶을 때 유용하답니다.

executing api route (app) [next.route]

  • next.span_type: AppRouteRouteHandlers.runHandler.

이 스팬은 앱 라우터에서 API 라우트 핸들러(Route Handler)가 실행되는 것을 나타냅니다.

속성(Attributes):

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

getServerSideProps [next.route]

  • next.span_type: Render.getServerSideProps.

특정 라우트에서 getServerSideProps가 실행되는 것을 나타내는 스팬입니다. (Pages Router 사용자분들께 익숙한 함수죠!)

속성(Attributes):

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

getStaticProps [next.route]

  • next.span_type: Render.getStaticProps.

특정 라우트에서 getStaticProps가 실행되는 것을 나타내는 스팬입니다.

속성(Attributes):

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

render route (pages) [next.route]

  • next.span_type: Render.renderDocument.

페이지 라우터(Pages Router) 환경에서 특정 라우트의 문서를 렌더링하는 과정을 나타냅니다.

속성(Attributes):

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

generateMetadata [next.page]

  • next.span_type: ResolveMetadata.generateMetadata.

특정 페이지에 대한 메타데이터(SEO 정보 등)를 생성하는 과정을 나타내는 스팬입니다. (참고로 하나의 라우트에서 이 스팬이 여러 개 생성될 수도 있어요.)

속성(Attributes):

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

resolve page components

  • next.span_type: NextNodeServer.findPageComponents.

특정 페이지를 렌더링하기 위해 필요한 페이지 컴포넌트들을 찾아내고 해석(resolve)하는 과정을 나타냅니다.

속성(Attributes):

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

resolve segment modules

  • next.span_type: NextNodeServer.getLayoutOrPageModule.

레이아웃이나 페이지를 위한 코드 모듈들을 불러오는(loading) 과정을 나타냅니다.

속성(Attributes):

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

start response

  • next.span_type: NextNodeServer.startResponse.

이 스팬은 응답의 첫 번째 바이트(first byte)가 전송되는 순간의 시간을 나타내는 '길이가 0인(zero-length)' 스팬입니다. 보통 TTFB(Time To First Byte)를 측정하는 데 유용한 기준점이 됩니다.


모든 문서의 논리적 개요를 보고 싶으시다면 /docs/sitemap.md를 확인해주세요.

사용 가능한 모든 문서의 전체 인덱스는 /docs/llms.txt에서 확인할 수 있습니다.

profile
프론트에_가까운_풀스택_개발자

0개의 댓글