← 개발 로그 목록

LearningCollector: PM2 로깅 강화

/ 18분 분량 / 개발 로그

이번 커밋은 기존 시스템의 안정성과 유지보수성을 크게 향상시키는 데 중점을 두었습니다. 요청 로깅, 에러 핸들링, 인증 및 CRUD 작업 로깅 강화 등 다양한 백엔드 기능 개선과 함께, 더 이상 사용되지 않는 컨퍼런스 관련 기능들을 깔끔하게 제거했습니다. 프론트엔드에서도 SSR 에러 로깅 구조화를 통해 디버깅 효율성을 높였습니다.

LearningCollector: PM2 로깅 강화 및 컨퍼런스 기능 제거

이번 커밋은 기존 시스템의 안정성과 유지보수성을 크게 향상시키는 데 중점을 두었습니다. 요청 로깅, 에러 핸들링, 인증 및 CRUD 작업 로깅 강화 등 다양한 백엔드 기능 개선과 함께, 더 이상 사용되지 않는 컨퍼런스 관련 기능들을 깔끔하게 제거했습니다. 프론트엔드에서도 SSR 에러 로깅 구조화를 통해 디버깅 효율성을 높였습니다.

요약

이번 작업은 "PM2 로깅 강화 및 컨퍼런스 기능 제거"라는 메시지를 통해 전달되듯, 백엔드 시스템의 전반적인 로깅 메커니즘을 개선하고 불필요한 컨퍼런스 관련 코드를 삭제하는 것을 목표로 합니다. 이를 통해 시스템의 안정성을 높이고, 문제 발생 시 신속한 원인 파악 및 해결을 지원하며, 코드 베이스를 더욱 간결하게 유지할 수 있게 되었습니다.

배경 및 목적

기존 시스템은 개발 및 운영 중에 발생할 수 있는 다양한 이슈들을 추적하고 진단하는 데 필요한 로깅 기능이 부족했습니다. 특히, 요청 처리 과정에서의 문제, 인증 관련 오류, 데이터베이스 연동 시 발생하는 문제 등을 상세하게 파악하기 어려웠습니다. 또한, 프로젝트 초기에 구현되었던 컨퍼런스 관련 기능이 현재는 더 이상 사용되지 않아 코드 베이스의 복잡성을 증가시키는 요인이 되었습니다.

이러한 배경에서 다음과 같은 목적을 가지고 이번 작업을 진행했습니다.

  • 로깅 강화: 시스템의 모든 주요 동작 (요청, 인증, CRUD, DB 작업, 에러 발생 등)에 대한 상세한 로그를 기록하여 문제 발생 시 원인 분석 및 디버깅 시간을 단축합니다.
  • 안정성 향상: 글로벌 에러 핸들링 및 프로세스 레벨 에러 핸들러를 추가하여 예상치 못한 오류 발생 시에도 시스템이 안정적으로 유지되도록 합니다.
  • 코드 베이스 간결화: 더 이상 사용되지 않는 컨퍼런스 관련 기능 코드를 제거하여 코드 베이스를 정리하고 유지보수성을 높입니다.
  • 개발 경험 개선: 프론트엔드 SSR 에러 로깅 구조화를 통해 개발자들이 더 쉽게 오류를 파악하고 해결할 수 있도록 지원합니다.

구현 내용

이번 커밋에서는 크게 백엔드 로깅 및 에러 핸들링 강화, 컨퍼런스 기능 제거, 그리고 프론트엔드 로깅 개선 작업이 이루어졌습니다.

주요 변경사항 상세 설명

