구조화 로그와 추적 ID로 장애를 빠르게 추적하는 법

새벽에 “결제가 안 돼요”라는 문의가 들어왔습니다. 로그를 열었더니 수천 줄의 평문이 시간순으로만 쏟아져, 그 사용자의 요청이 어디서 깨졌는지 찾는 데 한참이 걸립니다. 구조화 로그와 추적 ID는 바로 이 “한참”을 몇 초로 줄여 줍니다. 로그를 사람이 읽는 문장이 아니라 기계가 검색·집계할 수 있는 데이터로 남기고(구조화 로그), 한 요청에 고유한 추적 ID를 붙여 여러 서비스에 걸친 흐름을 한 줄로 꿰면(trace ID), “그 요청만” 골라 처음부터 끝까지 따라갈 수 있습니다. 이 글에서는 평문 로그의 한계, JSON 구조화, 추적 ID 전파, 민감정보 마스킹과 보존, 그리고 작은 사이트가 바로 쓸 수 있는 설정까지 정리합니다.

평문 로그는 왜 한계에 부딪히나

가장 흔한 로그는 사람이 읽기 좋은 한 줄짜리 문장입니다. 2026-06-14 03:12:01 결제 실패 user=kim error=timeout 같은 형태입니다. 개발 중 콘솔에서 눈으로 훑기에는 편하지만, 장애 추적과 운영에서는 금세 한계를 드러냅니다.

첫째, 검색이 어렵습니다. “특정 사용자의 결제 실패만” 보려면 문자열 패턴을 직접 짜야 하고, 메시지 포맷이 조금만 달라도 누락됩니다. 둘째, 집계가 안 됩니다. “최근 한 시간 결제 실패가 몇 건인지”를 세려면 줄을 파싱해야 하는데, 자유 문장은 기계가 안정적으로 파싱하기 어렵습니다. 셋째, 흐름이 끊깁니다. 여러 요청의 로그가 시간순으로 뒤섞이면, 한 요청이 거친 단계들이 다른 요청 로그 사이에 흩어져 이어 붙이기 힘듭니다. 서비스가 여러 개로 나뉘면 이 문제는 더 심해집니다.

핵심 원인은 로그가 “데이터”가 아니라 “문장”이라는 데 있습니다. 사람이 읽기 위한 자유 텍스트는 기계가 다루기에 부적합합니다. 구조화 로그는 이 전제를 뒤집어, 로그를 처음부터 기계가 읽을 수 있는 키-값 데이터로 남깁니다.

JSON 구조화 로그 — 기계가 읽는 로그

구조화 로그는 각 로그 줄을 JSON 같은 정형 포맷으로 남기는 방식입니다. 메시지를 문장에 욱여넣는 대신, 의미 있는 값을 각각 필드로 분리합니다.

// 평문
2026-06-14 03:12:01 결제 실패 user=kim error=timeout

// 구조화(JSON)
{
  "ts": "2026-06-14T03:12:01Z",
  "level": "error",
  "event": "payment_failed",
  "user_id": "u_8231",
  "order_id": "o_55012",
  "error": "gateway_timeout",
  "duration_ms": 4980,
  "trace_id": "a1b2c3d4e5"
}

이렇게 남기면 로그 수집·검색 도구에서 event = payment_failed AND error = gateway_timeout처럼 필드로 정확히 조회할 수 있고, “결제 실패를 오류 유형별로 집계”하거나 “응답 시간 상위 요청만” 뽑는 분석이 가능해집니다. 사람이 읽기 위한 메시지는 event·level로 충분히 전달되므로 가독성도 크게 떨어지지 않습니다.

실무에서는 직접 JSON 문자열을 만들지 말고 구조화 로깅 라이브러리를 쓰는 편이 안전합니다. 언어마다 표준적으로 쓰이는 구조화 로거가 있고, 대부분 필드 추가·로그 레벨·출력 포맷을 일관되게 처리해 줍니다. 중요한 것은 팀 전체가 같은 필드 이름 규칙(예: 사용자 식별자는 항상 user_id)을 쓰는 것입니다. 같은 개념을 서로 다른 키로 남기면 구조화의 이점이 사라집니다.

