Node.js 인터뷰 질문 98
질문: Node.js 애플리케이션의 모니터링과 로깅을 구현하는 방법과 주요 도구들에 대해 설명해주세요.
답변:
Node.js 애플리케이션의 모니터링과 로깅 구현 방법을 살펴보겠습니다.
1. 애플리케이션 로깅 구현
// logger.js
const winston = require("winston");
const { createLogger, format, transports } = winston;
const { combine, timestamp, label, printf, colorize } = format;
class ApplicationLogger {
constructor() {
this.logger = createLogger({
level: process.env.LOG_LEVEL || "info",
format: combine(
label({ label: "APP" }),
timestamp(),
this.customFormat()
),
transports: [
// 콘솔 로그
new transports.Console({
format: combine(colorize(), this.customFormat()),
}),
// 파일 로그
new transports.File({
filename: "error.log",
level: "error",
}),
new transports.File({
filename: "combined.log",
}),
],
});
// Elasticsearch 전송 (선택사항)
if (process.env.ELASTICSEARCH_URL) {
const elasticsearch = require("winston-elasticsearch");
this.logger.add(
new elasticsearch({
level: "info",
clientOpts: { node: process.env.ELASTICSEARCH_URL },
})
);
}
}
customFormat() {
return printf(({ level, message, label, timestamp }) => {
return `${timestamp} [${label}] ${level}: ${message}`;
});
}
// 구조화된 로깅
logRequest(req, res, responseTime) {
this.logger.info("HTTP 요청", {
method: req.method,
url: req.url,
status: res.statusCode,
responseTime,
userAgent: req.get("user-agent"),
ip: req.ip,
});
}
// 에러 로깅
logError(error, context = {}) {
this.logger.error("에러 발생", {
error: {
message: error.message,
stack: error.stack,
},
...context,
});
}
// 성능 메트릭 로깅
logPerformance(metric) {
this.logger.info("성능 메트릭", {
...metric,
timestamp: Date.now(),
});
}
}
module.exports = new ApplicationLogger();
2. 성능 모니터링 구현
// performance-monitor.js
const promClient = require("prom-client");
const os = require("os");
class PerformanceMonitor {
constructor() {
// 메트릭 레지스트리 초기화
this.register = new promClient.Registry();
promClient.collectDefaultMetrics({ register: this.register });
// 사용자 정의 메트릭 설정
this.setupMetrics();
}
setupMetrics() {
// HTTP 요청 카운터
this.httpRequestsTotal = new promClient.Counter({
name: "http_requests_total",
help: "전체 HTTP 요청 수",
labelNames: ["method", "path", "status"],
});
// 응답 시간 히스토그램
this.httpRequestDuration = new promClient.Histogram({
name: "http_request_duration_seconds",
help: "HTTP 요청 처리 시간",
labelNames: ["method", "path"],
buckets: [0.1, 0.5, 1, 2, 5],
});
// 메모리 사용량 게이지
this.memoryUsage = new promClient.Gauge({
name: "node_memory_usage_bytes",
help: "메모리 사용량",
});
// 활성 연결 수 게이지
this.activeConnections = new promClient.Gauge({
name: "active_connections",
help: "활성 연결 수",
});
// 메트릭 등록
this.register.registerMetric(this.httpRequestsTotal);
this.register.registerMetric(this.httpRequestDuration);
this.register.registerMetric(this.memoryUsage);
this.register.registerMetric(this.activeConnections);
}
// 미들웨어 설정
middleware() {
return (req, res, next) => {
const start = process.hrtime();
// 응답 완료 시 메트릭 기록
res.on("finish", () => {
const duration = process.hrtime(start);
const durationSeconds = duration[0] + duration[1] / 1e9;
this.httpRequestsTotal.inc({
method: req.method,
path: req.path,
status: res.statusCode,
});
this.httpRequestDuration.observe(
{
method: req.method,
path: req.path,
},
durationSeconds
);
});
next();
};
}
// 시스템 메트릭 수집
collectSystemMetrics() {
setInterval(() => {
const memoryUsage = process.memoryUsage();
this.memoryUsage.set(memoryUsage.heapUsed);
const loadAvg = os.loadavg();
this.register.getSingleMetric("system_load").set(loadAvg[0]);
}, 5000);
}
// 메트릭 엔드포인트
metricsEndpoint(req, res) {
res.set("Content-Type", this.register.contentType);
this.register.metrics().then((data) => res.send(data));
}
}
3. 분산 추적 구현
// tracer.js
const opentelemetry = require("@opentelemetry/api");
const { NodeTracerProvider } = require("@opentelemetry/node");
const { SimpleSpanProcessor } = require("@opentelemetry/tracing");
const { JaegerExporter } = require("@opentelemetry/exporter-jaeger");
class DistributedTracer {
constructor() {
this.provider = new NodeTracerProvider();
// Jaeger 익스포터 설정
const exporter = new JaegerExporter({
serviceName: process.env.SERVICE_NAME,
agent: {
host: process.env.JAEGER_AGENT_HOST,
port: parseInt(process.env.JAEGER_AGENT_PORT),
},
});
this.provider.addSpanProcessor(new SimpleSpanProcessor(exporter));
// 전역 프로바이더로 등록
this.provider.register();
this.tracer = opentelemetry.trace.getTracer(process.env.SERVICE_NAME);
}
// 요청 추적 미들웨어
middleware() {
return (req, res, next) => {
const span = this.tracer.startSpan(`${req.method} ${req.path}`);
// 요청 정보 기록
span.setAttribute("http.method", req.method);
span.setAttribute("http.url", req.url);
span.setAttribute("http.route", req.path);
// 컨텍스트 전파
const context = opentelemetry.trace.setSpan(
opentelemetry.context.active(),
span
);
res.on("finish", () => {
span.setAttribute("http.status_code", res.statusCode);
span.end();
});
return opentelemetry.context.with(context, next);
};
}
// 수동 스팬 생성
createSpan(name, fn) {
return this.tracer.startActiveSpan(name, async (span) => {
try {
const result = await fn(span);
span.end();
return result;
} catch (error) {
span.recordException(error);
span.setStatus({
code: opentelemetry.SpanStatusCode.ERROR,
message: error.message,
});
span.end();
throw error;
}
});
}
}
4. 헬스 체크 구현
// health-check.js
class HealthCheck {
constructor(dependencies) {
this.dependencies = dependencies;
}
async checkHealth() {
const results = {
status: "ok",
timestamp: new Date().toISOString(),
checks: {},
};
// 데이터베이스 연결 확인
try {
await this.checkDatabase();
results.checks.database = { status: "ok" };
} catch (error) {
results.checks.database = {
status: "error",
message: error.message,
};
results.status = "error";
}
// Redis 연결 확인
try {
await this.checkRedis();
results.checks.redis = { status: "ok" };
} catch (error) {
results.checks.redis = {
status: "error",
message: error.message,
};
results.status = "error";
}
// 외부 API 연결 확인
try {
await this.checkExternalAPIs();
results.checks.externalAPIs = { status: "ok" };
} catch (error) {
results.checks.externalAPIs = {
status: "error",
message: error.message,
};
results.status = "error";
}
// 시스템 리소스 확인
const systemHealth = this.checkSystemResources();
results.checks.system = systemHealth;
if (systemHealth.status === "warning") {
results.status = "warning";
}
return results;
}
async checkDatabase() {
await this.dependencies.db.query("SELECT 1");
}
async checkRedis() {
await this.dependencies.redis.ping();
}
async checkExternalAPIs() {
const apis = this.dependencies.externalAPIs;
await Promise.all(Object.values(apis).map((api) => api.healthCheck()));
}
checkSystemResources() {
const memoryUsage = process.memoryUsage();
const cpuUsage = process.cpuUsage();
const status = {
memory: {
heapUsed: memoryUsage.heapUsed,
heapTotal: memoryUsage.heapTotal,
rss: memoryUsage.rss,
},
cpu: {
user: cpuUsage.user,
system: cpuUsage.system,
},
};
// 경고 임계값 확인
if (memoryUsage.heapUsed / memoryUsage.heapTotal > 0.9) {
return {
status: "warning",
message: "높은 메모리 사용량",
details: status,
};
}
return {
status: "ok",
details: status,
};
}
}
5. 알림 시스템 구현
// alert-manager.js
class AlertManager {
constructor(config) {
this.config = config;
this.alertHistory = new Map();
}
async sendAlert(alert) {
const alertKey = `${alert.type}-${alert.source}`;
const lastAlert = this.alertHistory.get(alertKey);
// 중복 알림 방지
if (lastAlert && Date.now() - lastAlert < this.config.minInterval) {
return;
}
this.alertHistory.set(alertKey, Date.now());
// 알림 채널별 전송
await Promise.all([
this.sendSlackAlert(alert),
this.sendEmailAlert(alert),
this.sendPagerDutyAlert(alert),
]);
}
async sendSlackAlert(alert) {
if (!this.config.slack.webhook) return;
const message = {
text: `🚨 경고: ${alert.message}`,
attachments: [
{
color: this.getAlertColor(alert.severity),
fields: [
{
title: "심각도",
value: alert.severity,
short: true,
},
{
title: "소스",
value: alert.source,
short: true,
},
{
title: "세부 정보",
value: JSON.stringify(alert.details, null, 2),
},
],
},
],
};
await fetch(this.config.slack.webhook, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(message),
});
}
getAlertColor(severity) {
switch (severity) {
case "critical":
return "#ff0000";
case "warning":
return "#ffa500";
default:
return "#36a64f";
}
}
async sendEmailAlert(alert) {
if (!this.config.email.enabled) return;
const emailContent = `
<h2>시스템 경고</h2>
<p><strong>메시지:</strong> ${alert.message}</p>
<p><strong>심각도:</strong> ${alert.severity}</p>
<p><strong>소스:</strong> ${alert.source}</p>
<pre>${JSON.stringify(alert.details, null, 2)}</pre>
`;
await this.config.email.transport.sendMail({
from: this.config.email.from,
to: this.config.email.to,
subject: `[${alert.severity.toUpperCase()}] 시스템 경고`,
html: emailContent,
});
}
async sendPagerDutyAlert(alert) {
if (!this.config.pagerDuty.enabled) return;
const event = {
routing_key: this.config.pagerDuty.routingKey,
event_action: "trigger",
payload: {
summary: alert.message,
source: alert.source,
severity: alert.severity,
custom_details: alert.details,
},
};
await fetch("https://events.pagerduty.com/v2/enqueue", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(event),
});
}
}
요약
Node.js 모니터링과 로깅의 주요 구성 요소:
로깅 시스템
- 구조화된 로깅
- 다중 전송 대상
- 로그 레벨 관리
성능 모니터링
- 메트릭 수집
- 프로메테우스 통합
- 리소스 사용량 추적
분산 추적
- 요청 추적
- 스팬 관리
- 컨텍스트 전파
헬스 체크
- 종속성 모니터링
- 시스템 상태 확인
- 자동 복구
알림 시스템
- 다중 채널 알림
- 임계값 기반 알림
- 중복 알림 방지