🤖

본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.

⚠️

본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.

이미지 로딩 중...

로그 관리 및 모니터링 완벽 가이드 - 슬라이드 1/11
A

AI Generated

2025. 11. 14. · 130 Views

로그 관리 및 모니터링 완벽 가이드

실무에서 필수적인 로그 관리와 모니터링 시스템 구축 방법을 다룹니다. Nginx 로그 설정부터 실시간 모니터링, 로그 분석까지 실무에 바로 적용할 수 있는 내용을 제공합니다.


목차

  1. Nginx 액세스 로그 설정 - 효율적인 로그 포맷 구성하기
  2. Nginx 에러 로그 분석 - 장애 원인을 빠르게 찾기
  3. Winston 로거 설정 - Node.js 애플리케이션 로그 관리
  4. PM2 로그 관리 - 프로세스 매니저 로그 통합
  5. 실시간 로그 모니터링 - tail과 grep 활용
  6. 로그 로테이션 설정 - 디스크 공간 관리
  7. 구조화된 로그 - JSON 로그의 장점과 활용
  8. 성능 모니터링 - 응답 시간 추적 및 분석
  9. 에러 추적 및 알림 - Sentry 연동
  10. 로그 시각화 및 대시보드 - Grafana 활용

1. Nginx 액세스 로그 설정 - 효율적인 로그 포맷 구성하기

시작하며

여러분이 서비스를 운영하다가 "왜 갑자기 응답 속도가 느려졌지?"라는 질문을 마주한 적 있나요? 어떤 API가 문제인지, 어느 사용자가 어떤 요청을 보냈는지 추적할 방법이 없다면 정말 답답한 상황입니다.

이런 문제는 실제 개발 현장에서 매일 발생합니다. 로그가 제대로 남지 않으면 장애 원인을 찾는데 몇 시간이 걸릴 수 있고, 심지어 원인을 못 찾는 경우도 있습니다.

사용자는 불편을 겪고, 개발팀은 스트레스를 받게 됩니다. 바로 이럴 때 필요한 것이 체계적인 Nginx 액세스 로그 설정입니다.

적절한 로그 포맷을 설정하면 요청 시간, 응답 시간, 사용자 정보, 에러 상태 등을 한눈에 파악할 수 있습니다.

개요

간단히 말해서, Nginx 액세스 로그는 서버로 들어오는 모든 HTTP 요청과 응답 정보를 기록하는 시스템입니다. 웹 서버를 운영할 때 사용자 행동 패턴, 성능 병목 지점, 보안 위협을 파악하는 것은 필수입니다.

Nginx 액세스 로그는 이 모든 정보를 제공합니다. 예를 들어, 특정 API 엔드포인트의 평균 응답 시간이 3초를 넘는다면, 로그를 분석해서 즉시 최적화 작업을 시작할 수 있습니다.

기존에는 단순히 IP, 요청 경로만 기록했다면, 이제는 응답 시간, 업스트림 서버 정보, 사용자 에이전트, 요청 본문 크기까지 상세하게 기록할 수 있습니다. Nginx의 log_format 지시어를 사용하면 JSON 형식으로 구조화된 로그를 만들 수 있고, 응답 시간을 마이크로초 단위로 추적할 수 있으며, 실시간 로그 분석 도구와 연동이 가능합니다.

이러한 특징들이 대규모 서비스에서 안정적인 운영을 가능하게 만듭니다.

코드 예제

# /etc/nginx/nginx.conf
http {
    # JSON 형식의 상세한 로그 포맷 정의
    log_format json_combined escape=json '{'
        '"time_local":"$time_local",'
        '"remote_addr":"$remote_addr",'
        '"request":"$request",'
        '"status":$status,'
        '"body_bytes_sent":$body_bytes_sent,'
        '"request_time":$request_time,'
        '"upstream_response_time":"$upstream_response_time",'
        '"http_user_agent":"$http_user_agent",'
        '"http_referer":"$http_referer"'
    '}';

    # 로그 파일 경로와 포맷 지정
    access_log /var/log/nginx/access.log json_combined;
}

설명

이것이 하는 일: Nginx 서버로 들어오는 모든 HTTP 요청에 대한 상세 정보를 JSON 형식으로 기록하여 분석과 모니터링을 쉽게 만듭니다. 첫 번째로, log_format 지시어는 로그의 구조를 정의합니다.

json_combined라는 이름으로 커스텀 포맷을 만들고, escape=json 옵션으로 특수문자를 자동으로 이스케이프 처리합니다. 왜 이렇게 하냐면, 로그 파싱 도구들이 JSON을 쉽게 처리할 수 있기 때문입니다.

그 다음으로, 각 필드를 정의합니다. $time_local은 요청 시간, $remote_addr은 클라이언트 IP, $request는 HTTP 메서드와 URI, $status는 응답 상태 코드를 기록합니다.

특히 중요한 것은 $request_time과 $upstream_response_time인데, 이 둘을 비교하면 Nginx 자체의 처리 시간과 백엔드 서버의 응답 시간을 구분할 수 있습니다. 마지막으로, access_log 지시어가 실제 로그 파일 경로와 사용할 포맷을 지정합니다.

/var/log/nginx/access.log 파일에 json_combined 포맷으로 로그가 쌓이게 됩니다. 여러분이 이 설정을 사용하면 로그 분석 도구(ELK Stack, Grafana)와 쉽게 연동할 수 있고, 느린 요청을 즉시 찾아낼 수 있으며, 사용자 행동 패턴을 시각화할 수 있습니다.

특히 장애 발생 시 정확한 시간대와 영향받은 사용자를 빠르게 파악할 수 있어 MTTR(평균 복구 시간)을 크게 줄일 수 있습니다.

실전 팁

💡 request_time이 1초를 넘는 요청들을 주기적으로 필터링해서 성능 최적화 우선순위를 정하세요. grep을 사용하면 쉽게 찾을 수 있습니다.

💡 로그 파일이 너무 커지면 디스크 공간 문제가 발생할 수 있으니 logrotate를 설정해서 자동으로 압축 및 삭제하도록 만드세요.

💡 민감한 정보(비밀번호, 토큰)가 URL 파라미터에 포함될 수 있으니, 로그에 기록되지 않도록 map 지시어로 필터링하세요.

💡 개발 환경에서는 debug 레벨로, 운영 환경에서는 info 레벨로 로그를 분리하면 불필요한 로그를 줄일 수 있습니다.

💡 buffer 옵션을 사용해서 로그를 메모리에 모았다가 한 번에 쓰면 디스크 I/O를 줄여 성능을 개선할 수 있습니다.


2. Nginx 에러 로그 분석 - 장애 원인을 빠르게 찾기

시작하며

여러분이 새벽 3시에 장애 알림을 받고 급하게 서버에 접속했을 때, 어디서부터 확인해야 할지 막막한 경험 있으신가요? 에러가 발생했는데 에러 메시지가 없다면, 마치 어둠 속에서 바늘 찾기와 같습니다.

이런 문제는 실제로 서비스 운영에서 가장 스트레스 받는 순간 중 하나입니다. 에러 로그가 제대로 설정되지 않으면 문제의 근본 원인을 찾는데 몇 시간씩 소비하게 되고, 그 사이 서비스는 계속 다운되어 있습니다.

사용자 이탈과 매출 손실로 직결됩니다. 바로 이럴 때 필요한 것이 체계적인 Nginx 에러 로그 설정과 분석 방법입니다.

적절한 로그 레벨과 분석 도구를 사용하면 문제의 원인을 몇 분 안에 파악할 수 있습니다.

개요

간단히 말해서, Nginx 에러 로그는 서버에서 발생하는 모든 오류와 경고를 기록하는 시스템입니다. 서버를 안정적으로 운영하려면 어떤 종류의 에러가 얼마나 자주 발생하는지 파악해야 합니다.

Nginx 에러 로그는 연결 실패, 타임아웃, 파일을 찾을 수 없음, 업스트림 서버 장애 등 다양한 에러 정보를 제공합니다. 예를 들어, upstream timed out 에러가 반복된다면 백엔드 서버의 성능 문제를 즉시 알 수 있습니다.