백엔드:

  • 요청 로깅 미들웨어 추가: 모든 HTTP 요청에 대해 메서드, URL, 상태 코드, 응답 시간, IP 주소를 기록하여 요청 처리 과정을 투명하게 추적할 수 있게 합니다.
  • 글로벌 에러 핸들링 미들웨어 추가: 애플리케이션 전반에서 발생하는 예외를 처리하고, 상세한 에러 로그를 기록하도록 합니다.
  • 프로세스 레벨 에러 핸들러 추가: uncaughtException, unhandledRejection, SIGTERM/SIGINT와 같은 프로세스 레벨의 이벤트에 대한 핸들러를 구현하여 시스템의 갑작스러운 종료를 방지하고 안전하게 종료되도록 합니다.
  • 인증 로깅 강화: 로그인 성공/실패, 자동 인증 처리, 미인증 접근 시도 등 인증 관련 모든 이벤트를 상세히 기록합니다.
  • CRUD 작업 로깅 추가: 포스트 및 프로젝트 생성, 수정, 삭제와 같은 핵심 CRUD 작업에 대한 로그를 남깁니다.
  • DB 슬로우 쿼리 로깅: 1초 이상 소요되는 데이터베이스 쿼리에 대해 경고 로그를 생성하여 성능 병목 지점을 파악할 수 있도록 합니다.
  • DB 쿼리 실패 로깅 및 커넥션 풀 모니터링: 데이터베이스 쿼리 실행 실패 시 에러 로그를 기록하고, 커넥션 풀 상태를 모니터링합니다.
  • KaTeX 마크다운 렌더링 실패 로깅: 마크다운 렌더링 중 발생하는 KaTeX 관련 오류를 기록합니다.
  • CORS 차단 로깅: CORS 정책에 의해 차단된 요청에 대한 정보를 기록합니다.

프론트엔드:

  • SSR 에러 로깅 구조화: 서버 사이드 렌더링 시 발생하는 에러에 [SSR:PAGE] 접두사를 사용하여 에러 발생 위치를 명확히 합니다.
  • index.astro 파일에서 불필요한 디버그 로그를 제거하고, 에러 발생 시에만 로깅하도록 수정합니다.
  • status.astro 파일에서 SSR API URL을 수정하고 에러 핸들링을 추가하여 안정성을 높입니다.

컨퍼런스 기능 제거:

  • conferenceRoutes.js, conferenceController.js, conferenceService.js 파일을 삭제했습니다.
  • create-conferences-table.sql 파일을 삭제했습니다.
  • conferences.astro 페이지를 삭제했습니다.
  • app.js 및 README.md에서 컨퍼런스 관련 참조를 제거했습니다.

변경된 파일 목록

  • backend/README.md
  • backend/app.js
  • backend/config/db.js
  • backend/controllers/authController.js
  • backend/controllers/conferenceController.js
  • backend/db/create-conferences-table.sql
  • backend/middleware/auth.js
  • backend/routes/conferenceRoutes.js
  • backend/services/conferenceService.js
  • backend/services/postService.js
  • backend/services/projectService.js
  • backend/utils/markdown.js
  • frontend/src/pages/conferences.astro
  • frontend/src/pages/index.astro
  • frontend/src/pages/status.astro

추가/삭제된 코드 라인 수

  • 추가 라인: 143
  • 삭제 라인: 164

핵심 코드 설명

1. 백엔드 요청 로깅 미들웨어 (backend/app.js)

// Request logging middleware
app.use((req, res, next) => {
  const start = Date.now();
  const { method, originalUrl } = req;
  const ip = req.headers['x-forwarded-for']?.split(',')[0].trim() || req.socket.remoteAddress;

  res.on('finish', () => {
    const duration = Date.now() - start;
    const { statusCode } = res;
    const level = statusCode >= 500 ? 'ERROR' : statusCode >= 400 ? 'WARN' : 'INFO';
    console.log(`[{method} {statusCode} {ip}`);
  });

  next();
});

이 미들웨어는 모든 요청이 들어올 때마다 시작 시간을 기록하고, 응답이 완료될 때까지의 시간을 측정합니다. 응답의 상태 코드에 따라 INFO, WARN, ERROR 레벨로 구분하여 method, originalUrl, statusCode, duration, ip 정보를 콘솔에 기록합니다. 이를 통해 어떤 요청이 얼마나 오래 걸렸고, 어떤 상태 코드로 응답했는지 쉽게 파악할 수 있습니다.

2. 백엔드 글로벌 에러 핸들링 미들웨어 (backend/app.js)

// Global error handling middleware
app.use((err, req, res, next) => {
  const ip = req.headers['x-forwarded-for']?.split(',')[0].trim() || req.socket.remoteAddress;
  console.error(`[ERROR] Unhandled error on {req.originalUrl} - ${ip}:`, err.message);
  console.error(err.stack);
  res.status(500).json({ error: 'Internal server error' });
});

