예제
Sophonz Node.js SDK로 Express, Koa, Fastify, NestJS 서버를 계측하는 완전한 실행 예제 — 초기화 순서, 요청 보강기, 로깅, 에러 핸들러.
지원하는 네 가지 프레임워크의 엔드투엔드 엔트리포인트로, 공식 sophonz-express-server 레퍼런스 앱을 본떴습니다. 가장 중요한 규칙은 순서입니다. 계측 대상 모듈(express, koa, fastify, @nestjs/core, winston 등)이 로드되기 전에 SDK를 초기화하세요. 모든 예제는 파일 최상단에서 init()을 호출하고 앱을 동적 import()로 지연 로딩해 이를 보장합니다. 덕분에 그냥 node main.js만으로 충분하며 -r/프리로드 파일이 필요 없습니다.
CAUTION — 가장 먼저 초기화
계측 대상 라이브러리가 init() 실행 전에 import되면 자동 계측이 일부를 놓칠 수 있습니다. init()을 프로세스에서 가장 먼저 실행하고 나머지는 그 뒤에 로드하세요(bootstrap() 안의 동적 import()가 이를 보장하는 가장 간단한 방법입니다).
전체 서버 엔트리포인트
프레임워크를 선택하세요. 각 탭은 node main.js(또는 bun main.js)로 바로 실행할 수 있는 완전한 main.ts이며 프리로드 플래그가 필요 없습니다.
// main.ts
import { enableDebugPayloadExporters, init } from '@sophonz/node-sdk';
// 1. 계측 대상 모듈이 로드되기 전에 SDK를 초기화합니다.
process.env.OTEL_EXPORTER_OTLP_ENDPOINT =
process.env.OTEL_EXPORTER_OTLP_ENDPOINT ?? 'https://in-otel.sophonz.com';
process.env.OTEL_PROPAGATORS =
process.env.OTEL_PROPAGATORS ?? 'tracecontext,baggage';
init({
service: process.env.OTEL_SERVICE_NAME ?? 'sophonz-express-server',
apiKey: process.env.SOPHONZ_API_KEY,
consoleCapture: true,
betaMode: true,
advancedNetworkCapture: true,
experimentalExceptionCapture: true,
});
if (process.env.SOPHONZ_DEBUG_PAYLOAD === 'true') {
void enableDebugPayloadExporters();
}
// 2. SDK 초기화 이후에만 앱을 부팅합니다.
async function bootstrap(): Promise<void> {
const { default: express } = await import('express');
const { default: winston } = await import('winston');
const {
getWinstonTransport,
setupExpressErrorHandler,
sophonzExpressMiddleware,
} = await import('@sophonz/node-sdk');
const PORT = parseInt(process.env.PORT ?? '7788', 10);
const logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
transports: [
new winston.transports.Console(),
getWinstonTransport('info', { detectResource: true }),
],
});
const app = express();
app.use(express.json());
// 모든 요청 스팬에 baggage, 클라이언트 IP, user-agent 추가.
app.use(sophonzExpressMiddleware());
app.get('/health', (_req, res) => {
res.json({ status: 'ok', uptime: process.uptime() });
});
app.get('/api/users/:id', (req, res) => {
logger.info('GET /api/users/:id', { id: req.params.id });
res.json({ id: Number(req.params.id), name: 'Alice' });
});
app.get('/api/error', () => {
throw new Error('Intentional error from /api/error');
});
// 상태 코드 >= 500인 오류를 기록. 라우트 뒤에 등록.
setupExpressErrorHandler(app);
// 선택: 직접 작성한 폴백 핸들러.
app.use((err, _req, res, next) => {
logger.error('Unhandled error', { message: err.message });
if (res.headersSent) return next(err);
res.status(500).json({ error: err.message });
});
app.listen(PORT, () => {
logger.info(`listening on http://localhost:${PORT}`);
});
}
void bootstrap();NOTE — 서비스 간 baggage 전파
OTEL_PROPAGATORS=tracecontext,baggage를 설정하면, 들어오는 baggage 항목이 보강기에 의해 활성 스팬과 트레이스 컨텍스트에 baggage. 접두사로 복사됩니다. 호출자별 메타데이터(예: 브라우저 SDK가 설정한 값)가 백엔드 스팬으로 흘러드는 방식입니다. service.name은 시작 시 고정되는 리소스 속성이라 각 서비스가 자신의 값을 유지하며, 트레이스는 서비스 이름 공유가 아니라 W3C traceparent 헤더로 연결됩니다.