추적 ID — 한 요청을 끝까지 따라가기

추적 ID는 하나의 요청에 부여하는 고유 식별자입니다. 사용자의 클릭 한 번이 웹 서버, 인증, DB, 외부 결제 API를 거친다면, 그 전 과정에서 같은 ID를 모든 로그에 함께 남깁니다. 그러면 나중에 그 ID 하나로 필터링했을 때, 그 요청이 거친 모든 단계가 시간순으로 한 줄에 꿰어집니다.

용어를 구분해 두면 도움이 됩니다. request ID는 보통 단일 서비스 안의 한 요청을 가리키고, trace ID는 여러 서비스에 걸친 하나의 흐름 전체를 가리킵니다. 그 흐름 안의 각 구간(예: DB 호출 하나)은 span으로 나누고 span ID로 식별합니다. 이 추적 정보를 서비스 간에 어떻게 전달할지는 W3C Trace Context 명세가 traceparent 헤더로 표준화해 두었고, OpenTelemetry 같은 도구가 이를 구현합니다.

용어범위역할
request ID단일 서비스의 한 요청그 서비스 내부 로그 묶기
trace ID여러 서비스를 가로지른 흐름 전체전 구간을 하나로 연결
span ID흐름 안의 한 구간(작업)구간별 소요·관계 파악

ID를 만드는 시점은 보통 요청이 시스템에 처음 들어오는 입구입니다. 들어온 요청에 추적 ID 헤더가 이미 있으면 그대로 이어받고, 없으면 새로 생성합니다. 그리고 내부에서 다른 서비스를 호출할 때 그 ID를 헤더로 함께 넘겨, 다음 서비스가 같은 ID로 로그를 남기게 합니다. 이 “전파”가 끊기면 흐름도 끊깁니다.

추적 ID 전파와 상관관계 분석

전파를 매번 손으로 챙기면 빠뜨리기 쉽습니다. 그래서 보통은 미들웨어에서 한 번 처리해 둡니다. 요청 입구에서 추적 ID를 확보해 요청 컨텍스트에 담아 두고, 그 요청을 처리하는 동안 찍히는 모든 로그에 자동으로 그 ID가 붙도록 만드는 방식입니다.

// 의사코드: 요청 입구 미들웨어
function withTrace(req, res, next) {
  const traceId = req.headers["traceparent"] ?? generateId();
  // 이 요청 동안의 모든 로그에 trace_id 자동 부착
  logContext.run({ trace_id: traceId }, () => {
    res.setHeader("trace-id", traceId);  // 응답에도 노출
    next();
  });
}

추적 ID를 응답 헤더나 사용자에게 보이는 오류 화면에 함께 노출해 두면, 문의가 들어왔을 때 사용자가 알려 준 ID 하나로 해당 요청 로그를 즉시 찾을 수 있습니다. 이것이 상관관계 분석의 출발점입니다. 장애가 났을 때 “에러가 난 trace_id”를 찾고, 그 ID로 전체 로그를 필터링하면, 요청이 어느 서비스의 어느 단계에서 느려졌거나 실패했는지가 한눈에 드러납니다. 평문 로그를 헤매던 작업이 ID 한 줄 필터로 끝납니다.

로그 레벨과 민감정보 마스킹

로그를 잘 남기는 것만큼 “무엇을 어느 수준으로 남길지”도 중요합니다. 로그 레벨은 메시지의 심각도를 나눠, 평소에는 중요한 것만 보다가 문제가 생기면 상세 로그를 켜는 식으로 신호 대 잡음비를 조절하는 장치입니다.