기존에는 에러가 발생한 후에야 로그를 확인했다면, 이제는 실시간으로 에러 패턴을 모니터링하고 특정 에러가 임계치를 넘으면 자동으로 알림을 받을 수 있습니다. 에러 로그의 핵심 특징은 다양한 로그 레벨(debug, info, notice, warn, error, crit, alert, emerg)을 지원하고, 각 레벨별로 다른 파일에 기록할 수 있으며, 에러 발생 컨텍스트(어떤 요청에서 발생했는지)도 함께 기록된다는 것입니다.

이러한 특징들이 빠른 장애 대응을 가능하게 만듭니다.

코드 예제

# /etc/nginx/nginx.conf
http {
    # 전역 에러 로그 설정 (warn 레벨 이상)
    error_log /var/log/nginx/error.log warn;

    server {
        listen 80;
        server_name example.com;

        # 특정 서버 블록의 에러 로그 (debug 레벨)
        error_log /var/log/nginx/example.error.log debug;

        location /api {
            proxy_pass http://backend;
            # upstream 에러만 별도 기록
            error_log /var/log/nginx/api.error.log error;
        }
    }
}

설명

이것이 하는 일: Nginx에서 발생하는 다양한 레벨의 에러를 각각 적절한 파일에 기록하여 문제 해결 시간을 단축시킵니다. 첫 번째로, 전역 error_log 설정은 http 블록에서 warn 레벨 이상의 모든 에러를 기록합니다.

warn 레벨은 경고 수준의 문제들을 포함하므로 잠재적인 문제를 조기에 발견할 수 있습니다. 왜 이렇게 하냐면, 운영 환경에서는 debug 레벨까지 기록하면 로그가 너무 많아져서 정작 중요한 에러를 놓칠 수 있기 때문입니다.

그 다음으로, 서버 블록 레벨에서 더 상세한 디버그 로그를 설정합니다. 특정 도메인이나 서비스에서 문제가 발생했을 때, 해당 서버 블록의 debug 로그를 활성화하면 매우 상세한 정보를 얻을 수 있습니다.

예를 들어, SSL 핸드셰이크 실패, 리라이트 규칙 처리 과정 등을 볼 수 있습니다. 마지막으로, location 블록에서 특정 경로의 에러만 별도로 기록합니다.

/api 경로는 백엔드와 통신하므로 upstream 관련 에러가 많이 발생할 수 있는데, 이를 별도 파일에 기록하면 API 서버 문제와 웹 서버 문제를 쉽게 구분할 수 있습니다. 여러분이 이 설정을 사용하면 에러를 카테고리별로 분류할 수 있고, 급한 상황에서 필요한 로그만 빠르게 확인할 수 있으며, 로그 분석 도구로 에러 트렌드를 추적할 수 있습니다.

특히 upstream timeout 같은 반복적인 에러 패턴을 발견하면, 백엔드 서버의 스케일 아웃이나 타임아웃 조정 같은 근본적인 해결책을 마련할 수 있습니다.

실전 팁

💡 운영 환경에서는 error 레벨 이상만 기록하고, 문제가 발생했을 때만 일시적으로 debug 레벨로 변경하세요. nginx -s reload로 즉시 적용됩니다.

💡 tail -f로 실시간 로그를 모니터링하면서 curl로 테스트 요청을 보내면, 에러 발생 과정을 단계별로 확인할 수 있습니다.

💡 awk나 grep을 사용해서 특정 에러 코드(502, 504)만 필터링하고, 발생 빈도를 카운트하면 가장 심각한 문제를 우선 처리할 수 있습니다.

💡 Sentry나 Datadog 같은 APM 도구와 연동하면 에러 발생 시 자동으로 슬랙 알림을 받을 수 있습니다.

💡 로그 파일에 쓰기 권한이 없으면 Nginx가 시작되지 않으니, 항상 chown으로 nginx 유저에게 권한을 부여하세요.


3. Winston 로거 설정 - Node.js 애플리케이션 로그 관리

시작하며

여러분이 Node.js 애플리케이션을 개발할 때 console.log만으로 모든 로그를 관리하고 계신가요? 개발할 때는 괜찮지만, 운영 환경에서 수천 개의 로그 중에서 필요한 정보를 찾는 것은 거의 불가능합니다.

이런 문제는 실제로 많은 주니어 개발자들이 겪는 어려움입니다. console.log는 로그 레벨이 없고, 파일로 저장되지 않으며, 구조화되지 않아서 검색도 어렵습니다.

장애가 발생하면 "언제, 어디서, 왜" 발생했는지 추적할 방법이 없습니다. 바로 이럴 때 필요한 것이 Winston 같은 전문적인 로깅 라이브러리입니다.

로그 레벨 관리, 파일 저장, JSON 포맷팅, 트랜스포트 기능을 통해 엔터프라이즈급 로그 시스템을 쉽게 구축할 수 있습니다.

개요

간단히 말해서, Winston은 Node.js를 위한 강력하고 유연한 로깅 라이브러리로, 다양한 출력 대상과 포맷을 지원합니다. 프로덕션 환경에서 애플리케이션을 안정적으로 운영하려면 로그를 체계적으로 관리해야 합니다.

Winston은 info, warn, error 같은 로그 레벨을 제공하고, 각 레벨별로 다른 파일에 저장하거나, 외부 서비스로 전송할 수 있습니다. 예를 들어, error 레벨 로그만 별도 파일에 저장하고 동시에 Slack으로 알림을 보낼 수 있습니다.

기존에는 console.log로 모든 로그를 콘솔에만 출력했다면, 이제는 동시에 파일에 저장하고, 로그 수집 서비스로 전송하며, 중요한 로그는 별도로 모니터링할 수 있습니다. Winston의 핵심 특징은 다중 트랜스포트(콘솔, 파일, HTTP, DB 등)를 지원하고, 커스텀 포맷을 쉽게 만들 수 있으며, 로그 레벨을 환경별로 다르게 설정할 수 있고, 메타데이터를 함께 기록할 수 있다는 것입니다.

이러한 특징들이 대규모 애플리케이션에서도 로그를 효율적으로 관리할 수 있게 만듭니다.

코드 예제

const winston = require('winston');

// 로그 포맷 정의: 타임스탬프와 JSON 형식
const logFormat = winston.format.combine(
    winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
    winston.format.errors({ stack: true }),
    winston.format.json()
);

// Winston 로거 생성
const logger = winston.createLogger({
    level: process.env.LOG_LEVEL || 'info',
    format: logFormat,
    transports: [
        // 모든 로그를 combined.log에 저장
        new winston.transports.File({ filename: 'logs/combined.log' }),
        // 에러 로그만 error.log에 저장
        new winston.transports.File({ filename: 'logs/error.log', level: 'error' })
    ]
});

// 개발 환경에서는 콘솔에도 출력
if (process.env.NODE_ENV !== 'production') {
    logger.add(new winston.transports.Console({
        format: winston.format.simple()
    }));
}

// 사용 예시
logger.info('User logged in', { userId: 12345, ip: '192.168.1.1' });
logger.error('Database connection failed', { error: 'ETIMEDOUT', db: 'users' });

설명

이것이 하는 일: Node.js 애플리케이션에서 발생하는 모든 로그를 레벨별로 분류하고, 여러 대상(파일, 콘솔, 외부 서비스)에 동시에 기록합니다. 첫 번째로, logFormat을 정의합니다.

winston.format.combine으로 여러 포맷을 조합하는데, timestamp는 각 로그에 시간 정보를 추가하고, errors는 에러 객체의 스택 트레이스를 자동으로 캡처하며, json은 로그를 JSON 형식으로 변환합니다. 왜 JSON을 사용하냐면, 나중에 ELK Stack 같은 로그 분석 도구로 쉽게 파싱할 수 있기 때문입니다.

그 다음으로, createLogger로 실제 로거 인스턴스를 만듭니다. level은 기록할 최소 로그 레벨을 지정하는데, 환경 변수로 설정할 수 있어서 개발/스테이징/운영 환경별로 다른 레벨을 사용할 수 있습니다.

transports 배열에는 로그를 출력할 대상들을 정의합니다. File 트랜스포트 두 개를 추가했는데, 하나는 모든 로그를, 다른 하나는 error 레벨만 저장합니다.

