TECH NOTES
구조화 로깅 도입하기 — 검색 가능한 로그로 바꾸는 절차
로그가 검색되지 않는 이유는 양이 아니라 형태다. 사람이 읽는 문장을 기계가 읽는 필드로 바꾸는 최소 절차를 정리했다.
로그가 검색되지 않는 이유는 양이 많아서가 아니다. 사람이 읽으려고 쓴 문장을 기계가 읽어야 하기 때문이다. 형태를 바꾸면 같은 양의 로그가 갑자기 쓸모 있어진다.
준비물
- 애플리케이션 로그를 표준 출력으로 내보내고 있을 것
- 로그를 수집해 조회할 수 있는 곳이 하나는 있을 것 (파일, 수집기, 관리형 서비스 무엇이든)
- 요청 단위로 구분할 식별자를 만들 수 있을 것
1단계 — 무엇을 필드로 올릴지 먼저 정한다
도구를 고르는 것보다 먼저 할 일이 있다. 장애가 났을 때 실제로 던지는 질문을 세 개만 적어 보는 것이다. "어떤 요청이었나", "누구에게 일어났나", "어디서 멈췄나". 이 질문에 답하는 값이 곧 필드다.
질문에서 출발하지 않으면 필드가 수십 개로 늘어나고, 그중 조회에 쓰이는 것은 서너 개에 그친다. 나머지는 비용만 만든다.
- requestId — 요청 하나를 끝까지 따라가는 식별자
- actor — 누구의 요청인지 (익명이면 익명임을 명시)
- outcome — 성공·실패·부분성공 중 무엇으로 끝났는지
- durationMs — 걸린 시간
2단계 — 출력 형식을 환경에서 분리한다
로깅 코드가 형식을 알면 안 된다. 코드는 "무슨 일이 있었고 어떤 값이 딸려 있다"만 말하고, 그것을 어떤 모양으로 내보낼지는 출력 시점에 정한다.
// 나쁜 예 — 문장에 값이 녹아 있어 나중에 꺼낼 수 없다
logger.info(`user ${userId} checkout failed in ${ms}ms`);
// 좋은 예 — 값이 필드로 남는다
logger.info("checkout.failed", {
requestId,
actor: userId,
outcome: "failure",
durationMs: ms,
});이렇게 두면 개발 환경에서는 읽기 좋은 한 줄로, 배포 환경에서는 JSON으로 내보낼 수 있다. 같은 코드가 두 형식을 모두 지원한다.
3단계 — 요청 식별자를 경계에서 심는다
구조화 로그의 값은 대부분 requestId에서 나온다. 한 요청에서 나온 로그를 모아 볼 수 있어야 순서와 인과를 읽을 수 있기 때문이다.
식별자는 요청이 시스템에 들어오는 가장 바깥에서 한 번 만들고, 그 뒤로는 만들지 않는다. 중간에서 새로 만들면 같은 요청이 두 개로 갈라진다.
4단계 — 조회로 검증한다
도입이 끝났는지는 코드가 아니라 조회로 판단한다. 1단계에서 적어 둔 질문 세 개를 실제로 조회해 보고, 한 번의 검색으로 답이 나오면 그 단계는 끝난 것이다.
| 장애 시 던지는 질문 | 조회 조건 |
|---|---|
| 이 요청에 무슨 일이 있었나 | requestId 일치 |
| 실패가 특정 사용자에게 몰리나 | outcome=failure 그룹화 by actor |
| 어느 단계가 느린가 | durationMs 상위 정렬 |
답이 나오지 않는 질문이 있다면 필드가 부족한 것이다. 그때 필드를 하나 추가한다. 미리 다 넣어 두는 것보다 이 순서가 언제나 싸다.
문제 해결
필드를 추가했는데 조회에서 잡히지 않는다
수집기가 필드를 문자열 전체로 저장하고 있을 가능성이 높다. 로그 한 줄을 그대로 꺼내 JSON으로 파싱되는지부터 확인한다. 파싱이 안 되면 애플리케이션이 아니라 수집 설정 문제다.
요청 식별자가 중간에 끊긴다
비동기 경계에서 컨텍스트가 전파되지 않은 것이다. 스레드 로컬이나 async context에 의존하는 대신, 경계를 넘는 지점에서 식별자를 인자로 명시해 넘긴다.
로그 양이 늘어 비용이 급증했다
구조화 자체가 원인인 경우는 드물다. 대개 디버그 레벨을 그대로 올린 것이다. 레벨별 보존 기간을 나누고, 고빈도 경로는 표본만 남긴다.
자주 묻는 질문
JSON 로그는 사람이 읽기 어렵지 않은가?
개발 환경에서는 사람이 읽는 형식으로, 배포 환경에서는 JSON으로 내보내면 된다. 형식은 출력 시점의 선택이지 로깅 코드의 성질이 아니다.
어떤 필드부터 올려야 하는가?
조회할 때 실제로 조건으로 쓰는 것부터다. 요청 식별자, 사용자 구분, 결과 상태 세 가지면 대부분의 질문에 답할 수 있다. 나머지는 필요해질 때 추가한다.
기존 로그를 전부 바꿔야 하는가?
아니다. 새로 쓰는 로그부터 구조화하고, 기존 로그는 조회에 실제로 쓰이는 것만 옮긴다. 전면 교체는 대개 중단된다.
참고 자료
- The Twelve-Factor App — Logs — 12factor
작성자
AI와 기술을 발견하고, 이해하고, 기록합니다.