미들웨어
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를 받습니다.
| 옵션 | 타입 | 기본값 | 설명 |
|---|---|---|---|
captureBaggage | boolean | true | 모든 OTel baggage 항목을 baggage. 접두사로 스팬과 가변 컨텍스트에 복사. |
captureClientIp | boolean | true | 클라이언트 IP를 http.client.ip로 캡처. |
captureUserAgent | boolean | true | user-agent 헤더를 http.user_agent로 캡처. |
attributes | Attributes | ((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 레퍼런스를 참고하세요.