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 모니터링과 로깅의 주요 구성 요소:

  1. 로깅 시스템

    • 구조화된 로깅
    • 다중 전송 대상
    • 로그 레벨 관리
  2. 성능 모니터링

    • 메트릭 수집
    • 프로메테우스 통합
    • 리소스 사용량 추적
  3. 분산 추적

    • 요청 추적
    • 스팬 관리
    • 컨텍스트 전파
  4. 헬스 체크

    • 종속성 모니터링
    • 시스템 상태 확인
    • 자동 복구
  5. 알림 시스템

    • 다중 채널 알림
    • 임계값 기반 알림
    • 중복 알림 방지

results matching ""

    No results matching ""