기본 콘텐츠로 건너뛰기

로그와 메트릭의 차이: 운영 중인 서비스를 어떻게 관찰할 것인가

로그와 메트릭의 차이: 운영 중인 서비스를 어떻게 관찰할 것인가

빠른 답

  • 로그는 개별 사건의 맥락을 남기고, 메트릭은 시간에 따른 상태 변화를 숫자로 요약한다.
  • 이상 징후 감지와 알림은 메트릭이 빠르고, 원인 분석은 로그가 더 깊은 단서를 준다.
  • System.out.println만으로는 로그 레벨, 포맷, 필터링, 수집 연동, 마스킹을 안정적으로 관리하기 어렵다.
  • 운영 품질은 로그와 메트릭을 따로 도입했는지가 아니라, 탐지부터 분석과 재발 방지까지 이어지는지로 판단하는 편이 좋다.

한눈에 비교

목적
로그는 “무슨 일이 있었는가”를 설명하고, 메트릭은 “상태가 얼마나 변했는가”를 보여준다.
단위
로그는 요청, 예외, 결제 실패 같은 개별 이벤트 단위이고, 메트릭은 요청 수, 오류율, 지연 시간 같은 집계 단위다.
형태
로그는 문자열이나 구조화된 이벤트 기록이고, 메트릭은 이름, 숫자 값, 라벨, 시계열로 구성된다.
강점
로그는 요청 맥락과 예외 추적에 유리하고, 메트릭은 추세 파악, 임계치 알림, 대시보드 구성에 유리하다.
비용
로그는 상세할수록 저장량과 검색 비용이 커지고, 메트릭은 라벨 조합이 늘어날수록 시계열 수가 급격히 증가한다.
사용 시점
메트릭은 “지금 이상한가”를 먼저 알려주고, 로그는 “왜 이상한가”를 좁힐 때 쓰인다.

시간 흐름으로 이해하기

첫 요청 발생
애플리케이션은 요청 ID, 경로, 사용자 범주, 처리 시작 시각 같은 로그 맥락을 만든다.
요청 처리 누적
성공 수, 실패 수, 응답 시간, 큐 적체량 같은 값이 메트릭으로 쌓인다.
관찰 윈도우 계산
모니터링 시스템은 1분, 5분 같은 구간으로 오류율, 평균, 백분위 지연 시간을 계산한다.
임계치 도달
오류율이나 지연 시간이 기준을 넘으면 알림이 발생하고, 담당자는 대시보드에서 흔들린 지표를 확인한다.
원인 추적
같은 시간대의 로그를 요청 ID, API 경로, 상태 코드, 예외명으로 검색해 실패 지점을 좁힌다.

왜 운영 품질의 기준선인가

운영 중인 서비스는 배포가 성공했다는 사실만으로 설명되지 않는다. 사용자가 실제로 요청을 보내고, 외부 API가 응답하고, 데이터베이스와 큐가 함께 움직이는 동안 어디에서 느려지고 어디에서 실패하는지 계속 확인할 수 있어야 한다.

이 관찰 가능성이 부족하면 장애는 “가끔 느려진다”, “특정 사용자가 실패한다”, “배포 뒤부터 이상하다” 같은 모호한 말로 남는다. 원인을 찾는 시간은 길어지고, 같은 문제가 다시 발생해도 재발 방지 기준을 세우기 어렵다.

로그와 메트릭은 기능 요구사항처럼 화면에 바로 드러나지는 않지만, 운영 품질의 기본 장치에 가깝다. 최소한 다음 질문에 답할 수 있어야 한다.

  • 지금 서비스가 정상 범위 안에서 동작하는가?
  • 실패율이 어느 시점부터 증가했는가?
  • 특정 API, 외부 시스템, 배포 버전에 문제가 집중되는가?
  • 사용자가 겪은 실패를 요청 단위로 추적할 수 있는가?
  • 알림이 울렸을 때 원인 분석으로 이어질 증거가 남아 있는가?

로그와 메트릭이 헷갈리는 이유

