
Security News
Axios Maintainer Confirms Social Engineering Attack Behind npm Compromise
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.
@naverpay/prometheus-next
Advanced tools
Next.js integration server with Prometheus metrics collection and PM2 cluster support.
Next.js 애플리케이션을 위한 통합 Prometheus 메트릭 수집 서버입니다.
npm install @naverpay/prometheus-next
/metrics 엔드포인트 자동 생성_next/ 및 정적 파일 요청 자동 분류[id], [...slug] 등 동적 라우트 자동 인식import { createNextServerWithMetrics } from '@naverpay/prometheus-next'
const server = await createNextServerWithMetrics({
pm2: true, // PM2 환경에서 실행
nextOptions: {
dev: process.env.NODE_ENV !== 'production',
port: 3000,
},
metricsPath: '/metrics',
collectDefaultMetrics: true,
})
server.listen(3000, () => {
console.log('Server running on http://localhost:3000')
console.log('Metrics available at http://localhost:3000/metrics')
})
// 서버 종료 시 정리
process.on('SIGTERM', () => {
server.close(() => {
process.exit(0)
})
})
import { createNextServerWithMetrics } from '@naverpay/prometheus-next'
const server = await createNextServerWithMetrics({
pm2: false, // 개발 환경에서는 PM2 비활성화
nextOptions: {
dev: true,
turbo: true, // Turbopack 사용
},
metricsPath: '/dev-metrics',
collectDefaultMetrics: false, // 개발 시 성능을 위해 비활성화
})
server.listen(3000)
import { createNextServerWithMetrics } from '@naverpay/prometheus-next'
const server = await createNextServerWithMetrics({
pm2: true,
nextOptions: {
dev: false,
// Next.js 빌드 결과를 사용
},
metricsPath: '/metrics',
collectDefaultMetrics: true,
// 프로덕션 최적화
bypass: (request, response) => {
// 헬스체크 제외
if (request.url === '/health') return true
// 로드밸런서 헬스체크 제외
const userAgent = request.headers['user-agent']
if (userAgent?.includes('ELB-HealthChecker')) return true
return false
},
// 커스텀 경로 정규화
normalizePath: (request, response) => {
let path = request.url || ''
// API 버전 정규화
path = path.replace(/\/api\/v\d+/, '/api/v*')
// 쿼리 파라미터 제거
path = path.split('?')[0] || path
return path
},
// 응답 상태 분류
formatStatusCode: (response) => {
if (response.statusCode >= 500) return 'server_error'
if (response.statusCode >= 400) return 'client_error'
if (response.statusCode >= 300) return 'redirect'
return 'success'
}
})
server.listen(process.env.PORT || 3000)
createNextServerWithMetrics(options)메트릭 수집이 통합된 Next.js 서버를 생성합니다.
interface NextjsPrometheusExporterOptions {
/** PM2 클러스터링 지원 */
pm2: boolean
/** Next.js 서버 설정 */
nextOptions: NextServerOptions & {
turbo?: boolean
turbopack?: boolean
}
/** 메트릭 엔드포인트 경로 */
metricsPath?: string
/** 기본 Node.js 메트릭 수집 (기본값: true) */
collectDefaultMetrics?: boolean
/** 요청 바이패스 함수 */
bypass?: (request: IncomingMessage, response: ServerResponse) => boolean
/** 경로 정규화 함수 */
normalizePath?: (request: IncomingMessage, response: ServerResponse) => string
/** 상태 코드 포맷팅 함수 */
formatStatusCode?: (response: ServerResponse) => string
}
/** Node.js HTTP 서버 인스턴스 */
Server
const server = await createNextServerWithMetrics({
pm2: true,
nextOptions: { dev: false },
bypass: (request, response) => {
const url = request.url || ''
const userAgent = request.headers['user-agent'] || ''
// 봇 트래픽 제외
if (/bot|crawler|spider/i.test(userAgent)) return true
// 프리플라이트 요청 제외
if (request.method === 'OPTIONS') return true
// 웹소켓 업그레이드 요청 제외
if (request.headers.upgrade === 'websocket') return true
// 특정 도메인의 요청만 수집
const host = request.headers.host
if (!host?.includes('api.example.com')) return true
return false
}
})
const server = await createNextServerWithMetrics({
pm2: true,
nextOptions: { dev: false },
normalizePath: (request, response) => {
let path = request.url?.split('?')[0] || ''
// Next.js API 라우트 정규화
if (path.startsWith('/api/')) {
// /api/users/123 -> /api/users/[id]
path = path.replace(/\/api\/users\/\d+/, '/api/users/[id]')
// /api/posts/abc-123/comments -> /api/posts/[slug]/comments
path = path.replace(/\/api\/posts\/[^/]+\/comments/, '/api/posts/[slug]/comments')
}
// 페이지 라우트는 Next.js 매니페스트 사용 (자동 처리됨)
return path
}
})
const server = await createNextServerWithMetrics({
pm2: true,
nextOptions: { dev: false },
formatStatusCode: (response) => {
const status = response.statusCode
// 서버 에러 세분화
if (status >= 500) {
if (status === 502) return 'bad_gateway'
if (status === 503) return 'service_unavailable'
if (status === 504) return 'gateway_timeout'
return 'server_error'
}
// 클라이언트 에러 세분화
if (status >= 400) {
if (status === 401) return 'unauthorized'
if (status === 403) return 'forbidden'
if (status === 404) return 'not_found'
if (status === 429) return 'rate_limited'
return 'client_error'
}
// 성공 응답 세분화
if (status >= 200 && status < 300) {
if (status === 201) return 'created'
if (status === 202) return 'accepted'
if (status === 204) return 'no_content'
return 'success'
}
return 'redirect'
}
})
Next.js의 routes-manifest.json을 활용하여 자동으로 라우트를 정규화합니다:
// 실제 요청 -> 정규화된 라벨
'/user/123' -> '/user/[id]'
'/posts/abc-def' -> '/posts/[slug]'
'/api/users/456' -> '/api/users/[id]'
'/blog/2023/12/post' -> '/blog/[...slug]'
'/_next/static/...' -> 'STATIC'
'/favicon.ico' -> 'STATIC'
개발 환경에서는 불필요한 요청들이 자동으로 제외됩니다:
// 자동으로 바이패스되는 요청들
'/_next/webpack-hmr' // 웹팩 HMR
'//double-slash' // 잘못된 URL
'/metrics' // 메트릭 엔드포인트
'/healthcheck' // 헬스체크
// ecosystem.config.js
module.exports = {
apps: [{
name: 'next-app-with-metrics',
script: './dist/server.js',
instances: 'max', // CPU 코어 수만큼 인스턴스 생성
exec_mode: 'cluster',
env: {
NODE_ENV: 'production',
PORT: 3000
},
// 메트릭 수집을 위한 설정
wait_ready: true,
listen_timeout: 10000,
}]
}
// server.ts
import { createNextServerWithMetrics } from '@naverpay/prometheus-next'
async function start() {
const server = await createNextServerWithMetrics({
pm2: true,
nextOptions: {
dev: false,
},
metricsPath: '/metrics',
collectDefaultMetrics: true,
})
const port = process.env.PORT || 3000
server.listen(port, () => {
console.log(`Server running on port ${port}`)
// PM2에게 준비 완료 신호 전송
if (process.send) {
process.send('ready')
}
})
// 우아한 종료
process.on('SIGINT', () => {
server.close(() => {
process.exit(0)
})
})
}
start().catch(console.error)
http_request_duration_seconds)모든 HTTP 요청의 응답 시간을 히스토그램으로 수집합니다.
라벨:
status_code: HTTP 상태 코드 그룹method: HTTP 메서드path: 정규화된 요청 경로예시 메트릭:
http_request_duration_seconds_bucket{status_code="200",method="GET",path="/",le="0.1"} 145
http_request_duration_seconds_bucket{status_code="200",method="GET",path="/user/[id]",le="0.5"} 23
http_request_duration_seconds_bucket{status_code="4xx",method="GET",path="/api/not-found",le="1"} 5
up)서비스 가용성을 나타내는 게이지 메트릭입니다.
up 1
collectDefaultMetrics: true일 때 다음 메트릭들이 추가로 수집됩니다:
process_cpu_user_seconds_totalprocess_cpu_system_seconds_totalprocess_resident_memory_bytesprocess_heap_bytesnodejs_eventloop_lag_secondsnodejs_gc_duration_seconds# prometheus.yml
global:
scrape_interval: 15s
scrape_configs:
- job_name: 'next-app'
static_configs:
- targets: ['localhost:3000']
metrics_path: '/metrics'
scrape_interval: 10s
주요 모니터링 지표:
http_request_duration_seconds 히스토그램rate(http_request_duration_seconds_count[5m])rate(http_request_duration_seconds_count{status_code!="success"}[5m])up// 좋은 예: 제한된 라벨 값
const server = await createNextServerWithMetrics({
pm2: true,
nextOptions: { dev: false },
normalizePath: (req) => {
// ID를 파라미터로 변경
return req.url?.replace(/\/\d+/g, '/[id]') || ''
}
})
// 나쁜 예: 무제한 라벨 값
const badServer = await createNextServerWithMetrics({
pm2: true,
nextOptions: { dev: false },
normalizePath: (req) => {
return req.url || '' // 모든 고유 URL을 개별 메트릭으로 생성
}
})
const server = await createNextServerWithMetrics({
pm2: true,
nextOptions: { dev: false },
bypass: (req) => {
// 정적 파일 요청 제외로 메모리 절약
if (req.url?.includes('.')) return true
// 빈번한 헬스체크 제외
if (req.url === '/health') return true
return false
}
})
pm2: false로 설정/metrics 경로로 접근 시 메트릭 출력 확인PM2 클러스터 환경에서는 자동으로 메트릭이 집계됩니다. 개별 프로세스별 메트릭이 필요한 경우 별도 설정이 필요합니다.
MIT License
FAQs
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.

Security News
The Axios compromise shows how time-dependent dependency resolution makes exposure harder to detect and contain.