기준은 하나다

로그의 목적은 일이 벌어진 뒤에 무슨 일이었는지 재구성하는 것이다. 이 기준으로 보면 판단이 단순해진다.

“이 줄이 없으면 장애 상황을 설명할 수 있는가?” 설명할 수 있으면 빼도 된다.

레벨을 기준으로 정한다

레벨을 감으로 붙이면 나중에 아무 의미가 없어진다. 기준을 정해둔다.

  • ERROR — 사람이 개입해야 하는 것. 알림이 울려야 하는 수준
  • WARN — 지금은 넘어갔지만 반복되면 문제. 재시도 성공, 폴백 동작
  • INFO — 시스템의 상태 변화. 기동, 종료, 설정 로드, 주요 비즈니스 이벤트
  • DEBUG — 개발·조사용. 운영에서는 평소에 끈다

가장 흔한 실수는 잡아서 처리한 예외를 ERROR로 남기는 것이다. 정상 흐름으로 복구했다면 ERROR가 아니다. ERROR가 하루 수천 건 쌓이면 아무도 보지 않게 되고, 진짜 오류가 묻힌다.

구조화 로그와 상관관계 ID

문자열을 이어붙인 로그는 사람은 읽어도 검색이 어렵다. JSON으로 남기면 필드로 조회할 수 있다.

{"level":"ERROR","trace_id":"a1b2c3","user_id":1042,
 "event":"payment_failed","reason":"insufficient_balance","amount":15000}

특히 요청마다 고유 ID를 부여하고 모든 로그에 싣는 것이 중요하다. 서비스를 넘나드는 요청도 ID 하나로 전체 흐름을 이어 볼 수 있다. MDC나 트레이싱 컨텍스트로 자동 전파되게 해둔다.

남기면 안 되는 것

로그는 여러 곳에 복제되고 오래 보관되며 접근 권한이 느슨한 편이다. 한 번 찍히면 회수하기 어렵다.

  • 비밀번호, 토큰, API 키, 인증 헤더
  • 주민등록번호, 카드번호, 전화번호, 주소
  • 요청/응답 본문 전체 (위 항목이 섞여 들어온다)

식별이 필요하면 원본 대신 ID나 마스킹된 값을 남긴다. 직렬화 시 특정 필드를 자동으로 가리도록 설정해두는 편이 사람이 매번 조심하는 것보다 안전하다.

비용

로그는 공짜가 아니다. 수집·저장·검색 모두 비용이고, 디스크 I/O는 애플리케이션 성능에도 영향을 준다.

  • 보존 기간을 레벨별로 다르게 둔다 (ERROR는 길게, INFO는 짧게)
  • 초당 수천 건씩 쏟아지는 로그는 샘플링한다
  • 반복되는 같은 오류는 묶어서 집계로 남긴다

정리

“많이 남기기”가 아니라 “재구성할 수 있게 남기기”가 목표다. 레벨 기준을 팀에서 합의하고, 개인정보는 애초에 들어갈 수 없게 막아두는 것부터 시작한다.