마지막으로, 개발 환경에서만 콘솔 출력을 추가합니다. 운영 환경에서는 콘솔 출력이 성능에 영향을 줄 수 있고 불필요하므로 제외합니다.

로그를 기록할 때는 logger.info, logger.error 같은 메서드를 사용하고, 두 번째 인자로 메타데이터 객체를 전달하면 userId, ip 같은 컨텍스트 정보도 함께 기록됩니다. 여러분이 이 설정을 사용하면 로그를 구조화해서 검색이 쉬워지고, 에러 로그만 따로 모니터링할 수 있으며, 운영 환경에서는 debug 로그를 제외해서 로그 파일 크기를 줄일 수 있습니다.

특히 메타데이터를 활용하면 "특정 사용자가 언제 어떤 작업을 했는지" 같은 복잡한 쿼리도 쉽게 수행할 수 있어, 사용자 문의 대응이나 버그 재현에 큰 도움이 됩니다.

실전 팁

💡 winston-daily-rotate-file 패키지를 사용하면 날짜별로 로그 파일을 자동 분리하고, 오래된 파일은 자동 삭제할 수 있습니다.

💡 로그에 요청 ID를 포함하면 하나의 HTTP 요청이 여러 서비스를 거치면서 남긴 모든 로그를 추적할 수 있습니다.

💡 process.on('uncaughtException')과 process.on('unhandledRejection')에서 logger.error를 호출하면 예상치 못한 에러도 놓치지 않습니다.

💡 민감한 정보(비밀번호, 신용카드 번호)는 로그에 기록되지 않도록 커스텀 포맷에서 필터링하세요.

💡 HTTP 요청 로깅은 morgan 미들웨어를 Winston과 연동해서 사용하면 더 효율적입니다.


4. PM2 로그 관리 - 프로세스 매니저 로그 통합

시작하며

여러분이 Node.js 애플리케이션을 PM2로 실행하고 있는데, 애플리케이션 로그와 PM2 로그가 여기저기 흩어져 있어서 찾기 힘든 경험 있으신가요? 로그가 분산되어 있으면 장애 상황에서 전체 그림을 파악하기가 정말 어렵습니다.

이런 문제는 실제로 많은 프로젝트에서 발생합니다. PM2는 자체적으로 stdout, stderr를 별도 파일에 기록하는데, 애플리케이션의 Winston 로그와 별도로 관리되면 두 곳을 다 확인해야 합니다.

시간 낭비가 심하고, 로그 간 상관관계를 파악하기도 어렵습니다. 바로 이럴 때 필요한 것이 PM2와 애플리케이션 로거를 통합하는 전략입니다.

PM2의 로그 설정을 최적화하고, 애플리케이션 로그를 적절히 구성하면 모든 로그를 한 곳에서 확인할 수 있습니다.

개요

간단히 말해서, PM2 로그 관리는 프로세스 매니저가 생성하는 로그와 애플리케이션 로그를 효율적으로 통합하고 관리하는 방법입니다. PM2로 여러 개의 Node.js 프로세스를 실행할 때, 각 프로세스의 로그를 개별적으로 관리하는 것은 매우 비효율적입니다.

PM2는 기본적으로 out 로그와 error 로그를 분리해서 저장하고, 로그 로테이션 기능도 제공합니다. 예를 들어, 클러스터 모드로 4개의 인스턴스를 실행한다면, 각 인스턴스의 로그를 하나로 합쳐서 볼 수 있어야 디버깅이 가능합니다.

기존에는 pm2 logs 명령어로 실시간 로그만 확인하거나, 각 로그 파일을 직접 열어봐야 했다면, 이제는 PM2 설정 파일(ecosystem.config.js)을 통해 로그 경로, 포맷, 로테이션을 세밀하게 제어할 수 있습니다. PM2 로그 관리의 핵심 특징은 모든 인스턴스의 로그를 하나의 파일로 병합할 수 있고, 로그 로테이션을 자동화할 수 있으며, timestamp를 추가해서 로그 발생 시간을 정확히 추적할 수 있고, pm2-logrotate 모듈로 오래된 로그를 자동 삭제할 수 있다는 것입니다.

이러한 특징들이 여러 인스턴스를 실행하는 환경에서도 로그를 깔끔하게 관리할 수 있게 만듭니다.

코드 예제

// ecosystem.config.js
module.exports = {
  apps: [{
    name: 'api-server',
    script: './app.js',
    instances: 4,
    exec_mode: 'cluster',

    // 로그 파일 경로 지정
    out_file: './logs/pm2-out.log',
    error_file: './logs/pm2-error.log',

    // 로그에 타임스탬프 추가
    time: true,

    // 로그를 하나의 파일로 병합 (인스턴스별 분리하지 않음)
    merge_logs: true,

    // 환경 변수 설정
    env: {
      NODE_ENV: 'production',
      LOG_LEVEL: 'info'
    }
  }]
};

설명

이것이 하는 일: PM2가 관리하는 여러 Node.js 인스턴스의 로그를 통합하고, 타임스탬프를 추가하며, 로그 파일 위치를 제어합니다. 첫 번째로, ecosystem.config.js 파일에서 애플리케이션 설정을 정의합니다.

name은 PM2에서 식별할 이름이고, instances: 4는 클러스터 모드로 4개의 프로세스를 실행한다는 의미입니다. 왜 클러스터 모드를 사용하냐면, CPU 코어를 모두 활용해서 성능을 극대화하고 무중단 배포도 가능하기 때문입니다.

그 다음으로, 로그 파일 경로를 명시적으로 지정합니다. out_file은 표준 출력(console.log 등)이 저장될 파일이고, error_file은 에러 로그가 저장될 파일입니다.

기본값은 ~/.pm2/logs/ 아래인데, 프로젝트 디렉토리 내에 저장하면 관리가 훨씬 쉽습니다. time: true 옵션은 각 로그 라인 앞에 타임스탬프를 자동으로 추가해줍니다.

마지막으로, merge_logs: true 설정이 매우 중요합니다. 이 옵션이 없으면 4개의 인스턴스가 각각 pm2-out-0.log, pm2-out-1.log 같은 별도 파일을 만드는데, 이렇게 되면 전체 로그 흐름을 파악하기가 어렵습니다.

merge_logs를 활성화하면 모든 인스턴스의 로그가 하나의 파일에 시간순으로 기록됩니다. 여러분이 이 설정을 사용하면 pm2 logs 명령어 하나로 모든 인스턴스의 로그를 실시간으로 볼 수 있고, 로그 파일 위치를 명확히 알 수 있으며, 각 로그가 언제 발생했는지 정확한 시간을 알 수 있습니다.

특히 부하가 많은 시간대에 어느 인스턴스에서 에러가 발생했는지 쉽게 추적할 수 있어, 로드 밸런싱 문제나 메모리 누수를 빠르게 발견할 수 있습니다.

실전 팁

💡 pm2 install pm2-logrotate 명령어로 로그 로테이션 모듈을 설치하면, 로그 파일이 일정 크기를 넘으면 자동으로 압축하고 새 파일을 생성합니다.

💡 pm2 logs --lines 100 명령어로 최근 100줄만 빠르게 확인할 수 있고, pm2 logs --err로 에러 로그만 필터링할 수 있습니다.

💡 log_date_format 옵션을 사용하면 타임스탬프 형식을 'YYYY-MM-DD HH:mm:ss' 같이 커스터마이징할 수 있습니다.

💡 max_memory_restart 옵션과 함께 사용하면, 메모리 누수로 인스턴스가 재시작될 때 로그에 명확히 기록되어 문제 파악이 쉽습니다.

💡 pm2 flush 명령어로 모든 로그를 한 번에 삭제할 수 있지만, 운영 환경에서는 신중하게 사용하세요.


5. 실시간 로그 모니터링 - tail과 grep 활용

시작하며

여러분이 배포를 막 완료했는데, 실시간으로 로그를 보면서 에러가 발생하는지 확인하고 싶은 상황이라고 생각해보세요. 로그 파일을 계속 열어서 새로고침하는 것은 정말 비효율적입니다.