둘 다 운영 중인 시스템을 보기 위한 데이터라서 처음에는 비슷하게 느껴진다. 예를 들어 ERROR 로그 개수는 숫자로 셀 수 있고, CPU 사용률이 높아진 순간도 하나의 사건처럼 보인다. 그래서 “에러 로그 개수를 세면 메트릭 아닌가”, “메트릭이 있으면 로그를 줄여도 되지 않나” 같은 질문이 생긴다.

차이는 데이터가 답하려는 질문에서 나온다. 로그는 한 번의 요청이 어떤 흐름을 거쳤고, 어떤 값과 외부 응답 때문에 실패했는지 설명한다. 반면 메트릭은 많은 이벤트를 숫자로 접어 “최근 5분 동안 결제 API의 5xx 비율이 3%에서 12%로 올랐다”처럼 상태 변화를 보여준다.

예를 들어 결제 API 장애를 보면 로그는 이런 단서를 남긴다.

2026-04-13T10:15:27.431+09:00 ERROR service=payment-api trace_id=7f3a9c order_id=ORD-1042 path=/payments provider=toss status=502 elapsed_ms=1840 message="payment provider request failed"

이 한 줄은 특정 주문의 결제 요청이 어떤 외부 결제사 호출 중 실패했고, 응답 코드와 소요 시간이 어땠는지 알려준다. 반면 메트릭은 같은 상황을 다음처럼 요약한다.

http_requests_total{service="payment-api",method="POST",path="/payments",status_class="5xx"} 42
http_request_duration_seconds_p95{service="payment-api",method="POST",path="/payments"} 1.82

메트릭은 개별 주문 번호를 알려주지 않는다. 대신 POST /payments에서 실패 수와 p95 지연 시간이 올라갔다는 운영 신호를 빠르게 보여준다.

스택 중립 기준선

도구를 고르기 전에 먼저 정해야 할 기준이 있다. 어떤 로그를 남길지, 어떤 메트릭을 수집할지, 얼마나 오래 보관할지, 어떤 조건에서 알림을 보낼지 정하지 않으면 도구를 붙여도 운영 품질은 크게 좋아지지 않는다.

로그의 최소 필드는 시간, 레벨, 서비스명, 환경, 요청 ID, API 경로, 상태 코드, 지연 시간, 예외명 정도에서 시작할 수 있다. 사용자 ID나 주문 ID처럼 원인 분석에 필요한 값은 남기되, 개인정보와 인증 토큰은 제외하거나 마스킹해야 한다.

메트릭은 요청 수, 오류 수, 응답 시간, 큐 적체량, 외부 호출 실패율, CPU와 메모리 같은 런타임 지표부터 보는 편이 안정적이다. 이때 user_id, order_id, email처럼 값의 종류가 계속 늘어나는 항목을 메트릭 라벨로 넣으면 시계열 수가 폭증한다. 이런 값은 로그에 두고, 메트릭 라벨은 service, method, path, status_class, env처럼 제한된 값으로 관리하는 편이 낫다.

구조화 로그는 수집기가 필드를 파싱하기 쉬운 형태로 남긴다. JSON 로그를 쓰는 경우도 많지만, 아래처럼 로그 포맷 자체를 키-값 형태로 맞추는 것만으로도 검색성이 좋아진다.

timestamp=2026-04-13T10:15:27.431+09:00 level=ERROR service=payment-api env=prod trace_id=7f3a9c method=POST path=/payments status=502 elapsed_ms=1840 message="payment provider request failed"

보관 정책도 품질 기준에 포함된다. 디버그 로그는 짧게 보관하고, 오류 로그와 감사성 이벤트는 더 길게 보관할 수 있다. 다만 오래 보관할수록 비용과 개인정보 관리 부담이 함께 커진다. 로그와 메트릭은 “많을수록 안전한 데이터”가 아니라 “운영 판단에 필요한 만큼 설계해야 하는 데이터”에 가깝다.

장애 대응에서 함께 쓰는 방식

