미들웨어

Sophonz Node.js SDK로 Express, Koa, Fastify, NestJS의 요청 스팬 보강하기 — baggage, 클라이언트 IP, user-agent, 사용자 지정 속성, 에러 핸들러.

SDK는 최신 안정화 버전의 공식 @opentelemetry/api API 도구를 기반으로 한 프레임워크 요청 보강기를 제공합니다 — Express·Koa용 미들웨어, Fastify용 플러그인, NestJS용 인터셉터. 각 보강기는 OpenTelemetry baggage를 활성 스팬에 복사하고, 클라이언트 IP와 user-agent를 캡처하며, 지정한 사용자 속성을 적용한 뒤 이 모두를 가변(mutable) 트레이스 컨텍스트로 전달합니다. 네 프레임워크의 스팬은 SDK에 내장된 자동 계측이 생성하며, 보강기는 그 위에 요청 컨텍스트를 더합니다.

Express

다운스트림 스팬이 속성을 상속하도록 미들웨어를 체인의 가능한 한 앞쪽에 등록하세요.

import express from 'express';
import { sophonzExpressMiddleware } from '@sophonz/node-sdk';
 
const app = express();
 
app.use(sophonzExpressMiddleware());
 
app.get('/', (req, res) => res.send('ok'));

옵션 사용:

app.use(
  sophonzExpressMiddleware({
    captureClientIp: true,
    captureUserAgent: true,
    captureBaggage: true,
    // 모든 요청에 추가되는 정적 속성
    attributes: { 'deployment.environment': process.env.NODE_ENV ?? 'dev' },
  }),
);

함수로 요청별 속성 지정 — Express req를 인자로 받습니다.

app.use(
  sophonzExpressMiddleware({
    attributes: (req) => ({
      'http.route': (req as express.Request).route?.path,
      'enduser.id': (req as express.Request).header('x-user-id') ?? '',
    }),
  }),
);

Koa

Koa 미들웨어도 동일합니다. 라우터보다 앞에 등록하세요.

import Koa from 'koa';
import { sophonzKoaMiddleware } from '@sophonz/node-sdk';
 
const app = new Koa();
 
app.use(sophonzKoaMiddleware());

옵션 사용(attributes 함수는 Koa ctx를 받습니다):

app.use(
  sophonzKoaMiddleware({
    captureClientIp: true,
    attributes: (ctx) => ({
      'http.route': (ctx as any).routePath,
      'enduser.id': (ctx as any).get('x-user-id') ?? '',
    }),
  }),
);

Fastify

Fastify 통합은 onRequest 훅을 등록하는 플러그인입니다. 라우트보다 앞서 루트에 한 번만 등록하세요.

import Fastify from 'fastify';
import { sophonzFastifyPlugin } from '@sophonz/node-sdk';
 
const app = Fastify();
 
await app.register(sophonzFastifyPlugin());
 
app.get('/', async () => ({ status: 'ok' }));

옵션 사용(attributes 함수는 Fastify 요청 객체를 받습니다):

await app.register(
  sophonzFastifyPlugin({
    captureClientIp: true,
    attributes: (req) => ({
      'enduser.id': (req as any).headers['x-user-id'] ?? '',
    }),
  }),
);

이 플러그인은 Fastify의 캡슐화(encapsulation)를 해제하므로, 어디에 등록하든 훅이 모든 라우트에 적용됩니다.

NestJS

NestJS 통합은 NestInterceptor입니다. 전역으로 등록하세요 — Nest DI가 필요 없으며 인스턴스를 그대로 전달하면 됩니다. Express와 Fastify 플랫폼 어댑터 모두에서 동작합니다.

import { NestFactory } from '@nestjs/core';
import { SophonzNestInterceptor } from '@sophonz/node-sdk';
import { AppModule } from './app.module';
 
const app = await NestFactory.create(AppModule);
 
app.useGlobalInterceptors(new SophonzNestInterceptor());
 
await app.listen(3000);

옵션 사용(attributes 함수는 하위 플랫폼의 요청 객체를 받습니다):

app.useGlobalInterceptors(
  new SophonzNestInterceptor({
    attributes: (req) => ({
      'enduser.id': (req as any).headers['x-user-id'] ?? '',
    }),
  }),
);

new 대신 함수 호출을 선호하면 sophonzNestInterceptor(options?) 팩토리도 제공됩니다.

NOTE — 프레임워크 로드 전에 SDK를 초기화하세요

계측이 패치할 수 있도록 Express/Koa/Fastify/Nest가 로드되기 전에 init()으로 SDK를 초기화해야 합니다. 가장 간단한 방법은 엔트리포인트 맨 위에서 init()을 호출한 뒤, 나머지 앱을 동적 import()로 불러오는 것입니다. 전체 Express·Fastify·NestJS 엔트리포인트는 예제를 참고하세요.

옵션

sophonzExpressMiddleware, sophonzKoaMiddleware, sophonzFastifyPlugin, SophonzNestInterceptor는 모두 동일한 SophonzMiddlewareOptions를 받습니다.

옵션타입기본값설명
captureBaggagebooleantrue모든 OTel baggage 항목을 baggage. 접두사로 스팬과 가변 컨텍스트에 복사.
captureClientIpbooleantrue클라이언트 IP를 http.client.ip로 캡처.
captureUserAgentbooleantrueuser-agent 헤더를 http.user_agent로 캡처.
attributesAttributes | ((reqOrCtx) => Attributes)추가 속성 — 정적 객체이거나 프레임워크 req/ctx를 받는 함수.

NOTE — 텔레메트리가 요청을 막지 않습니다

미들웨어는 작업을 try/catch로 감쌉니다. 속성 보강이 어떤 이유로 실패해도 요청은 정상적으로 계속되고 오류는 무시됩니다. 텔레메트리 때문에 요청 파이프라인이 중단되는 일은 없습니다.

에러 핸들러

experimentalExceptionCapture가 활성화되면(init() 사용 시 기본값) 처리되지 않은 예외가 자동으로 캡처됩니다. 프레임워크의 에러 파이프라인에서 잡히는 오류까지 기록하려면 라우트 뒤에 해당 에러 핸들러를 등록하세요.

Express

import { setupExpressErrorHandler } from '@sophonz/node-sdk';
 
// ... app.use(routes) ...
 
setupExpressErrorHandler(app); // Express 에러 처리 미들웨어 등록

기본적으로 상태 코드가 >= 500인 오류를 기록합니다.

Koa

import { setupKoaErrorHandler } from '@sophonz/node-sdk';
 
setupKoaErrorHandler(app); // 체인을 try/catch로 감싸 throw된 오류 기록

수동 기록

recordException으로 어디서든 처리된 오류를 기록할 수 있습니다.

import { recordException } from '@sophonz/node-sdk';
 
try {
  await doWork();
} catch (err) {
  await recordException(err, {
    attributes: { 'job.name': 'nightly-sync' },
  });
}

전체 시그니처는 API 레퍼런스를 참고하세요.