이런 문제는 실제로 배포 직후, 성능 테스트 중, 장애 대응 상황에서 매우 자주 발생합니다. 로그가 실시간으로 업데이트되지 않으면 문제가 발생해도 늦게 알아차리게 되고, 빠른 대응이 불가능합니다.

심각한 에러가 수백 개 쌓인 후에야 발견하는 경우도 있습니다. 바로 이럴 때 필요한 것이 tail -f와 grep을 조합한 실시간 로그 모니터링입니다.

로그 파일의 끝부분을 계속 추적하면서, 특정 키워드만 필터링해서 볼 수 있습니다.

개요

간단히 말해서, tail과 grep은 유닉스 계열 시스템에서 로그 파일을 실시간으로 모니터링하고 필터링하는 강력한 커맨드라인 도구입니다. 서버를 운영하다 보면 로그 파일이 실시간으로 계속 업데이트되는데, 이 변화를 즉시 확인해야 할 때가 많습니다.

tail -f 명령어는 파일의 마지막 부분을 계속 추적해서 새로운 라인이 추가되면 즉시 화면에 출력합니다. 예를 들어, 배포 후 5분간 에러 로그를 실시간으로 모니터링하면서 문제가 없는지 확인할 수 있습니다.

기존에는 로그 파일을 주기적으로 열어서 수동으로 확인했다면, 이제는 tail -f로 자동으로 업데이트되는 로그를 보면서, grep으로 'ERROR', '500', 'timeout' 같은 중요한 키워드만 하이라이트할 수 있습니다. 실시간 로그 모니터링의 핵심 특징은 파일이 로테이트되어도 계속 추적할 수 있고(tail -F), 여러 파일을 동시에 모니터링할 수 있으며, 정규표현식으로 복잡한 패턴을 매칭할 수 있고, 색상으로 매칭된 부분을 강조할 수 있다는 것입니다.

이러한 특징들이 수천 줄의 로그에서 중요한 정보를 즉시 찾아낼 수 있게 만듭니다.

코드 예제

# 기본 실시간 로그 모니터링
tail -f /var/log/nginx/access.log

# 로그 파일이 로테이트되어도 계속 추적
tail -F /var/log/nginx/error.log

# ERROR 키워드만 필터링해서 실시간 모니터링
tail -f /var/log/application.log | grep --color=auto "ERROR"

# 여러 패턴 동시 검색 (ERROR 또는 WARN)
tail -f /var/log/application.log | grep -E --color=auto "ERROR|WARN"

# 특정 API 엔드포인트의 요청만 실시간 추적
tail -f /var/log/nginx/access.log | grep "/api/users"

# 응답 시간이 1초 이상인 요청만 필터링 (request_time > 1.0)
tail -f /var/log/nginx/access.log | grep -E "request_time\":[1-9]\."

설명

이것이 하는 일: 로그 파일에 새로운 라인이 추가되는 즉시 화면에 출력하고, 특정 키워드나 패턴만 필터링해서 보여줍니다. 첫 번째로, tail -f 명령어는 파일의 마지막 10줄을 출력한 후, 파일에 변경이 생기면 새로운 라인을 계속 출력합니다.

-f 옵션은 "follow"의 약자로, 파일을 계속 추적한다는 의미입니다. 왜 이게 유용하냐면, 터미널을 열어두기만 하면 새로운 로그가 자동으로 나타나서 별도로 파일을 다시 열 필요가 없기 때문입니다.

그 다음으로, tail -F(대문자 F)는 조금 더 강력한 버전입니다. 파일이 삭제되거나 이름이 변경되어도(로그 로테이션 시) 새로운 파일을 자동으로 찾아서 계속 추적합니다.

예를 들어, logrotate가 매일 자정에 error.log를 error.log.1로 변경하고 새 error.log를 만들어도, tail -F는 중단 없이 새 파일을 추적합니다. 마지막으로, 파이프(|)로 grep과 연결하면 필터링 기능이 추가됩니다.

grep "ERROR"는 ERROR 문자열이 포함된 라인만 출력하고, --color=auto 옵션은 매칭된 부분을 빨간색으로 강조합니다. -E 옵션은 확장 정규표현식을 사용할 수 있게 해서, "ERROR|WARN" 같은 OR 조건이나 "[1-9]." 같은 숫자 패턴 매칭도 가능합니다.

여러분이 이 명령어를 사용하면 배포 직후 실시간으로 에러를 감지할 수 있고, 특정 사용자의 요청만 추적할 수 있으며, 성능 문제(느린 응답)를 즉시 발견할 수 있습니다. 특히 스테이징 환경에서 QA 테스트를 하는 동안 터미널에 tail -f를 띄워두면, 테스터가 버그를 리포트하기 전에 개발자가 먼저 에러를 발견하고 수정할 수 있어 개발 속도가 크게 향상됩니다.

실전 팁

💡 Ctrl+C로 tail 명령어를 종료할 수 있고, Ctrl+S로 스크롤을 일시정지하고 Ctrl+Q로 다시 재개할 수 있습니다.

💡 tail -n 100 -f로 처음에 최근 100줄을 출력한 후 실시간 추적을 시작하면, 컨텍스트를 파악하기 좋습니다.

💡 grep -v "GET /health"로 헬스체크 요청을 제외하면, 의미 있는 로그만 볼 수 있어 노이즈가 줄어듭니다.

💡 multitail 도구를 사용하면 여러 로그 파일을 동시에 모니터링하고 화면을 분할해서 볼 수 있습니다.

💡 tee 명령어와 함께 사용하면 실시간으로 보면서 동시에 파일로도 저장할 수 있습니다: tail -f app.log | tee filtered.log


6. 로그 로테이션 설정 - 디스크 공간 관리

시작하며

여러분이 어느 날 서버가 갑자기 멈췄는데, 확인해보니 디스크 공간이 100% 찼다는 메시지를 본 적 있나요? 원인을 찾아보니 로그 파일이 수십 GB씩 쌓여 있었습니다.

정말 황당한 상황입니다. 이런 문제는 실제로 많은 서비스에서 발생하는 치명적인 장애 원인 중 하나입니다.

로그는 계속 쌓이는데 자동으로 삭제하거나 압축하지 않으면, 결국 디스크 공간을 모두 소진하게 됩니다. 디스크가 가득 차면 애플리케이션이 정상적으로 작동하지 않고, 데이터베이스도 쓰기를 할 수 없게 됩니다.

바로 이럴 때 필요한 것이 로그 로테이션입니다. 오래된 로그는 자동으로 압축하고, 일정 기간이 지나면 삭제해서 디스크 공간을 효율적으로 관리할 수 있습니다.

개요

간단히 말해서, 로그 로테이션은 로그 파일을 주기적으로 분리하고, 오래된 파일을 압축하거나 삭제해서 디스크 공간을 관리하는 시스템입니다. 서버를 장기간 운영하다 보면 로그 파일이 기하급수적으로 증가합니다.

하루에 1GB씩 쌓인다면 한 달이면 30GB, 1년이면 365GB가 됩니다. logrotate는 리눅스에서 기본 제공하는 도구로, 날짜별, 크기별로 로그를 분리하고, 오래된 파일을 자동으로 압축(gzip)하며, 정해진 개수만 유지하고 나머지는 삭제합니다.

예를 들어, 매일 자정에 access.log를 access.log.1로 변경하고, 기존 access.log.1은 access.log.2가 되며, 7일 이상 된 파일은 삭제하도록 설정할 수 있습니다. 기존에는 cron으로 직접 스크립트를 작성해서 로그를 관리했다면, 이제는 logrotate 설정 파일 하나로 모든 로그 파일의 로테이션 정책을 중앙에서 관리할 수 있습니다.

로그 로테이션의 핵심 특징은 시간 기반(daily, weekly) 또는 크기 기반(100M) 로테이션을 지원하고, 압축으로 디스크 공간을 90% 이상 절약할 수 있으며, postrotate 스크립트로 로테이션 후 특정 작업(Nginx reload 등)을 실행할 수 있고, missingok 옵션으로 파일이 없어도 에러 없이 계속 실행된다는 것입니다. 이러한 특징들이 무인 운영 환경에서도 로그를 안정적으로 관리할 수 있게 만듭니다.

코드 예제