장애 대응은 보통 메트릭에서 시작해 로그로 깊어진다. 메트릭 알림은 오류율 증가, 응답 시간 상승, 커넥션 풀 고갈, 큐 적체 같은 이상 징후를 빠르게 알려준다. 로그는 그다음 단계에서 어떤 요청이 실패했는지, 어떤 예외가 발생했는지, 외부 시스템 응답이 어땠는지를 확인하는 데 쓰인다.

간단한 확인 흐름은 다음과 같다.

  1. 대시보드에서 요청 수, 오류율, 지연 시간, 포화도 지표를 확인한다.
  2. 문제가 시작된 시간대와 영향을 받은 API를 좁힌다.
  3. 같은 시간대의 로그를 trace_id, API 경로, 상태 코드, 예외명으로 검색한다.
  4. 배포, 설정 변경, 외부 API 장애, 데이터베이스 병목 같은 후보를 비교한다.
  5. 재발 방지를 위해 알림 기준, 로그 필드, 테스트, 스모크 검증을 보강한다.

알림은 다음처럼 출발점을 제공한다.

ALERT HighErrorRate
service=payment-api
condition=5xx_rate > 5% for 5m
current=11.8%
since=2026-04-13T10:12:00+09:00

이 출력만으로 원인을 확정할 수는 없다. 하지만 결제 API의 5xx 비율이 5분 동안 11.8%까지 올라갔다는 사실은 알 수 있다. 이후 같은 시간대의 로그를 보면 외부 결제사 502 응답, DB 타임아웃, 인증 토큰 만료, 배포 직후 설정 누락 같은 후보를 더 구체적으로 비교할 수 있다.

설정 예시

Spring Boot 같은 특정 프레임워크를 쓰면 Actuator와 Micrometer로 메트릭을 쉽게 노출할 수 있다. 다만 이 글의 초점은 프레임워크 튜토리얼이 아니라 운영 기준이다. 어떤 스택이든 상태 확인 엔드포인트, 메트릭 노출, 서비스 공통 라벨, 로그 상관관계가 잡혀 있어야 한다.

예를 들어 Spring Boot 환경에서는 다음처럼 상태와 Prometheus 형식 메트릭을 노출할 수 있다. Actuator와 Micrometer의 자세한 옵션은 Spring Boot Actuator 문서Micrometer 문서를 기준으로 확인하는 편이 좋다.

management:
  endpoints:
    web:
      exposure:
        include: health,info,prometheus
  endpoint:
    health:
      probes:
        enabled: true
  metrics:
    tags:
      service: payment-api
      env: prod

logging:
  level:
    root: INFO
    com.example.payment: INFO

이 설정은 /actuator/health/actuator/prometheus를 통해 상태와 메트릭을 확인할 수 있게 한다. 운영에서는 여기에 HTTP 요청 수, JVM 메모리, 스레드 풀, 커넥션 풀, 외부 API 호출 시간 같은 지표를 연결한다.

로그 추적에는 요청 단위 식별자가 필요하다. Java 애플리케이션에서는 MDC를 사용해 같은 요청의 로그에 동일한 trace_id를 넣는 방식이 자주 쓰인다.

try {
    String traceId = request.getHeader("X-Request-Id");
    if (traceId == null || traceId.isBlank()) {
        traceId = UUID.randomUUID().toString();
    }

    MDC.put("trace_id", traceId);
    log.info("request started method={} path={}", request.getMethod(), request.getRequestURI());

    chain.doFilter(request, response);
} finally {
    MDC.clear();
}

MDC.clear()를 빠뜨리면 스레드 재사용 환경에서 이전 요청의 trace_id가 다음 요청 로그에 섞일 수 있다. 이런 문제는 장애 분석을 더 어렵게 만들기 때문에, 로그 품질 기준에 “요청 종료 시 MDC 정리” 같은 세부 규칙을 포함하는 것이 좋다.

운영 확인 예시

배포 후에는 설정이 들어갔다는 사실보다 실제로 관찰 가능한지가 중요하다. 메트릭 엔드포인트가 응답하고, 주요 API 요청 후 카운터가 증가하며, 로그에 요청 ID가 남는지 확인해야 한다.

아래는 로컬이나 스테이징에서 메트릭과 로그를 함께 확인하는 예시다.

