@naverpay/prometheus-koa
v2.0.0
Published
Koa middleware for Prometheus metrics integration.
Readme
@naverpay/prometheus-koa
Koa.js 애플리케이션을 위한 Prometheus 메트릭 수집 미들웨어 및 라우터입니다.
설치
npm install @naverpay/prometheus-koa주요 기능
🎯 HTTP 메트릭 수집
- 요청 지속시간: 모든 HTTP 요청의 응답 시간 측정
- 자동 라벨링: 상태 코드, HTTP 메서드, 경로 자동 수집
- 경로 정규화: 동적 라우트를 패턴으로 그룹화
🚦 스마트 필터링
- 자동 바이패스: 헬스체크, 메트릭 엔드포인트 등 자동 제외
- 커스텀 필터: 사용자 정의 바이패스 로직 지원
- Next.js 지원: Next.js 정적 파일 및 라우트 자동 처리
📊 API 트레이싱
- 전용 API 메트릭: API 엔드포인트 전용 세밀한 모니터링
- 커스텀 상태 처리: 비즈니스 로직 기반 상태 분류
- 404 핸들링: API 미매칭 요청 자동 처리
🔧 유연한 설정
- 경로 정규화: 커스텀 경로 그룹화 함수
- 상태 코드 포맷팅: 사용자 정의 상태 분류
- PM2 통합: 클러스터 환경에서 메트릭 집계
빠른 시작
기본 설정 (Standalone 모드)
import Koa from 'koa'
import { createKoaPrometheusExporter } from '@naverpay/prometheus-koa'
const app = new Koa()
// 단일 프로세스 환경 (Docker, K8s 등)
const { middleware, router, disconnect } = await createKoaPrometheusExporter({
metricsPath: '/metrics',
collectDefaultMetrics: true,
})
// 미들웨어 등록 (모든 라우터보다 먼저 등록)
app.use(middleware)
// 다른 라우터들...
app.use(someOtherRouter.routes())
// 메트릭 라우터 (가장 마지막에 등록)
app.use(router.routes())
const server = app.listen(3000)
// 서버 종료 시 정리
process.on('SIGTERM', async () => {
await disconnect()
server.close()
})PM2 클러스터 모드
const { middleware, router, disconnect } = await createKoaPrometheusExporter({
pm2: true, // PM2 클러스터 메트릭 집계 활성화
metricsPath: '/metrics',
})
app.use(middleware)
app.use(router.routes())
process.on('SIGTERM', async () => {
await disconnect()
})메트릭 수집 비활성화 (개발 환경)
const { middleware, router } = await createKoaPrometheusExporter({
enabled: process.env.NODE_ENV === 'production',
metricsPath: '/metrics',
})
// enabled: false일 경우 noop 미들웨어/라우터 반환
app.use(middleware)
app.use(router.routes())API 참조
createKoaPrometheusExporter(options)
Koa Prometheus 익스포터를 생성합니다.
옵션
interface KoaPrometheusExporterOptions {
/** 메트릭 수집 활성화 여부 (기본값: true) */
enabled?: boolean
/** PM2 클러스터링 지원 (기본값: false) */
pm2?: boolean
/** Next.js 라우트 정규화 활성화 */
nextjs?: boolean
/** 메트릭 엔드포인트 경로 */
metricsPath?: string
/** 기본 Node.js 메트릭 수집 (기본값: true) */
collectDefaultMetrics?: boolean
/** 요청 바이패스 함수 */
bypass?: (context: Context) => boolean
/** 경로 정규화 함수 */
normalizePath?: (context: Context) => string
/** 상태 코드 포맷팅 함수 */
formatStatusCode?: (context: Context) => string
}반환값
{
/** 메트릭 수집 미들웨어 */
middleware: Middleware
/** 메트릭 엔드포인트 라우터 */
router: Router
/** 연결 해제 함수 */
disconnect: () => Promise<void>
}API 트레이싱
createKoaApiTraceMiddleware(options?)
API 전용 트레이싱 미들웨어를 생성합니다.
import { createKoaApiTraceMiddleware } from '@naverpay/prometheus-koa'
const { startApiTrace, writeApiTrace, endApiTrace } = createKoaApiTraceMiddleware({
normalizePath: (context) => {
// API 경로 정규화 로직
return context.path.replace(/\/api\/v\d+/, '/api/v*')
}
})
// API 라우터에서 사용
const apiRouter = new Router({ prefix: '/api' })
// 모든 API 요청에 트레이싱 시작
apiRouter.use(startApiTrace)
// API 라우트들...
apiRouter.get('/users/:id', async (context) => {
// 비즈니스 로직...
context.status = 200
context.body = { user: 'data' }
// 메트릭 기록
writeApiTrace(context)
})
// 404 처리 (매칭되지 않은 API 요청)
apiRouter.all('(.*)', endApiTrace)라우터 유틸리티
createNormalizedKoaRouterPath(router, prefix?)
Koa 라우터의 경로를 정규화하는 함수를 생성합니다.
import Router from '@koa/router'
import { createNormalizedKoaRouterPath } from '@naverpay/prometheus-koa'
const router = new Router()
router.get('/users/:id', handler)
router.get('/posts/:id/comments', handler)
const normalizeUrl = createNormalizedKoaRouterPath(router)
console.log(normalizeUrl('/users/123')) // '/users/:id'
console.log(normalizeUrl('/posts/456/comments')) // '/posts/:id/comments'고급 설정
커스텀 바이패스
const { middleware } = await createKoaPrometheusExporter({
pm2: true,
bypass: (context) => {
// 특정 헤더가 있는 요청 제외
if (context.get('X-Health-Check')) return true
// 특정 User-Agent 제외
if (context.get('User-Agent')?.includes('monitoring')) return true
// 개발 환경에서 webpack 요청 제외
if (process.env.NODE_ENV === 'development' &&
context.path.startsWith('/__webpack')) return true
return false
}
})동적 경로 정규화
const { middleware } = await createKoaPrometheusExporter({
pm2: true,
normalizePath: (context) => {
let path = context.path
// UUID 패턴을 :id로 변경
path = path.replace(/\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi, '/:id')
// 숫자 ID를 :id로 변경
path = path.replace(/\/\d+/g, '/:id')
// 파일 확장자를 :file로 변경
path = path.replace(/\/[^/]+\.(jpg|png|gif|css|js)$/i, '/:file')
return path
}
})비즈니스 로직 기반 상태 분류
const { middleware } = await createKoaPrometheusExporter({
pm2: true,
formatStatusCode: (context) => {
// 비즈니스 에러와 시스템 에러 분리
if (context.status >= 500) return 'system_error'
if (context.status >= 400) return 'client_error'
if (context.status >= 300) return 'redirect'
// 성공 응답을 세분화
if (context.status === 201) return 'created'
if (context.status === 204) return 'no_content'
return 'success'
}
})API 전용 모니터링
import Router from '@koa/router'
import { createKoaApiTraceMiddleware } from '@naverpay/prometheus-koa'
const apiRouter = new Router({ prefix: '/api/v1' })
const { startApiTrace, writeApiTrace, endApiTrace } = createKoaApiTraceMiddleware({
normalizePath: (context) => {
// API 버전 정규화
return context.path.replace(/\/api\/v\d+/, '/api/v*')
}
})
// 모든 API 요청에 트레이싱 적용
apiRouter.use(startApiTrace)
// 각 엔드포인트에서 메트릭 기록
apiRouter.get('/users', async (context) => {
try {
const users = await getUserList()
context.body = users
context.status = 200
} catch (error) {
context.status = 500
context.body = { error: 'Internal Server Error' }
}
writeApiTrace(context)
})
// 404 핸들링
apiRouter.all('(.*)', endApiTrace)수집되는 메트릭
HTTP 요청 메트릭 (http_request_duration_seconds)
모든 HTTP 요청의 응답 시간을 히스토그램으로 수집합니다.
라벨:
status_code: HTTP 상태 코드 (그룹화됨)method: HTTP 메서드 (GET, POST, etc.)path: 정규화된 요청 경로
API 요청 메트릭 (http_api_request_duration_seconds)
API 트레이싱 미들웨어가 활성화된 경우 수집됩니다.
라벨:
path: 정규화된 API 경로status: 응답 상태method: HTTP 메서드
서비스 상태 메트릭 (up)
서비스 가용성을 나타내는 게이지 메트릭입니다.
1: 서비스 실행 중0: 서비스 중단
모범 사례
1. 미들웨어 순서
const app = new Koa()
// 1. 메트릭 미들웨어 (가장 먼저)
app.use(metricsMiddleware)
// 2. 에러 핸들러
app.use(errorHandler)
// 3. 로깅 미들웨어
app.use(loggingMiddleware)
// 4. 비즈니스 로직 라우터들
app.use(apiRouter.routes())
app.use(webRouter.routes())
// 5. 메트릭 엔드포인트 라우터 (가장 마지막)
app.use(metricsRouter.routes())2. 카디널리티 관리
// 좋은 예: 제한된 값들
const { middleware } = await createKoaPrometheusExporter({
pm2: true,
normalizePath: (context) => {
// 동적 ID를 파라미터로 변경
return context.path.replace(/\/\d+/g, '/:id')
},
formatStatusCode: (context) => {
// 상태 코드를 그룹화
if (context.status >= 500) return 'error'
if (context.status >= 400) return 'client_error'
return 'success'
}
})
// 나쁜 예: 무제한 값들
const badConfig = {
normalizePath: (context) => {
return context.path // 모든 고유 경로를 개별 메트릭으로 생성
},
formatStatusCode: (context) => {
return context.status.toString() // 모든 상태 코드를 개별적으로
}
}3. 성능 최적화
const { middleware } = await createKoaPrometheusExporter({
pm2: true,
bypass: (context) => {
// 정적 파일 요청 빠르게 제외
if (context.path.includes('.')) return true
// 자주 호출되는 헬스체크 제외
if (context.path === '/health') return true
return false
}
})문제 해결
PM2 연결 실패
// PM2 클러스터 환경에서만 pm2: true로 설정
const { middleware } = await createKoaPrometheusExporter({
pm2: process.env.PM2_HOME !== undefined,
// 다른 설정들...
})개발 환경에서 메트릭 비활성화
const { middleware } = await createKoaPrometheusExporter({
enabled: process.env.NODE_ENV === 'production',
})메트릭 엔드포인트 접근 불가
// 메트릭 라우터가 다른 라우터보다 뒤에 위치해야 함
app.use(businessRouter.routes())
app.use(metricsRouter.routes()) // 마지막에 추가높은 카디널리티 경고
메트릭 라벨의 고유 조합이 너무 많으면 메모리 사용량이 증가합니다. 경로 정규화를 통해 해결하세요.
요구 사항
- Node.js 16.0.0 이상
- TypeScript 4.5 이상
- Koa 2.0.0 이상
라이센스
MIT License