# /etc/logrotate.d/nginx
/var/log/nginx/*.log {
    # 매일 로테이션 실행
    daily

    # 로그 파일이 없어도 에러 발생 안 함
    missingok

    # 최근 30개 파일만 유지
    rotate 30

    # 오래된 파일은 gzip으로 압축
    compress

    # 가장 최근 파일은 압축하지 않음 (분석 편의)
    delaycompress

    # 빈 파일은 로테이트하지 않음
    notifempty

    # 날짜 형식으로 파일명 생성 (access.log-20250114)
    dateext

    # 로테이션 후 Nginx에 재시작 신호 전송
    postrotate
        [ -f /var/run/nginx.pid ] && kill -USR1 `cat /var/run/nginx.pid`
    endscript
}

설명

이것이 하는 일: 지정된 주기마다 로그 파일을 새 파일로 분리하고, 오래된 파일은 압축하며, 설정된 개수 이상은 자동으로 삭제합니다. 첫 번째로, 로테이션 대상 파일을 지정합니다.

/var/log/nginx/*.log는 해당 디렉토리의 모든 .log 파일에 이 설정을 적용한다는 의미입니다. daily는 매일 자정에 로테이션을 실행하라는 지시이고, weekly나 monthly도 사용할 수 있습니다.

왜 daily를 사용하냐면, 트래픽이 많은 서비스는 하루에도 GB 단위로 로그가 쌓이기 때문입니다. 그 다음으로, 파일 관리 정책을 설정합니다.

rotate 30은 최근 30개 파일만 유지하고 그 이상은 삭제한다는 뜻입니다. compress는 오래된 파일을 gzip으로 압축하는데, 텍스트 파일은 보통 90% 이상 압축되므로 100GB가 10GB로 줄어듭니다.

delaycompress는 가장 최근 로테이트된 파일(어제 것)은 압축하지 않아서, 급하게 확인해야 할 때 압축 해제 없이 바로 볼 수 있게 합니다. 마지막으로, postrotate 스크립트가 실행됩니다.

로그 파일을 새로 만든 후 Nginx가 계속 예전 파일에 쓰는 것을 방지하기 위해, USR1 시그널을 보내서 Nginx가 로그 파일을 다시 열도록 합니다. 이렇게 하지 않으면 access.log.1 파일이 계속 커지고, 새 access.log는 비어있게 됩니다.

여러분이 이 설정을 사용하면 디스크 공간 부족으로 인한 장애를 예방할 수 있고, 날짜별로 로그가 정리되어 특정 날짜의 로그를 쉽게 찾을 수 있으며, 압축으로 스토리지 비용을 크게 절감할 수 있습니다. 특히 규정상 로그를 일정 기간 보관해야 하는 경우(금융권은 보통 1년), rotate 365로 설정하고 compress를 활성화하면 최소한의 공간으로 요구사항을 충족할 수 있습니다.

실전 팁

💡 size 100M 옵션을 추가하면 크기가 100MB를 넘을 때도 로테이션되어, 갑작스런 트래픽 증가에도 대응할 수 있습니다.

💡 logrotate -d /etc/logrotate.d/nginx 명령어로 실제 실행 없이 시뮬레이션해보면, 설정이 올바른지 미리 확인할 수 있습니다.

💡 copytruncate 옵션을 사용하면 파일을 이동하지 않고 복사 후 원본을 비우는데, 파일을 계속 열고 있는 프로세스에 유용합니다.

💡 sharedscripts 옵션을 추가하면 여러 로그 파일이 있어도 postrotate 스크립트를 한 번만 실행해서 효율적입니다.

💡 /var/lib/logrotate/status 파일을 확인하면 각 로그 파일이 마지막으로 언제 로테이트되었는지 알 수 있습니다.


7. 구조화된 로그 - JSON 로그의 장점과 활용

시작하며

여러분이 "지난주 화요일에 특정 사용자가 결제 실패한 원인을 찾아달라"는 요청을 받았을 때, 일반 텍스트 로그에서 해당 정보를 찾기가 얼마나 어려운지 아시나요? grep으로 검색해도 수천 줄 중에서 정확한 라인을 찾기가 정말 힘듭니다.

이런 문제는 실제로 고객 지원이나 버그 조사에서 매일 발생합니다. 텍스트 로그는 사람이 읽기에는 편하지만, 프로그램으로 파싱하기 어렵고, 필드별로 검색하기 힘들며, 통계를 내기도 복잡합니다.

결국 수동으로 로그를 하나씩 읽어야 하는 비효율이 발생합니다. 바로 이럴 때 필요한 것이 JSON 형식의 구조화된 로그입니다.

각 필드가 명확히 구분되어 있어서 파싱이 쉽고, 데이터베이스처럼 쿼리할 수 있으며, 시각화 도구와 연동도 간편합니다.

개요

간단히 말해서, JSON 로그는 로그의 각 항목을 키-값 쌍으로 구조화해서 기록하는 방식으로, 검색과 분석이 훨씬 쉬워집니다. 프로덕션 환경에서 로그를 효과적으로 활용하려면 구조화가 필수입니다.

JSON 형식으로 로그를 기록하면 jq 같은 도구로 즉시 파싱할 수 있고, Elasticsearch에 저장해서 Kibana로 시각화할 수 있으며, 특정 필드만 추출해서 분석할 수 있습니다. 예를 들어, "응답 시간이 3초 이상인 요청 중 사용자 ID가 12345인 것만 찾아줘" 같은 복잡한 쿼리도 한 줄로 처리됩니다.

기존에는 "2025-01-14 10:30:45 [ERROR] User login failed: invalid password" 같은 비구조화된 텍스트였다면, 이제는 {"timestamp":"2025-01-14T10:30:45Z", "level":"error", "event":"login_failed", "userId":12345, "reason":"invalid_password"} 같은 JSON 객체로 기록됩니다. JSON 로그의 핵심 특징은 각 필드를 독립적으로 쿼리할 수 있고, 중첩 객체로 복잡한 데이터 구조를 표현할 수 있으며, 로그 수집 파이프라인(Fluentd, Logstash)이 추가 파싱 없이 바로 처리할 수 있고, 프로그래밍 언어에서 JSON 파서로 쉽게 읽을 수 있다는 것입니다.

이러한 특징들이 대규모 분산 시스템에서 로그를 효율적으로 분석할 수 있게 만듭니다.

코드 예제

const logger = require('winston');

const logFormat = logger.format.combine(
    logger.format.timestamp(),
    logger.format.errors({ stack: true }),
    logger.format.json()
);

const appLogger = logger.createLogger({
    format: logFormat,
    transports: [
        new logger.transports.File({ filename: 'logs/app.json.log' })
    ]
});

// 사용 예시: 구조화된 데이터 로깅
appLogger.info('User login', {
    event: 'user_login',
    userId: 12345,
    email: 'user@example.com',
    ip: '192.168.1.1',
    userAgent: 'Mozilla/5.0...'
});

appLogger.error('Payment failed', {
    event: 'payment_failed',
    userId: 12345,
    orderId: 'ORD-2025-001',
    amount: 50000,
    reason: 'insufficient_balance',
    balance: 30000
});

// 출력 예시:
// {"level":"info","message":"User login","timestamp":"2025-01-14T10:30:45.123Z","event":"user_login","userId":12345,"email":"user@example.com"}

설명

이것이 하는 일: 로그의 모든 정보를 JSON 객체로 구조화해서 저장하므로, 나중에 프로그램으로 쉽게 파싱하고 분석할 수 있습니다. 첫 번째로, Winston의 format 옵션에서 json() 포맷터를 사용합니다.

timestamp()는 ISO 8601 형식의 타임스탬프를 추가하고, errors()는 에러 객체의 스택 트레이스를 자동으로 JSON에 포함시킵니다. 왜 JSON을 사용하냐면, 파싱이 표준화되어 있어서 어떤 언어든 쉽게 읽을 수 있고, 로그 분석 도구들이 JSON을 기본 지원하기 때문입니다.

그 다음으로, 로그를 기록할 때 메시지와 함께 메타데이터 객체를 전달합니다. appLogger.info의 첫 번째 인자는 'User login' 같은 사람이 읽을 메시지이고, 두 번째 인자는 구조화된 데이터입니다.

이 데이터가 JSON의 최상위 레벨에 병합되어 저장됩니다. event 필드로 로그 타입을 분류하고, userId, orderId 같은 식별자를 포함하면 나중에 특정 사용자나 주문의 모든 로그를 추적할 수 있습니다.

마지막으로, 출력된 JSON 로그는 한 줄로 저장됩니다. 여러 줄로 예쁘게 포맷하면 읽기는 편하지만, 파일 크기가 커지고 줄 단위 파싱이 어려워집니다.

한 줄 JSON은 각 로그 엔트리가 독립적인 줄이므로, grep이나 sed로 쉽게 필터링할 수 있습니다. 여러분이 이 방식을 사용하면 jq '.userId' logs/app.json.log 같은 명령어로 모든 userId만 추출할 수 있고, jq 'select(.level=="error" and .amount > 100000)' 같은 복잡한 필터링도 가능하며, ELK Stack에 로그를 전송하면 Kibana에서 시각화된 대시보드를 만들 수 있습니다.

특히 마이크로서비스 아키텍처에서는 각 서비스의 JSON 로그를 중앙 저장소에 모아서 분산 추적(distributed tracing)을 구현할 수 있어, 하나의 요청이 여러 서비스를 거치는 전체 과정을 추적할 수 있습니다.

실전 팁

💡 jq 도구를 설치하면 터미널에서 JSON 로그를 쉽게 필터링하고 포맷팅할 수 있습니다: cat app.json.log | jq '.message'

💡 순환 참조가 있는 객체를 로깅하면 에러가 발생하니, 로그에 포함할 데이터만 선택적으로 추출하세요.

💡 JSON 로그는 텍스트 로그보다 파일 크기가 약간 크지만, 압축하면 차이가 거의 없어집니다.

💡 timestamp를 ISO 8601 형식으로 기록하면 시간대 변환 없이 전 세계 어디서든 정확한 시간을 알 수 있습니다.

💡 level, message, timestamp는 표준 필드로 유지하고, 나머지는 자유롭게 추가하면 로그 분석 도구와 호환성이 좋습니다.


8. 성능 모니터링 - 응답 시간 추적 및 분석

시작하며

여러분이 사용자로부터 "웹사이트가 너무 느려요"라는 불만을 받았을 때, 정확히 어느 부분이 느린지 알 수 있는 방법이 있나요? 막연하게 "서버가 느린 것 같다"는 추측만으로는 문제를 해결할 수 없습니다.

이런 문제는 실제로 서비스 품질에 직접적인 영향을 줍니다. 응답 시간을 측정하지 않으면 어느 API가 병목인지, 데이터베이스 쿼리가 문제인지, 아니면 네트워크 지연인지 알 수 없습니다.

최적화할 곳을 찾지 못하면 무작정 서버만 증설하게 되어 비용만 증가합니다. 바로 이럴 때 필요한 것이 체계적인 성능 모니터링입니다.

각 요청의 응답 시간을 기록하고 분석하면 정확히 어느 부분을 최적화해야 하는지 알 수 있습니다.

개요

간단히 말해서, 성능 모니터링은 애플리케이션의 각 구간별 처리 시간을 측정하고 기록해서 병목 지점을 찾아내는 프로세스입니다. 웹 애플리케이션의 성능을 개선하려면 먼저 현재 상태를 정확히 측정해야 합니다.

응답 시간 추적은 HTTP 요청이 들어온 시점부터 응답을 보낸 시점까지의 시간을 기록하고, 더 나아가 데이터베이스 쿼리 시간, 외부 API 호출 시간, 비즈니스 로직 처리 시간을 각각 측정합니다. 예를 들어, 전체 응답 시간이 2초인데 그 중 1.8초가 데이터베이스 쿼리라면, 쿼리 최적화가 최우선 과제임을 알 수 있습니다.

기존에는 느낌으로 "이 부분이 느린 것 같다"고 추측했다면, 이제는 실제 측정 데이터를 바탕으로 "이 쿼리가 평균 1.2초 걸리므로 인덱스를 추가해야 한다"고 정확히 판단할 수 있습니다. 성능 모니터링의 핵심 특징은 실시간으로 메트릭을 수집할 수 있고, 평균/최대/백분위수(p95, p99) 같은 통계를 계산할 수 있으며, 시간대별 트렌드를 분석해서 피크 타임을 파악할 수 있고, 임계치를 넘으면 자동으로 알림을 보낼 수 있다는 것입니다.

이러한 특징들이 사용자 경험을 저해하는 성능 문제를 조기에 발견하고 해결할 수 있게 만듭니다.

코드 예제

const express = require('express');
const app = express();

// 응답 시간 측정 미들웨어
app.use((req, res, next) => {
    const startTime = Date.now();

    // 응답이 완료될 때 실행될 리스너
    res.on('finish', () => {
        const duration = Date.now() - startTime;

        // 로그에 응답 시간 기록
        logger.info('Request completed', {
            method: req.method,
            url: req.url,
            statusCode: res.statusCode,
            duration: duration,
            userAgent: req.get('user-agent'),
            ip: req.ip
        });

        // 느린 요청 감지 (1초 이상)
        if (duration > 1000) {
            logger.warn('Slow request detected', {
                method: req.method,
                url: req.url,
                duration: duration
            });
        }
    });

    next();
});

// API 엔드포인트 예시
app.get('/api/users', async (req, res) => {
    const dbStart = Date.now();
    const users = await db.query('SELECT * FROM users');
    const dbDuration = Date.now() - dbStart;

    logger.debug('Database query completed', {
        query: 'SELECT users',
        duration: dbDuration,
        rowCount: users.length
    });

    res.json(users);
});

설명

이것이 하는 일: HTTP 요청이 시작되는 시점과 응답이 완료되는 시점의 시간 차이를 계산해서 로그에 기록하고, 임계치를 넘으면 경고를 발생시킵니다. 첫 번째로, Express 미들웨어에서 Date.now()로 요청 시작 시간을 기록합니다.

이 미들웨어는 모든 라우트보다 먼저 실행되므로, 애플리케이션에 요청이 들어오는 순간의 타임스탬프를 정확히 잡을 수 있습니다. 왜 Date.now()를 사용하냐면, process.hrtime()보다 간단하면서도 밀리초 단위 정밀도로 충분하기 때문입니다.

그 다음으로, res.on('finish') 이벤트 리스너를 등록합니다. 'finish' 이벤트는 응답이 클라이언트에게 완전히 전송된 후 발생하므로, 실제 사용자가 체감하는 시간을 정확히 측정할 수 있습니다.

duration을 계산해서 로그에 기록하는데, method와 url을 함께 기록하면 나중에 "GET /api/users 엔드포인트의 평균 응답 시간"을 분석할 수 있습니다. 마지막으로, 조건문으로 느린 요청을 필터링합니다.

duration > 1000은 1초 이상 걸린 요청을 의미하는데, 이런 요청은 warn 레벨로 별도 기록하면 나중에 grep으로 쉽게 찾을 수 있습니다. API 엔드포인트 내부에서도 개별 작업(DB 쿼리, 외부 API 호출)의 시간을 측정하면, 전체 응답 시간 중 어느 부분이 가장 오래 걸리는지 세밀하게 분석할 수 있습니다.

여러분이 이 코드를 사용하면 모든 API의 응답 시간 분포를 파악할 수 있고, 특정 엔드포인트의 성능 저하를 즉시 감지할 수 있으며, 배포 전후 성능 변화를 비교할 수 있습니다. 특히 로그를 시계열 데이터베이스(InfluxDB)에 저장하고 Grafana로 시각화하면, "최근 1주일간 /api/checkout의 p95 응답 시간이 30% 증가했다" 같은 트렌드를 한눈에 볼 수 있어, 성능 회귀를 조기에 발견할 수 있습니다.

실전 팁

💡 process.hrtime.bigint()를 사용하면 나노초 단위 정밀도로 측정할 수 있어, 매우 빠른 함수의 성능도 비교할 수 있습니다.

💡 response-time 패키지를 사용하면 X-Response-Time 헤더를 자동으로 추가해서 클라이언트도 응답 시간을 알 수 있습니다.

💡 APM 도구(New Relic, Datadog)를 연동하면 응답 시간뿐만 아니라 CPU, 메모리, 트랜잭션 추적까지 자동으로 수집됩니다.

💡 slowlog를 설정해서 특정 시간 이상 걸린 요청의 상세 정보(헤더, 바디)를 별도 파일에 기록하면 재현하기 어려운 버그를 디버깅할 수 있습니다.

💡 부하 테스트 도구(Artillery, k6)로 성능 측정을 자동화하면, CI/CD 파이프라인에서 성능 회귀를 자동 감지할 수 있습니다.


9. 에러 추적 및 알림 - Sentry 연동

시작하며

여러분이 운영 중인 서비스에서 에러가 발생했는데, 사용자가 문의하기 전까지 전혀 몰랐던 경험이 있나요? 에러가 발생해도 개발팀이 모르면, 사용자는 계속 불편을 겪게 됩니다.

이런 문제는 실제로 사용자 신뢰를 떨어뜨리는 주요 원인입니다. 로그 파일에 에러가 기록되어 있어도, 누군가 직접 확인하지 않으면 의미가 없습니다.

특히 밤이나 주말에 발생한 에러는 월요일 아침에야 발견되는 경우가 많아, 이미 많은 사용자가 피해를 본 후입니다. 바로 이럴 때 필요한 것이 자동 에러 추적 및 알림 시스템입니다.

Sentry 같은 도구를 사용하면 에러가 발생하는 즉시 슬랙이나 이메일로 알림을 받고, 에러 발생 환경과 스택 트레이스를 자동으로 수집할 수 있습니다.

개요

간단히 말해서, Sentry는 애플리케이션에서 발생하는 모든 에러를 자동으로 수집하고, 팀에게 실시간으로 알림을 보내며, 에러를 재현하는데 필요한 모든 컨텍스트를 제공하는 에러 추적 플랫폼입니다. 프로덕션 환경에서 안정적인 서비스를 제공하려면 에러를 빠르게 감지하고 대응해야 합니다.

Sentry는 JavaScript, Python, Java 등 거의 모든 언어를 지원하고, 에러가 발생하면 자동으로 스택 트레이스, 브레드크럼(에러 발생 전 사용자 행동), 디바이스 정보, 환경 변수 등을 수집해서 대시보드에 보여줍니다. 예를 들어, "iPhone 13에서 iOS 15를 사용하는 사용자가 /checkout 페이지에서 결제 버튼을 클릭했을 때 null reference 에러가 발생했다" 같은 상세 정보를 자동으로 얻을 수 있습니다.

기존에는 사용자가 "에러가 났어요"라고 말하면 "어디서요? 무엇을 하셨나요?"라고 물어봐야 했다면, 이제는 Sentry 대시보드에서 정확한 에러 위치, 발생 빈도, 영향받은 사용자 수를 즉시 확인할 수 있습니다.

Sentry의 핵심 특징은 에러를 자동으로 그룹핑해서 중복을 제거하고, 각 에러의 발생 빈도와 트렌드를 시각화하며, 이미 해결된 에러가 다시 발생하면(regression) 알림을 보내고, GitHub/Jira와 연동해서 이슈를 자동 생성할 수 있다는 것입니다. 이러한 특징들이 개발팀이 가장 심각한 에러부터 우선 처리할 수 있게 만듭니다.

코드 예제

const express = require('express');
const Sentry = require('@sentry/node');

// Sentry 초기화 (앱 시작 시 가장 먼저 실행)
Sentry.init({
    dsn: 'https://your-dsn@sentry.io/project-id',
    environment: process.env.NODE_ENV,

    // 샘플링 비율 (운영 환경에서는 100% 추적하면 비용 증가)
    tracesSampleRate: process.env.NODE_ENV === 'production' ? 0.1 : 1.0,

    // 민감한 정보 필터링
    beforeSend(event, hint) {
        // 쿠키에서 토큰 제거
        if (event.request && event.request.cookies) {
            delete event.request.cookies.authToken;
        }
        return event;
    }
});

const app = express();

// Sentry 요청 핸들러 (모든 라우터보다 먼저)
app.use(Sentry.Handlers.requestHandler());
app.use(Sentry.Handlers.tracingHandler());

// 라우트 정의
app.get('/api/test', (req, res) => {
    throw new Error('Test error for Sentry');
});

// Sentry 에러 핸들러 (모든 라우터 뒤에)
app.use(Sentry.Handlers.errorHandler());

// 커스텀 에러 핸들러
app.use((err, req, res, next) => {
    logger.error('Unhandled error', {
        error: err.message,
        stack: err.stack,
        sentryEventId: res.sentry // Sentry 이벤트 ID
    });

    res.status(500).json({
        error: 'Internal server error',
        eventId: res.sentry // 사용자에게 이벤트 ID 제공
    });
});

설명

이것이 하는 일: 애플리케이션에서 발생하는 모든 예외를 자동으로 Sentry 서버에 전송하고, 에러 컨텍스트(스택 트레이스, 요청 정보, 사용자 정보)를 함께 기록합니다. 첫 번째로, Sentry.init()으로 SDK를 초기화합니다.

dsn은 Sentry 프로젝트의 고유 식별자이고, environment는 개발/스테이징/운영 환경을 구분하는데 사용됩니다. tracesSampleRate는 성능 추적 샘플링 비율인데, 1.0이면 모든 트랜잭션을 추적하고 0.1이면 10%만 추적합니다.

왜 샘플링을 하냐면, 트래픽이 많은 서비스에서 모든 요청을 추적하면 Sentry 비용이 기하급수적으로 증가하기 때문입니다. 그 다음으로, beforeSend 콜백에서 민감한 정보를 필터링합니다.

Sentry는 기본적으로 쿠키, 헤더, 요청 바디를 모두 수집하는데, 여기에 인증 토큰이나 개인정보가 포함될 수 있습니다. beforeSend에서 이런 필드를 삭제하거나 마스킹하면, GDPR 같은 규정을 준수할 수 있습니다.

마지막으로, Express 미들웨어로 Sentry를 통합합니다. requestHandler는 각 요청의 컨텍스트를 수집하고, errorHandler는 발생한 에러를 자동으로 Sentry에 전송합니다.

res.sentry에는 Sentry 이벤트 ID가 저장되는데, 이를 사용자에게 보여주면 "이벤트 ID: abc123"를 고객 지원팀에 전달할 수 있고, 팀은 Sentry에서 정확히 그 에러를 찾을 수 있습니다. 여러분이 이 설정을 사용하면 에러가 발생한 즉시 슬랙 알림을 받을 수 있고, 같은 에러가 여러 번 발생해도 자동으로 그룹핑되어 중복 알림을 방지하며, 브레드크럼으로 에러 발생 전 사용자의 행동을 추적할 수 있습니다.

특히 source map을 업로드하면 난독화/압축된 프로덕션 코드의 스택 트레이스를 원본 소스 코드로 변환해서 보여주므로, 정확히 어느 파일의 몇 번째 줄에서 에러가 발생했는지 알 수 있어 디버깅 시간이 크게 단축됩니다.

실전 팁

💡 Sentry.setUser()로 사용자 ID를 설정하면, 특정 사용자가 겪는 에러만 필터링해서 볼 수 있습니다.

💡 Sentry.setTag()로 커스텀 태그를 추가하면, "payment 태그가 있는 에러만" 같은 세밀한 필터링이 가능합니다.

💡 ignoreErrors 옵션에 "Network request failed" 같은 클라이언트 네트워크 에러를 추가하면, 서버가 제어할 수 없는 에러로 인한 노이즈를 줄일 수 있습니다.

💡 Sentry CLI를 CI/CD 파이프라인에 통합하면, 배포할 때마다 자동으로 소스맵을 업로드하고 릴리즈를 생성할 수 있습니다.

💡 Slack 연동에서 alert rule을 설정하면, 같은 에러가 10회 이상 발생했을 때만 알림을 받아 불필요한 방해를 줄일 수 있습니다.


10. 로그 시각화 및 대시보드 - Grafana 활용

시작하며

여러분이 수천 줄의 로그 파일을 보면서 패턴을 찾으려고 한 적 있나요? 텍스트로만 보면 전체적인 추세나 이상 징후를 파악하기가 정말 어렵습니다.

이런 문제는 실제로 데이터가 많을수록 심각해집니다. 로그를 grep으로 검색할 수는 있지만, "최근 24시간 동안 에러 발생 추이", "시간대별 트래픽 분포", "가장 느린 API Top 10" 같은 인사이트를 얻기는 어렵습니다.

숫자와 텍스트만으로는 직관적인 이해가 불가능합니다. 바로 이럴 때 필요한 것이 로그 시각화 도구입니다.

Grafana를 사용하면 로그 데이터를 그래프, 차트, 테이블로 변환해서 한눈에 서비스 상태를 파악할 수 있습니다.

개요

간단히 말해서, Grafana는 시계열 데이터를 시각화하는 오픈소스 플랫폼으로, 로그와 메트릭을 아름다운 대시보드로 만들어줍니다. 서비스를 효과적으로 모니터링하려면 데이터를 시각적으로 표현해야 합니다.

Grafana는 Prometheus, Elasticsearch, Loki, InfluxDB 등 다양한 데이터 소스와 연동되고, 시계열 그래프, 게이지, 히트맵, 테이블 등 수십 가지 시각화 타입을 제공합니다. 예를 들어, Nginx 액세스 로그를 Loki에 저장하고 Grafana로 시각화하면, "최근 1시간 동안 분당 요청 수", "응답 코드 비율(2xx, 4xx, 5xx)", "p95 응답 시간" 같은 메트릭을 실시간 그래프로 볼 수 있습니다.

기존에는 로그 파일을 스크립트로 파싱해서 수동으로 차트를 만들었다면, 이제는 Grafana 대시보드에서 쿼리만 작성하면 자동으로 실시간 그래프가 생성되고, 임계치를 넘으면 자동 알림도 보냅니다. Grafana의 핵심 특징은 여러 데이터 소스를 하나의 대시보드에서 통합해서 볼 수 있고, 변수를 사용해서 동적 대시보드를 만들 수 있으며, 알림 규칙을 설정해서 특정 조건에서 슬랙/이메일로 알림을 보낼 수 있고, 대시보드를 JSON으로 export/import해서 버전 관리할 수 있다는 것입니다.

이러한 특징들이 개발팀, 운영팀, 경영진이 각자 필요한 관점에서 서비스 상태를 모니터링할 수 있게 만듭니다.

코드 예제

# docker-compose.ymlGrafanaLoki 설정
version: '3'

services:
  loki:
    image: grafana/loki:latest
    ports:
      - "3100:3100"
    volumes:
      - ./loki-config.yaml:/etc/loki/local-config.yaml
    command: -config.file=/etc/loki/local-config.yaml

  promtail:
    image: grafana/promtail:latest
    volumes:
      - /var/log:/var/log
      - ./promtail-config.yaml:/etc/promtail/config.yml
    command: -config.file=/etc/promtail/config.yml

  grafana:
    image: grafana/grafana:latest
    ports:
      - "3000:3000"
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=admin
    volumes:
      - grafana-storage:/var/lib/grafana

volumes:
  grafana-storage:

설명

이것이 하는 일: Docker Compose로 로그 수집(Promtail), 저장(Loki), 시각화(Grafana) 스택을 한 번에 구축하여 로그를 자동으로 수집하고 대시보드에 표시합니다. 첫 번째로, Loki 서비스는 로그를 저장하는 데이터베이스 역할을 합니다.

Elasticsearch보다 훨씬 가볍고, 로그를 인덱싱하지 않고 메타데이터만 인덱싱하므로 저장 공간이 적게 듭니다. 왜 Loki를 사용하냐면, Prometheus와 같은 팀이 만들어서 Grafana와 완벽하게 통합되고, 쿼리 문법(LogQL)도 비슷하기 때문입니다.

그 다음으로, Promtail 서비스는 로그 파일을 읽어서 Loki로 전송하는 에이전트입니다. /var/log 디렉토리를 마운트해서 Nginx, 애플리케이션 로그를 자동으로 수집합니다.

promtail-config.yaml에서 어떤 파일을 읽을지, 어떤 레이블을 추가할지 정의할 수 있습니다. 예를 들어, nginx 로그에는 {job="nginx", env="production"} 같은 레이블을 추가하면, 나중에 이 레이블로 필터링할 수 있습니다.

마지막으로, Grafana 서비스는 웹 UI를 제공합니다. 3000번 포트로 접속하면 대시보드를 만들 수 있는데, Loki를 데이터 소스로 추가하고, LogQL 쿼리로 로그를 조회합니다.

예를 들어, {job="nginx"} |= "error" 쿼리는 Nginx 로그 중 "error" 문자열이 포함된 것만 가져오고, rate({job="nginx"}[5m])는 최근 5분간 초당 로그 발생 건수를 계산합니다. 여러분이 이 스택을 사용하면 여러 서버의 로그를 중앙에서 통합 조회할 수 있고, 시간대별 트래픽 패턴을 그래프로 보면서 서버 증설 시점을 결정할 수 있으며, 에러 발생 빈도가 급증하면 즉시 알림을 받아 장애에 대응할 수 있습니다.

특히 대시보드를 TV 모니터에 띄워두면, 개발팀 전체가 실시간으로 서비스 상태를 공유할 수 있어 문제 발생 시 빠른 커뮤니케이션이 가능합니다.

실전 팁

💡 Grafana의 Variables 기능을 사용하면 하나의 대시보드를 여러 환경(개발/스테이징/운영)에서 재사용할 수 있습니다.

💡 Templating으로 "서버 선택" 드롭다운을 만들면, 사용자가 직접 어느 서버의 로그를 볼지 선택할 수 있습니다.

💡 Grafana Alerting에서 "5분간 에러율이 5%를 넘으면" 같은 조건을 설정하고, 슬랙 채널로 알림을 보내도록 설정하세요.

💡 Annotation을 사용해서 배포 시점을 대시보드에 마킹하면, 성능 변화가 배포와 관련이 있는지 쉽게 파악할 수 있습니다.

💡 Grafana Cloud의 무료 티어를 사용하면 인프라 구축 없이 바로 시작할 수 있고, 데이터 보관 기간도 더 깁니다.


#Nginx#로그관리#모니터링#Winston#성능최적화#Nginx,로그,모니터링

댓글 (0)

댓글을 작성하려면 로그인이 필요합니다.

함께 보면 좋은 카드 뉴스

GitHub와 Vercel로 시작하는 배포 완벽 가이드

코드를 작성했다면 이제 세상에 공개할 차례입니다. GitHub에 코드를 올리고 Vercel로 배포하는 전체 과정을 실무 상황 스토리로 풀어냅니다. 초급 개발자도 따라하면서 자연스럽게 배포 프로세스를 이해할 수 있습니다.

3D 포트폴리오 웹사이트 개발 완벽 가이드

Three.js와 Blender를 활용하여 인상적인 3D 포트폴리오 웹사이트를 만드는 방법을 초급 개발자도 쉽게 이해할 수 있도록 실무 중심으로 설명합니다. 프로젝트 구조부터 접근성까지 모든 과정을 담았습니다.

Three.js 고급 코드 분석 완벽 가이드

Three.js 공식 예제를 분석하고 복잡한 씬 구조를 이해하는 방법부터 커스텀 Shader, PostProcessing, 성능 최적화, 디버깅까지 실무에서 바로 활용할 수 있는 고급 기법을 배웁니다. 초급 개발자도 쉽게 따라할 수 있도록 실무 상황 스토리와 비유로 풀어낸 가이드입니다.

Three.js 충돌 감지 완벽 가이드

3D 웹 게임에서 캐릭터가 벽을 뚫고 지나가는 문제를 해결하는 충돌 감지 기술을 배웁니다. Bounding Box부터 물리 엔진까지 실전 예제로 완벽하게 마스터해보세요.

Three.js Raycaster 상호작용 구현 완벽 가이드

Three.js의 Raycaster를 활용하여 3D 객체와의 상호작용을 구현하는 방법을 초급자 눈높이에서 설명합니다. 마우스 클릭, 터치 이벤트, 하이라이트 효과까지 실전 예제와 함께 배워봅니다.