$ curl -s http://localhost:8080/actuator/prometheus | grep 'http_server_requests_seconds_count'

http_server_requests_seconds_count{env="prod",method="GET",outcome="SUCCESS",status="200",uri="/orders"} 18342.0
http_server_requests_seconds_count{env="prod",method="POST",outcome="SERVER_ERROR",status="502",uri="/payments"} 42.0

$ grep 'trace_id=7f3a9c' app.log

2026-04-13T10:15:26.102+09:00 INFO service=payment-api trace_id=7f3a9c path=/payments message="request started"
2026-04-13T10:15:27.431+09:00 ERROR service=payment-api trace_id=7f3a9c provider=toss status=502 message="payment request failed"
2026-04-13T10:15:27.438+09:00 INFO service=payment-api trace_id=7f3a9c status=502 elapsed_ms=1840 message="request finished"

이 출력에서는 POST /payments의 서버 오류 카운터가 보이고, 같은 요청 ID를 가진 로그에서 외부 결제사 호출 실패까지 따라갈 수 있다. 메트릭은 조사 대상을 좁히고, 로그는 실패한 요청의 문맥을 복원한다.

실제로 막히는 지점

운영에서 자주 막히는 지점은 “많이 남기면 안전하다”는 생각이다. 로그를 너무 많이 남기면 저장 비용이 커지고, 중요한 로그가 묻히며, 검색 쿼리도 느려진다. 반대로 로그를 너무 줄이면 장애 당시의 요청 맥락이 사라진다. DEBUG 로그는 필요한 구간에 제한적으로 켜고, 운영 기본값은 INFO, WARN, ERROR 중심으로 관리하는 편이 유지하기 쉽다.

메트릭도 비슷하다. 라벨을 많이 붙이면 나중에 자세히 분석할 수 있을 것 같지만, 값의 종류가 제한되지 않은 라벨은 비용과 성능 문제를 만든다. 사용자 ID, 주문 ID, 이메일, 원문 요청 본문은 메트릭 라벨보다 로그 필드나 별도 감사 저장소에 두는 편이 낫다.

System.out.println은 로컬에서 값을 확인하는 정도에는 쓸 수 있지만, 운영 로그의 요구사항을 감당하기 어렵다. 로그 레벨별 필터링, 환경별 출력 제어, 구조화 포맷, 비동기 출력, 수집기 연동, 민감정보 마스킹, 샘플링 같은 기능이 필요해지기 때문이다. 운영 로그는 단순 출력이 아니라 저장, 검색, 보안, 비용을 함께 고려하는 데이터 파이프라인의 일부로 보는 편이 더 현실적이다.

테스트와 품질 게이트

관측성도 테스트 대상에 포함할 수 있다. 모든 로그 문구를 테스트로 고정할 필요는 없지만, 운영 판단에 필요한 최소 증거가 사라지지 않도록 품질 게이트를 두면 배포 후 조사 비용을 줄일 수 있다.

단위 테스트는 로그 마스킹 유틸리티, 메트릭 이름 생성 규칙, 오류 분류 함수처럼 작은 규칙을 검증하기에 좋다. 예를 들어 이메일, 토큰, 주민등록번호 같은 민감정보 패턴이 로그 필드에 남지 않는지 검사할 수 있다.

통합 테스트는 실제 HTTP 요청을 보냈을 때 trace_id가 로그에 포함되는지, 메트릭 카운터가 증가하는지 확인하는 데 맞다. 스모크 테스트는 배포 직후 /health/metrics 또는 /actuator/prometheus가 정상 응답하는지 확인하는 용도로 두기 좋다. 회귀 테스트는 과거 장애에서 빠졌던 로그 필드나 잘못된 알림 조건이 다시 사라지지 않도록 잡는 역할을 한다.

CI 품질 게이트에는 다음 항목을 넣을 수 있다.

  • 민감정보 패턴이 샘플 로그에 포함되지 않는지 검사한다.
  • 메트릭 엔드포인트가 정상 응답하고 주요 지표 이름이 유지되는지 확인한다.
  • 주요 API 통합 테스트에서 요청 ID가 로그에 연결되는지 검증한다.
  • 배포 산출물에 서비스명, 환경, 버전 라벨이 포함되는지 확인한다.
  • 실패 시 테스트 리포트, 샘플 로그, 메트릭 응답, 배포 버전 정보를 함께 남긴다.