레벨용도예시
DEBUG개발·심층 진단용 상세변수 값, 분기 흐름
INFO정상 동작의 주요 이벤트요청 수신, 작업 완료
WARN당장 장애는 아니나 주의재시도 발생, 한도 임박
ERROR처리 실패, 조치 필요예외, 외부 호출 실패

가장 주의할 점은 민감정보입니다. 비밀번호, 카드번호, 주민등록번호, 인증 토큰, 세션 쿠키 같은 값은 로그에 남기면 안 됩니다. 로그는 여러 사람이 보고 오래 보관되므로, 한 번 새면 피해가 큽니다. OWASP 등 보안 가이드도 민감정보를 로그에서 제외하거나 마스킹하라고 권고합니다. 실무에서는 요청 본문을 통째로 찍지 않고, 로깅 단계에서 특정 필드를 자동으로 가리는 마스킹 처리를 둡니다.

// 마스킹 예시
{
  "event": "login_attempt",
  "user_id": "u_8231",
  "password": "***",          // 절대 원문 금지
  "card_no": "****-****-****-1234",  // 일부만
  "auth_token": "[REDACTED]"
}

보존, 검색, 알림 연계

로그는 쌓이기만 하면 비용이 되고, 너무 빨리 지우면 추적할 수 없습니다. 그래서 보존 정책이 필요합니다. 최근 로그는 빠르게 검색되는 저장소에 두고, 오래된 로그는 값싼 저장소로 옮기거나 만료시키는 식으로 단계를 나눕니다. 적절한 보존 기간은 서비스 성격과 법적 요구에 따라 다르므로, 관련 규정과 내부 정책을 확인해 정하는 것이 맞습니다.

검색은 구조화 로그의 가장 큰 보상입니다. 로그를 수집·검색 도구에 모아 두면 필드 단위 질의, 시간대별 집계, 대시보드 시각화가 가능해집니다. 여기서 한 걸음 더 나아가, 특정 조건의 로그가 임계치를 넘으면 자동으로 알림을 보내도록 연계할 수 있습니다.

  • ERROR 레벨 로그가 5분에 N건 이상 발생하면 알림
  • 특정 event(예: payment_failed)가 평소보다 급증하면 알림
  • 응답 시간(duration_ms)이 임계치를 넘는 요청이 잦으면 알림
  • 알림 메시지에 trace_id를 포함해, 받자마자 해당 요청을 바로 조회

알림에 trace_id를 함께 실어 보내면, 담당자가 알림을 받는 즉시 그 ID로 전체 흐름을 열어 볼 수 있어 대응 시간이 크게 줄어듭니다. 다만 알림은 적을수록 좋습니다. 사소한 경고까지 모두 알림으로 보내면 정작 중요한 신호가 묻히는 경고 피로가 생깁니다. 처음에는 사용자에게 직접 영향이 가는 오류 위주로 좁게 걸고, 운영하면서 빠진 것을 더해 가는 편이 안전합니다.

작은 사이트의 실전 설정

규모가 작다면 처음부터 복잡한 관측 인프라를 깔 필요는 없습니다. 적은 노력으로 큰 효과를 보는 순서는 다음과 같습니다.

  • 표준 출력으로 JSON 한 줄씩 찍기 — 별도 수집기 없이도 호스팅 플랫폼의 로그 화면에서 검색됩니다(12-factor 원칙).
  • 요청 입구 미들웨어에서 trace_id를 생성·전파하고 모든 로그에 자동 부착하기.
  • 민감 필드 마스킹을 로깅 유틸에 한 번 심어, 실수로도 원문이 안 찍히게 하기.
  • 로그 레벨을 환경별로 분리 — 운영은 INFO 이상, 문제 진단 시 임시로 DEBUG.
  • 트래픽이 늘면 그때 로그 수집·검색 도구와 알림 연계를 추가하기.

핵심은 “처음부터 구조화와 trace_id만 지켜 두면, 나중에 도구를 붙일 때 그대로 활용된다”는 점입니다. 반대로 평문으로 쌓아 두면 나중에 도구를 도입해도 과거 로그는 검색·집계가 어렵습니다.

