구조화 로깅, 장애 때 찾을 수 있는 로그 남기기

2026. 10. 15. 13:00·IT
728x90
반응형

장애가 났을 때 로그를 열었는데 원인을 못 찾은 경험이 있다면, 로그를 남기는 방식을 바꿀 때가 된 것이다. 로그는 많이 남긴다고 좋은 것이 아니라 찾을 수 있어야 쓸모가 있다. 이 글은 구조화 로깅으로 바꾸는 방법과, 무엇을 남기고 무엇을 남기지 말아야 하는지를 정리한다.

문자열 로그는 검색할 수 없다

흔한 로그는 이렇게 생겼다.

2026-09-11 13:02:11 INFO 주문 처리 완료 - 주문번호: 10023, 사용자: 42, 금액: 35000원

사람이 읽기에는 괜찮다. 문제는 기계가 읽을 수 없다는 것이다. "금액이 10만 원 넘는 주문 중 실패한 것"을 찾으려면 정규식을 짜야 하고, 로그 형식이 조금만 달라도 깨진다.

구조화 로깅은 같은 내용을 이렇게 남긴다.

{"ts":"2026-09-11T13:02:11Z","level":"info","msg":"order.completed",
 "order_id":10023,"user_id":42,"amount":35000,"trace_id":"a3f9c1"}

이제 amount > 100000 AND level = "error" 같은 조건으로 바로 검색된다. 대시보드에 금액 분포를 그리는 것도 가능하다. 로그가 데이터가 된다.

메시지를 "주문 처리 완료" 대신 "order.completed"처럼 고정된 식별자로 쓰는 것도 요령이다. 문구가 바뀌어도 검색 조건이 안 깨지고, 같은 이벤트를 세기 쉽다.

요청 하나를 꿰는 trace_id

마이크로서비스가 아니더라도 이건 필요하다. 요청 하나가 처리되는 동안 남은 로그를 한 줄로 묶어 볼 수 있어야 한다.

구현은 간단하다. 요청이 들어올 때 ID를 만들어 컨텍스트에 넣고, 모든 로그에 자동으로 붙인다. 외부로 호출할 때는 헤더(X-Request-Id나 W3C traceparent)로 전달한다.

이게 있으면 장애 대응이 달라진다. 사용자가 "3시 5분쯤 결제가 안 됐어요"라고 하면, 그 시각의 에러 로그에서 trace_id를 찾고 그것으로 전체 흐름을 복원한다. 없으면 여러 서비스의 로그를 시각으로 대조해야 하는데, 초당 수백 건이 찍히는 환경에서는 사실상 불가능하다.

레벨을 실제로 구분해서 쓴다

로그 레벨이 형식적으로만 존재하는 경우가 많다. 기준을 정해두면 유용해진다.

레벨 기준 예
ERROR 사람이 대응해야 함 결제 승인 실패, DB 연결 끊김
WARN 지금은 괜찮지만 추세를 봐야 함 재시도 성공, 폴백 동작, 응답 지연
INFO 정상 흐름의 주요 사건 주문 생성, 로그인, 배치 시작·종료
DEBUG 개발·조사용 상세 쿼리 파라미터, 중간 계산값

ERROR는 알림과 연결된다는 전제로 써야 한다. 사용자 입력이 잘못된 것(400)까지 ERROR로 찍으면 알림이 계속 울리고, 사람들은 곧 알림을 무시하게 된다. 그러면 진짜 장애도 묻힌다.

반대로 빠지기 쉬운 실수는 예외를 잡고 로그만 남긴 뒤 그냥 넘어가는 것이다.

try:
    charge(order)
except Exception as e:
    log.error("결제 실패", error=str(e))   # 그리고 끝? 주문은 어떻게 되나

로그는 남았지만 주문 상태는 애매하게 남는다. 로그를 남기는 것과 처리하는 것은 다른 일이다.

예외는 스택 트레이스와 함께

에러 로그에서 가장 중요한 것은 어디서 났는지다. 메시지만 남기면 같은 메시지가 여러 곳에서 나올 때 구분할 수 없다.

# 나쁨 — 어디서 났는지 모른다
log.error("결제 실패: " + str(e))

# 좋음 — 스택 트레이스가 함께 남는다
log.exception("payment.failed", order_id=order.id)

파이썬은 logger.exception(), 자바는 예외 객체를 마지막 인자로 넘기면 스택이 남는다. 구조화 로깅 라이브러리들은 이걸 stack 필드로 분리해 준다.

원인 예외(cause chain)도 중요하다. 라이브러리 예외를 잡아 도메인 예외로 바꿔 던질 때, 원래 예외를 버리면 진짜 원인이 사라진다. 파이썬은 raise MyError() from e, 자바는 생성자에 cause를 넘긴다.

절대 로그에 남기면 안 되는 것

이건 보안 문제라 예외가 없다.

비밀번호, 토큰, API 키, 세션 ID. 로그는 여러 시스템에 복제되고 오래 보관된다. 한 번 들어가면 회수할 수 없다.

주민등록번호, 카드번호, 계좌번호 같은 개인정보. 마스킹해서 남기거나 아예 남기지 않는다. card_last4 정도면 조사에 충분하다.

요청 본문 전체. 편해 보이지만 위 항목이 그대로 들어간다. 필요한 필드만 골라 남긴다.

실수를 막으려면 로거 단에서 거르는 것이 확실하다. 필드 이름에 password, token, secret, authorization이 들어가면 값을 ***로 바꾸는 필터를 넣어둔다. 사람이 매번 조심하는 방식은 언젠가 뚫린다.