이런 증거가 남아 있으면 장애 분석은 추측보다 비교에 가까워진다. 어느 배포부터 오류율이 증가했는지, 어떤 API에 집중됐는지, 같은 요청의 로그가 어디까지 남았는지를 확인할 수 있기 때문이다.

운영 점검 순서

처음부터 완벽한 관측성 구성을 만들기는 어렵다. 작은 기준선을 먼저 만들고, 실제 장애나 리허설을 거치며 보강하는 방식이 더 유지하기 쉽다.

  1. 주요 사용자 흐름을 고른다. 로그인, 주문 생성, 결제, 정산처럼 실패 영향이 큰 흐름부터 시작한다.
  2. 각 흐름에서 성공, 실패, 지연을 관찰할 메트릭을 정한다.
  3. 실패 원인을 추적할 로그 필드를 정한다. 요청 ID, 외부 시스템명, 상태 코드, 예외명은 기본 후보가 된다.
  4. 알림 기준을 정한다. 단일 실패보다 일정 시간 지속된 오류율, p95 지연 시간, 큐 적체량처럼 행동으로 이어질 수 있는 기준을 둔다.
  5. 배포 후 스모크 테스트와 대시보드 확인을 자동화한다.
  6. 장애나 리허설 이후 부족했던 로그 필드, 메트릭, 알림 조건, 회귀 테스트를 보강한다.

도구 이름보다 중요한 것은 알림 이후의 다음 행동이다. Prometheus, Grafana, Grafana Loki, OpenTelemetry 같은 도구는 관찰 데이터를 다루는 데 도움을 주지만, 어떤 질문에 답할지 정하지 않으면 대시보드와 로그 저장소만 늘어난다. 로그와 메트릭은 운영자가 문제를 인지하고, 원인을 좁히고, 재발 방지 기준을 세울 때 가치가 생긴다.

원문 참고

https://www.maeil-mail.kr/question/66

댓글

이 블로그의 인기 게시물

아이콘 폰트 (icomoon 사용법)

 장난감 프로젝트를 만들다 보면, 아이콘이 필요한 경우가 있다. 간단하게 아이콘을 인터넷에서 검색하여, 이미지로 넣어두고 이미지 태그를 이용하여, 사용하는 경우가 일반적이였지만...  요즘에는 대부분 폰트를 이용하여 아이콘을 노출 한다. 나 같은 경우에도 기본적으로  https://material.io/resources/icons 를 참고하여 아이콘 폰트를 이용할 수 있도록 처리하고, 추가적으로 필요한 아이콘이고, 일상적으로 사용 되지 않는 아이콘의 경우에는  https://icomoon.io 에서 제작하여, 아이콘 폰트로 이용 하곤 한다.  그래서 이번에는 아이콘  https://icomoon.io 의 사용법을 간단히 공유하고자 한다.   들어가자 마자 위의 icoMoonApp버튼을 누르면 아래와 같은 화면이 나타난다.  icomoon에서 무료로 제공하는 아이콘들이 보이면 위에 파란색으로 표시 되어있는 집 모양 세가지를 선택한 후, 아래의 빨간색으로 표시되어있는 Generate Font를 눌러보자.  그리고 나서 바로 다운로드를 요청해보자. icomoon.zip이 다운로드가 될텐데, 압축을 해제해 보면, 아래의 폴더 및 파일들이 있다. 아래에서 중요한 것은 font 폴더와 style.css이다. demo-files fonts demo.html Read Me.txt selection.json style.css <!doctype html > <html> <head> <link rel ="stylesheet" href ="style.css" ></head> </head> <body> <span class ="icon-home" ></span> <span class ="icon-home2" ></span> <span class ="icon-hom...

Chart js와 amchart 비교