민감정보가 로그에 남으면 그 자체가 보안 사고입니다.

비밀번호·카드번호·토큰을 로그에 찍는 코드는 한 번 배포되면 그 시점부터 남는 모든 로그에 흔적을 남깁니다. 로그는 보관 기간이 길고 접근자가 많아 노출 위험이 큽니다. 새 기능을 배포하기 전에 어떤 값이 로그로 나가는지 점검하고, 마스킹을 코드 레벨에서 강제하세요.

적용 체크리스트

  • 로그를 JSON 등 구조화 포맷으로, 일관된 필드 이름 규칙으로 남기는가
  • 요청 입구에서 trace_id를 생성·이어받고 모든 로그에 자동 부착하는가
  • 내부 서비스 호출 시 추적 ID를 헤더로 전파하는가
  • trace_id를 응답·오류 화면에 노출해 문의 추적을 쉽게 했는가
  • 비밀번호·카드·토큰 등 민감 필드를 마스킹·제외하는가
  • 로그 레벨을 환경별로 분리하고 운영에서 DEBUG를 끄는가
  • 보존 기간을 정책·법령에 맞춰 정하고 오래된 로그를 정리하는가
  • ERROR 급증·지연 등 조건에 알림을 걸고 trace_id를 함께 보내는가

자주 묻는 질문

request ID와 trace ID는 무엇이 다른가요?
request ID는 보통 단일 서비스 안의 한 요청을 가리키고, trace ID는 여러 서비스를 가로지른 하나의 흐름 전체를 가리킵니다. 흐름 안의 각 구간은 span으로 나눠 span ID로 식별합니다.
작은 사이트도 구조화 로그가 필요한가요?
필요합니다. 규모가 작아도 장애는 납니다. 표준 출력으로 JSON을 찍고 trace_id만 붙여 두면 추가 인프라 없이도 추적이 쉬워지고, 나중에 도구를 붙일 때 과거 로그까지 활용됩니다.
어떤 정보를 로그에 남기면 안 되나요?
비밀번호, 카드번호, 주민등록번호, 인증 토큰, 세션 쿠키 같은 민감정보입니다. 보안 가이드(OWASP 등)도 제외·마스킹을 권고합니다. 로깅 유틸에서 해당 필드를 자동으로 가리도록 강제하세요.
trace ID는 어디서 만들어야 하나요?
요청이 시스템에 처음 들어오는 입구에서 만듭니다. 들어온 요청에 추적 ID 헤더가 있으면 그대로 이어받고, 없으면 새로 생성한 뒤, 내부 호출 시 헤더로 전파합니다. W3C Trace Context의 traceparent 헤더가 표준입니다.
로그는 얼마나 오래 보관해야 하나요?
정답이 정해져 있지 않습니다. 서비스 성격과 법적·규제 요구에 따라 다르므로, 관련 규정과 내부 정책을 확인해 정해야 합니다. 최근 로그는 검색이 빠른 저장소에, 오래된 로그는 값싼 저장소로 옮기는 단계 구성이 일반적입니다.

로그에 남기면 안 되는 민감정보와 안전한 로깅 설계 권고는 OWASP 공식 문서에서 확인할 수 있습니다.

이 글은 구조화 로그와 추적 ID를 활용한 장애 추적의 원리와 실무 설정을 일반적인 관점에서 정리한 참고 자료입니다. 로그 보존 기간과 개인정보 처리 의무는 서비스 성격과 관할 법령에 따라 다르므로 반드시 관련 규정과 내부 정책을 확인하시기 바랍니다. 추적 컨텍스트 전파(W3C Trace Context), 관측 도구(OpenTelemetry 등), 로깅 프레임워크의 구체 설정은 버전·시점에 따라 달라질 수 있으니 공식 문서를 함께 확인하세요. 본문의 코드는 개념 설명용 의사코드입니다.

위로 스크롤