이 미들웨어는 express 애플리케이션에서 발생하는 모든 에러를 잡아냅니다. 에러 발생 시, 해당 요청에 대한 정보와 에러 메시지, 그리고 스택 트레이스를 콘솔에 상세히 기록합니다. 클라이언트에게는 일반적인 "Internal server error" 메시지만 전달하여 민감한 정보 노출을 방지합니다.

3. DB 슬로우 쿼리 로깅 (backend/config/db.js)

// Wrap pool.query to add slow query logging
const originalQuery = pool.query.bind(pool);
pool.query = async (...args) => {
  const start = Date.now();
  try {
    const result = await originalQuery(...args);
    const duration = Date.now() - start;
    if (duration > SLOW_QUERY_THRESHOLD_MS) {
      const queryText = typeof args[0] === 'string' ? args[0] : args[0]?.text;
      console.warn(`[DB] Slow query ({queryText?.substring(0, 200)}`);
    }
    return result;
  } catch (error) {
    const duration = Date.now() - start;
    const queryText = typeof args[0] === 'string' ? args[0] : args[0]?.text;
    console.error(`[DB] Query failed ({queryText?.substring(0, 200)} - ${error.message}`);
    throw error;
  }
};

pg 라이브러리의 pool.query 메서드를 오버라이드하여 각 쿼리의 실행 시간을 측정합니다. 설정된 SLOW_QUERY_THRESHOLD_MS (1000ms, 즉 1초)를 초과하는 쿼리에 대해서는 경고 로그를 출력합니다. 또한, 쿼리 실행 중 발생하는 에러도 상세히 기록합니다. 이를 통해 데이터베이스 성능 저하의 원인을 빠르게 찾아낼 수 있습니다.

기술적 의사결정

PM2 로깅 및 에러 핸들링 강화

  • 기술 선택: Node.js의 내장 console 객체와 process 객체의 이벤트 리스너, 그리고 Express 미들웨어 패턴을 활용했습니다.
  • 선택 이유: 외부 라이브러리 의존성을 최소화하면서도 Node.js 환경에서 표준적으로 지원하는 기능을 최대한 활용하여 효율적인 로깅 및 에러 처리가 가능합니다. 특히, console.log, console.error는 PM2와 같은 프로세스 매니저와 잘 연동되어 로그 파일 관리가 용이합니다.
  • 다른 대안:
    • winston 또는 morgan과 같은 로깅 라이브러리 사용: 더욱 정교한 로그 포맷팅, 로그 레벨 관리, 다양한 로그 출력 대상(파일, DB 등) 설정이 가능합니다.
    • bunyan과 같은 구조화된 로깅 라이브러리 사용: JSON 형식의 로그를 생성하여 기계가 파싱하기 용이하게 만듭니다.
  • 장단점 분석:
    • 내장 기능 활용 (선택):
      • 장점: 구현이 간단하고 외부 라이브러리 의존성이 없어 초기 도입이 쉽습니다. PM2와의 호환성이 좋습니다.
      • 단점: 로그 포맷팅이나 레벨 관리가 상대적으로 제한적입니다. 대규모 시스템에서는 전문 로깅 라이브러리보다 관리 효율성이 떨어질 수 있습니다.
    • 로깅 라이브러리 사용 (대안):
      • 장점: 풍부한 기능과 유연성을 제공합니다. 복잡한 로깅 요구사항을 충족할 수 있습니다.
      • 단점: 라이브러리 학습 곡선이 있으며, 프로젝트에 새로운 의존성을 추가하게 됩니다.

현재 프로젝트의 규모와 목적을 고려했을 때, 핵심적인 로깅 및 에러 핸들링 기능 강화에는 내장 기능 활용이 충분하다고 판단했습니다. 향후 필요에 따라 전문 로깅 라이브러리로의 전환을 고려할 수 있습니다.