Chart js 특징은 위의 그림으로 대체 할 수 있을 듯 하다. 오픈 소스이고, 기본으로 제공하는 차트 종류가 8가지 Canv a s를 이용해서 차트를 그리고, 반응형을 지원한다. amchart amchart는 기본적으로 유료이며, 기본으로 제공하는 차트 종류가 기본적인 차트 + 주식 처럼 보이는 차트 + 지도에 관련된 차트(?) 까지 하면, 기본 제공 하는 종류가 20개 내외 이려나, 일일이 세기에는 양이 좀 많아 보인다. 렌더링은 svg를 통하여 그려지고, 당연 반응형도 지원이 된다. 그러면, 이 둘중에 어떤것이 내 프로젝트에 적합 하냐는 것이 문제이다. 일단, 주식 처럼 보이는 차트나 지도에 관련된 차트(?)가 필요하면, amchart를 선택해야 되는 것은 맞다. 그건 당연한 것이니 빼고 얘기 해보자! 여러 종류의 차트가 필요하다면, 일단은 amchart를 염두해 두는 것이 좋다. 돈 낸 만큼은 하는 듯 하다. 하지만, 기본적인 막대 그래프, 도넛 차트 등, 아주 기본적인 차트들인데, Chart js도 amchart도 그러한 차트가 없을 때가 문제가 된다. 그렇다면, 조금이라도 커스텀이 용이한 것을 찾는 것이 좋을 것이다.  일단 amchart에서 custom이라고 검색 하였을 때, 검색 결과가 61가지가 나온다. 차트의 종류도 많고, 각 차트마다 들어가는 속성이 매우 많기 때문에, 웬만한 내용들은 속성 값을 어떻게 주느냐에 따라서 변경이 가능 하게 된다. 커스텀의 예를 들면, 기본적으로 도넛 파이의 형태를 띄면서, 화살표로 목표를 표시해주는 차트가 필요하다고 생각 해보자. 이것은 amchart로 만든 그래프이고 이것은 chart js로 만든 그래프이다. 모양이 살짝 다르긴 하지만, 완벽하게 똑같이 구현 할 수도 있다. amchart로 만든 그래프의 경우, 저것은 도넛그래프가 아닌 guage 그래프이다. 원래 게이지 그래프는 이와 같...

javascript 압축 파일 다운로드

이번에는 전 게시글의 응용판? 이라고 해야하나....? 어쨋든! 우리는 각각의 파일들을 다운로드 해보았다. 그런데 생각보다 귀찮음?을 느꼇을 것이다. 파일을 각각 다운 받아야 한다는 현실때문에! 그래 파일 두개야 뭐 그렇다 치지... 하지만, 개발자도 사용자도 게으름뱅이이다. 자 결국, 우리가 해야 하는 것은 파일을 한 번에 둘다! 다운 받는 것이다. 물론, 클릭 한번에 여러개의 함수를 엮어서 다운받게 하면 되지만! 크롬에서 자주 봤듯이, 여러개의 파일을 다운로드를 시도하면 <- 여러개의 파일을 다운로드 합니다. 허용 합니까? 하고 물어보는 것을 볼 수 있다. 게다가 다운로드 한 파일들을 찾기도 귀찮다는 것. 자 해결책을 제시해보자면, https://github.com/Stuk/jszip 클라이언트 단에서 파일을 zip파일로 압축을 할 수가 있다! 필요한 작업은 아래와 같다. 0. 데이터 준비 1. BLOB(binary large object)를 만든다. 2. Blob을 URL.createObjectURL을 사용하여, 해당 binary의 주소를 생성. 3. 다운로드가 필요한 파일들을 Zip 객체에 셋팅! 4. a태그를 이용하여, 해당 url 셋팅 하고, 다운로드. 전 게시물과 별로 달라진게 없네... 자 그럼 샘플! 샘플을 보자! http://embed.plnkr.co/NMprnRxqYG0fkHa2J55D/ var util = {} function fixBinary(bin) { //binary to arrayBuffer var length = bin.length var buf = new ArrayBuffer(length) var arr = new Uint8Array(buf) for (var i = 0; i < length; i++) { arr[i] = bin.charCodeAt(i) } return buf } window.onload = function() { ...