예제

App Router 프로젝트에 @sophonz/nextjs를 붙인 완전한 예제 — 최소 구성, Route Handler와 Server Action의 속성 추가, 데이터베이스 쿼리 트레이싱.

아래 예제는 모두 App Router 기준이며 그대로 복사해 동작합니다. 각 파일이 왜 그 위치에 있어야 하는지는 설치에서 다루므로, 여기서는 완성된 형태만 보입니다.

최소 구성

새 프로젝트에 필요한 파일 넷입니다. 이보다 줄일 수 없습니다.

.env
NEXT_PUBLIC_SOPHONZ_COLLECTOR_URL=https://in.sophonz.ai
NEXT_PUBLIC_SOPHONZ_APP_NAME=my-app
NEXT_PUBLIC_SOPHONZ_APP_KEY=app-key-replace-me
NEXT_PUBLIC_SOPHONZ_PROJECT=my-project
NEXT_PUBLIC_SOPHONZ_ENVIRONMENT=production
instrumentation.ts
export { register } from '@sophonz/nextjs/server';
app/layout.tsx
import { SophonzProvider } from '@sophonz/nextjs/client';
 
export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="ko">
      <body>
        <SophonzProvider>{children}</SophonzProvider>
      </body>
    </html>
  );
}
next.config.js
module.exports = {
  serverExternalPackages: ['@sophonz/nextjs', '@sophonz/node-sdk'],
};

instrumentation.ts는 프로젝트 루트에 둡니다. app/ 안이 아닙니다. src/ 디렉터리를 쓰는 프로젝트라면 src/instrumentation.ts입니다.

코드로 설정하기

환경 변수 대신 값을 코드에 두는 구성입니다. 서버와 클라이언트가 각각 설정을 받습니다.

instrumentation.ts
import { register as sophonz } from '@sophonz/nextjs/server';
 
export const register = () =>
  sophonz({
    appName: 'my-app',
    appKey: process.env.SOPHONZ_APP_KEY,
    project: 'my-project',
    deploymentEnvironment: process.env.NODE_ENV,
    server: {
      advancedNetworkCapture: true,
    },
  });
app/layout.tsx
import { SophonzProvider } from '@sophonz/nextjs/client';
 
export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="ko">
      <body>
        <SophonzProvider
          appName="my-app"
          appKey={process.env.NEXT_PUBLIC_SOPHONZ_APP_KEY}
          project="my-project"
        >
          {children}
        </SophonzProvider>
      </body>
    </html>
  );
}

클라이언트 쪽 값은 브라우저 번들에 들어가야 하므로 NEXT_PUBLIC_ 접두사가 붙은 변수에서 읽어야 합니다. 서버 쪽은 접두사 없는 변수를 그대로 쓸 수 있습니다.

시작 여부 확인하기

register()는 결과를 돌려줍니다. 배포 후 텔레메트리가 오지 않을 때 원인을 좁히는 데 씁니다.

instrumentation.ts
import { register as sophonz } from '@sophonz/nextjs/server';
 
export async function register() {
  const result = await sophonz({ appName: 'my-app' });
 
  if (!result.started) {
    // 'edge-runtime'은 정상입니다. Edge 런타임에서는 서버 SDK가 동작하지
    // 않으며, 이 분기는 번들에서 제거됩니다.
    if (result.reason !== 'edge-runtime') {
      console.warn('[sophonz] 시작하지 않음:', result.reason, result.missing);
    }
  }
}

reasonedge-runtime, already-started, incomplete-config 중 하나입니다. 마지막 경우 missing에 빠진 필드 이름이 들어 있습니다.

Route Handler에 속성 추가하기

Next.js에는 미들웨어 체인이 없어 Express·Koa·Fastify 헬퍼를 쓸 수 없습니다. 대신 핸들러 안에서 직접 속성을 붙입니다.

app/api/orders/[id]/route.ts
import { setTraceAttributes } from '@sophonz/node-sdk';
 
export async function GET(
  request: Request,
  { params }: { params: Promise<{ id: string }> },
) {
  const { id } = await params;
 
  // 이 요청의 활성 스팬에 붙습니다. 트레이스 목록에서 주문 번호나 사용자로
  // 필터링할 수 있게 됩니다.
  setTraceAttributes({
    'order.id': id,
    'user.id': request.headers.get('x-user-id') ?? 'anonymous',
  });
 
  const order = await loadOrder(id);
  return Response.json(order);
}

이 호출은 server.betaMode가 켜져 있을 때만 활성 스팬에 반영됩니다. 이 패키지가 기본값으로 켜므로 별도 설정은 필요 없습니다.

Server Action

Server Action도 같은 방식입니다. 액션 하나를 스팬으로 감싸면 폼 제출이 트레이스에 개별 구간으로 남습니다.

app/actions.ts
'use server';
 
import { trace, setTraceAttributes } from '@sophonz/node-sdk';
 
export async function submitOrder(formData: FormData) {
  setTraceAttributes({ 'action.name': 'submitOrder' });
 
  // SDK는 OpenTelemetry API를 그대로 다시 내보냅니다. 직접 만드는 스팬은
  // 표준 API로 만들면 되고, 별도의 래퍼가 없습니다.
  return trace.getTracer('app').startActiveSpan('submitOrder', async (span) => {
    try {
      const order = await createOrder(formData);
      await sendConfirmation(order);
      return order.id;
    } finally {
      span.end();
    }
  });
}

데이터베이스 쿼리 트레이싱

브라우저에서 시작된 트레이스를 데이터베이스 실행계획까지 잇습니다. instrumentation.ts에서 SQLCommenter를 켜는 것 외에 애플리케이션 코드는 그대로입니다.

instrumentation.ts
import { register as sophonz } from '@sophonz/nextjs/server';
 
export const register = () =>
  sophonz({
    appName: 'my-app',
    server: {
      instrumentations: {
        // 모든 문장에 W3C traceparent를 주석으로 덧붙입니다. 이 설정이
        // 없으면 트레이스는 pg 클라이언트에서 끝납니다.
        '@opentelemetry/instrumentation-pg': {
          addSqlCommenterCommentToQueries: true,
        },
      },
    },
  });

요청 하나가 만드는 트레이스입니다.

스팬깊이생성 주체
화면 전환1브라우저 SDK
GET /api/orders/1232Next.js 서버 계측
pg.query3pg 계측
Planner · ExecutorRun4pg_tracing

마지막 줄은 데이터베이스에 pg_tracing 확장이 설치된 경우에만 나타납니다. 설치는 자체 운영 PostgreSQL, 확장을 설치할 수 없는 환경은 클라우드 관리형 PostgreSQL을 참고하세요.

계측 일부 끄기

serverbrowser는 각각 하위 SDK로 그대로 전달됩니다. 노이즈가 많은 계측을 끄는 예입니다.

instrumentation.ts
import { register as sophonz } from '@sophonz/nextjs/server';
 
export const register = () =>
  sophonz({
    appName: 'my-app',
    server: {
      instrumentations: {
        '@opentelemetry/instrumentation-fs': { enabled: false },
      },
    },
  });

전체 옵션 목록은 Node.js SDK 설정Browser SDK 설정에 있습니다.

확인

개발 서버를 띄우고 화면을 몇 번 이동한 뒤 대시보드에서 트레이스를 찾습니다. 브라우저와 서버가 한 트레이스에 함께 나와야 합니다.

서버 쪽만 비어 있다면 serverExternalPackages 설정을 먼저 확인하세요. 번들에 인라인된 모듈은 런타임에 require되지 않아 계측 훅이 놓칩니다.