기준은 하나다
로그의 목적은 일이 벌어진 뒤에 무슨 일이었는지 재구성하는 것이다. 이 기준으로 보면 판단이 단순해진다.
“이 줄이 없으면 장애 상황을 설명할 수 있는가?” 설명할 수 있으면 빼도 된다.
레벨을 기준으로 정한다
레벨을 감으로 붙이면 나중에 아무 의미가 없어진다. 기준을 정해둔다.
- 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는 짧게)
- 초당 수천 건씩 쏟아지는 로그는 샘플링한다
- 반복되는 같은 오류는 묶어서 집계로 남긴다
정리
“많이 남기기”가 아니라 “재구성할 수 있게 남기기”가 목표다. 레벨 기준을 팀에서 합의하고, 개인정보는 애초에 들어갈 수 없게 막아두는 것부터 시작한다.