컨퍼런스 기능 제거

  • 기술 선택: 해당 기능과 관련된 컨트롤러, 서비스, 라우트, 데이터베이스 스키마, 프론트엔드 페이지를 직접 삭제하는 방식으로 진행했습니다.
  • 선택 이유: 이 기능은 현재 프로젝트에서 완전히 사용되지 않고 있으므로, 해당 코드를 제거하는 것이 가장 확실하고 효율적인 방법입니다. 이는 코드 베이스의 복잡성을 줄이고, 잠재적인 버그 발생 가능성을 낮추며, 개발자들이 혼란스러워할 여지를 없애줍니다.
  • 다른 대안:
    • 기능 비활성화 (Feature Flag): 설정 파일 등을 통해 기능을 켜고 끌 수 있도록 관리할 수 있습니다.
    • 주석 처리: 코드를 삭제하지 않고 주석으로 처리하여 기록을 남길 수 있습니다.
  • 장단점 분석:
    • 직접 삭제 (선택):
      • 장점: 코드 베이스가 가장 깔끔해지고, 불필요한 코드로 인한 혼란이 없습니다.
      • 단점: 만약 해당 기능이 나중에라도 필요하게 되면, 다시 구현해야 하는 부담이 있습니다.
    • 기능 비활성화 (대안):
      • 장점: 필요에 따라 기능을 다시 활성화하기 용이합니다.
      • 단점: 관련 코드가 여전히 코드 베이스에 남아있어 약간의 복잡성이 유지됩니다.
    • 주석 처리 (대안):
      • 장점: 변경 기록을 명확히 남길 수 있습니다.
      • 단점: 코드 베이스가 지저분해지고, 실제 사용되지 않는 코드가 남아있어 유지보수에 방해가 될 수 있습니다.

프로젝트의 현재 상태에서 컨퍼런스 기능의 재사용 가능성이 매우 낮다고 판단하여, 코드 베이스를 최대한 간결하게 유지하기 위해 직접 삭제하는 방식을 선택했습니다.

배운 점 및 개선점

배운 점

  • 로깅의 중요성 재확인: 상세하고 구조화된 로그는 개발 및 운영 과정에서 발생하는 문제 해결의 핵심 열쇠임을 다시 한번 느꼈습니다. 특히, 요청 처리 과정, 인증 시도, 데이터베이스 연동 등 각 단계별 로그는 문제의 원인을 좁혀나가는 데 결정적인 역할을 합니다.
  • 에러 핸들링의 다층 구조: 단순히 예외를 잡는 것을 넘어, 애플리케이션 레벨, 프로세스 레벨의 에러를 모두 고려해야 시스템의 안정성을 보장할 수 있음을 배웠습니다. uncaughtException과 unhandledRejection 같은 이벤트에 대한 적절한 처리가 중요함을 깨달았습니다.
  • 불필요한 코드의 관리: 사용되지 않는 코드는 단순히 공간을 차지하는 것을 넘어, 코드 이해도를 떨어뜨리고 잠재적인 버그의 원인이 될 수 있음을 다시 한번 인지했습니다. 정기적인 코드 정리의 필요성을 느꼈습니다.

개선점 및 다음 단계 계획

  • 로그 포맷 표준화 및 중앙 집중화: 현재는 console.log를 사용하여 기본적인 로깅을 하고 있지만, 추후 프로젝트 규모가 커진다면 winston 또는 bunyan과 같은 라이브러리를 도입하여 로그 포맷을 표준화하고, ELK 스택(Elasticsearch, Logstash, Kibana)과 같은 중앙 집중식 로그 관리 시스템과 연동하여 로그 분석 및 모니터링을 강화하는 것이 필요합니다.
  • DB 쿼리 로깅 상세화: 슬로우 쿼리 로깅 시, 쿼리 자체뿐만 아니라 해당 쿼리가 어떤 API 호출이나 프로세스에서 발생했는지에 대한 컨텍스트 정보를 함께 기록하면 더 효과적인 성능 분석이 가능할 것입니다.
  • 사용되지 않는 코드 자동화된 탐지: 주기적으로 사용되지 않는 API 엔드포인트나, 호출되지 않는 함수 등을 자동으로 탐지하고 리포트하는 시스템을 구축하면 코드 베이스 관리에 큰 도움이 될 것입니다.
  • 프론트엔드 에러 리포팅 강화: 현재 프론트엔드 에러 로깅은 개선되었지만, 실제 사용자 환경에서 발생하는 에러를 개발팀에게 자동으로 알림을 주는 Sentry와 같은 에러 리포팅 도구 도입을 고려해볼 수 있습니다.

참고 자료