로그를 많이 남기는 것이 답은 아니다

마지막으로 균형이다. 로그는 비용이 든다. 저장 비용, 전송 대역, 그리고 디스크 I/O다. 초당 수천 건을 찍는 서비스에서 로그가 성능 병목이 되는 경우가 실제로 있다.

그래서 나는 "나중에 볼 로그"와 "지금 찍는 로그"를 구분하라고 권한다. 장애 조사에 쓸 로그는 INFO 이상으로 남기고, 개발 중 확인용은 DEBUG로 두고 프로덕션에서는 끈다. 반복문 안에서 매 항목마다 로그를 찍는 코드는 대개 DEBUG여야 한다.

그리고 로그만으로 모든 걸 해결하려 하지 않는 편이 낫다. 지표(metric)로 볼 것을 로그로 세지 않는다. "초당 요청 수"나 "평균 응답 시간"은 카운터와 히스토그램으로 수집하는 편이 훨씬 싸다. 로그는 개별 사건의 맥락을 보는 도구이고, 지표는 전체 추세를 보는 도구다. 둘을 섞으면 비용은 로그만큼 들고 효용은 지표만 못하다.

참고 자료

  • OpenTelemetry: Logs — 구조화 로그와 트레이스의 연결
  • W3C Trace Context — 서비스 간 trace_id 전달 표준
  • OWASP Logging Cheat Sheet — 로그에 남기면 안 되는 데이터와 보호 방법
728x90
반응형

'IT' 카테고리의 다른 글

캐시 전략, 무효화와 스탬피드 대응  (0) 2026.10.14
N+1 쿼리 문제, 발견하고 고치는 법  (0) 2026.10.13
인덱스를 만들었는데 쿼리가 느린 이유  (0) 2026.10.12
AI 코딩 에이전트 시대의 코드 리뷰  (0) 2026.10.11
Git 브랜치 전략, rebase와 merge 중 무엇을 쓸까  (0) 2026.10.10
'IT' 카테고리의 다른 글
  • 캐시 전략, 무효화와 스탬피드 대응
  • N+1 쿼리 문제, 발견하고 고치는 법
  • 인덱스를 만들었는데 쿼리가 느린 이유
  • AI 코딩 에이전트 시대의 코드 리뷰
밍글링글링
밍글링글링
mingling - 밍글링, 밍글밍글링. 코드와 어우러지다. IT/ 프로그래밍/소스
    반응형
    250x250
  • 밍글링글링
    mingling
    밍글링글링
  • 전체
    오늘
    어제
    • 밍글링글링 (451) N
      • Flutter (2)
      • 일상생활 (8)
        • 리뷰 (1)
        • 생활정보 (4)
        • 맛집 (0)
        • 여행 (0)
        • 모든정보 (3)
      • JAVA (126)
        • 개념 (6)
        • 예제 (115)
        • Exception (2)
      • C (1)
        • C (1)
        • C++ (0)
        • C# (0)
      • JS (29)
        • JavaScript (18)
        • JQuery (5)
        • AJax (0)
        • NODE.JS (6)
        • Angular.JS 2.0 (0)
      • WEB (87)
        • HTML (6)
        • CSS (61)
        • JSP (20)
        • JSTL (0)
      • FrameWork (8)
        • Spring (8)
        • BootStrap (0)
        • MyBATIS (0)
        • JUnit (0)
      • 외부 라이브러리 (5)
      • 공유 소스 관리 (5)
        • Git (5)
        • SVN (0)
      • 빅데이터 프로그래밍 (37)
        • Python (37)
        • R Programming (0)
      • DB (7)
        • ORACLE (0)
        • MySql (6)
      • Development Tools (7)
        • StarUML (0)
        • eXERD (0)
        • Eclipse (4)
      • SKILL (6)
        • Migration (0)
        • Security (6)
      • MicroSoft (0)
        • Excel (0)
        • Word (0)
      • Android (0)
      • Server (21)
        • Ubuntu (5)
        • Linux (15)
      • IOS (0)
      • XML (0)
      • 미디어 (0)
      • 공지사항 (3)
      • NETWORK (1)
      • 게임 (4)
        • 피파 (1)
        • 리니지M (0)
        • 배틀그라운드 (1)
        • 듀랑고 (2)
      • 세상 이슈 (6)
      • 일렉트론 (0)
      • 대회 소식 (4)
      • 업무 (2)
      • Express, Vue (6)
      • docker (11)
      • svelte (3)
      • 블록체인 (1)
      • IT (54) N
      • Rust (0)
  • 블로그 메뉴

    • 홈
    • 태그
    • 미디어로그
    • 위치로그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    gitlab 설치
    Extension Bisect
    ubuntu
    VS Code 팁
    vue cli
    spring java
    docker
    mysql db
    자바 클래스
    제미나이
    자바 exception
    자바 배열
    vscode
    css transition
    css tb
    리눅스 설치
    API 설계
    AI 반도체
    rust linux
    Node
    proxy pass
    에디터 팁
    servlet class
    java casting
    러스트
    Rust lang
    티스토리 자동화
    css table
    jsp include
    클론코딩
    자바 객체 지향
    프런트엔드
    svelte
    nginx
    jsp parameter
    자바 for문
    SSL 인증서
    React Compiler
    nginx ssl 설정
    오류
    lang rust
    ssl 인증서 발급
    vue 설치
    자바 생성자
    Java Array
    nginx ssl 적용
    AI 코딩 에이전트
    브라우저 자동화
    css float
    css perspective
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.6
밍글링글링
구조화 로깅, 장애 때 찾을 수 있는 로그 남기기
상단으로

티스토리툴바