관측성 · 로그 수준과 표본 추출 · 실습
디버그 로그를 켠 날 조사에 필요한 줄이 사라졌다
목표
고정된 로그 파일 하나로 수준별 비용 표를 만들고, 요청 단위 표본·오류 보존·반복 접기·초당 상한을 직접 구현해 비용과 조사 가능성의 맞바꿈을 숫자로 만든 뒤 정책 파일로 못박습니다.
왜 중요한가
로그 비용을 줄이라는 요구는 항상 온다. 그때 가장 쉬운 답이 '수준을 올린다' 와 '표본을 뽑는다' 인데, 둘 다 잘못하면 비용만 줄고 조사 능력은 0 이 된다. 사람이 조사할 때 읽는 단위는 줄이 아니라 한 요청이 남긴 줄의 묶음이기 때문이다. 줄 단위로 10% 를 남기면 어느 요청도 끝까지 읽을 수 없지만, 요청 단위로 10% 를 남기면 그 10% 는 완전하다. 여기에 '오류로 끝난 요청은 빼지 않는다' 는 규칙 하나를 더하면 비용은 거의 그대로인 채 실패한 요청의 보존율이 0% 에서 100% 로 올라간다. 이 실습에서 만드는 표는 다음번 비용 회의에서 그대로 쓸 수 있는 근거다.
단계
1. 재료 로그는 /opt/lab/logsample/app.log 입니다. 줄 형식은 <시각> <수준> req=<id> handler=<경로> msg="<메시지>" 이고 수준은 DEBUG·INFO·WARN·ERROR 넷입니다. /root/obs-log-sampling/cost.tsv 에 머리글 없이 네 줄, 각 줄은 탭으로 나눈 세 칸 <수준> <줄 수> <바이트> 를 적으세요. 바이트는 그 수준의 줄들이 차지하는 바이트 수이고 줄바꿈 문자 한 개를 포함합니다.
2. DEBUG 줄을 전부 버렸을 때의 결과를 /root/obs-log-sampling/drop-debug.txt 에 여섯 줄로 적으세요. kept_lines=<남은 줄 수>, kept_bytes=<남은 바이트>, saved_pct=<바이트 기준 절감률, 소수 두 자리>, req=<오류로 끝난 요청 중 request_id 가 사전순으로 가장 앞선 것>, req_lines_before=<그 요청의 원래 줄 수>, req_lines_after=<DEBUG 를 뺀 뒤 그 요청에 남은 줄 수> 입니다. '오류로 끝난 요청' 은 수준이 ERROR 인 줄을 하나 이상 가진 요청입니다.
3. /root/obs-log-sampling/sample.py 를 만드세요. python3 sample.py --rate N < 입력 > 출력 으로 부르면 표준 입력의 줄을 읽어 남길 줄만 그대로 표준 출력에 씁니다(입력 순서 유지). 남기는 기준은 요청 단위입니다 — 줄의 req= 값을 r 이라 할 때 int(hashlib.md5(r.encode()).hexdigest()[:8], 16) % N == 0 이면 그 줄을 남깁니다. --rate 1 은 전부 남깁니다. 그다음 python3 sample.py --rate 10 < /opt/lab/logsample/app.log > /root/obs-log-sampling/sampled-10.log 로 결과 파일을 만드세요.
4. /root/obs-log-sampling/sample.py 에 --keep-errors 옵션을 더하세요. 이 옵션이 있으면 수준이 ERROR 인 줄을 하나라도 가진 요청은 표본률과 무관하게 모든 줄을 남깁니다(입력 순서 유지). 옵션이 없을 때의 동작은 3단계와 같아야 합니다. 그다음 python3 sample.py --rate 10 --keep-errors < /opt/lab/logsample/app.log > /root/obs-log-sampling/sampled-10-err.log 로 결과를 만드세요.
5. 표본률 N 을 1·4·10·50 으로 바꿔 가며 /root/obs-log-sampling/tradeoff.tsv 를 만드세요. 머리글 없이 네 줄이고 각 줄은 탭으로 나눈 다섯 칸 <N> <표본만 썼을 때 남은 줄 수> <그때 완전히 남은 오류 요청 수> <오류 보존까지 켰을 때 남은 줄 수> <그때 완전히 남은 오류 요청 수> 입니다. '완전히 남은 오류 요청' 은 오류로 끝난 요청 중 결과 파일에 줄이 하나라도 남아 있는 요청을 셉니다.
6. /root/obs-log-sampling/squeeze.py 를 만드세요. python3 squeeze.py [--max-debug-per-sec K] < 입력 > 출력 (K 기본값 20)으로 부르면 두 규칙을 차례로 적용합니다. ① 바로 앞 줄과 (수준, handler, msg) 가 모두 같으면 한 덩어리로 보고 첫 줄만 남기되, 덩어리의 줄 수 k 가 2 이상이면 그 줄 끝에 repeated=<k> 를 붙입니다. ② 접은 뒤, DEBUG 줄만 같은 초(시각 문자열의 앞 19자) 안에서 앞에서부터 K 개까지 남기고 나머지는 버립니다. 그다음 python3 squeeze.py < /opt/lab/logsample/app.log > /root/obs-log-sampling/squeezed.log 를 만들고, /root/obs-log-sampling/squeeze.txt 에 네 줄 in_lines=, after_collapse=, after_cap=, bytes_saved_pct=<소수 두 자리> 를 적으세요.
7. sample.py --rate 10 --keep-errors 의 출력을 squeeze.py(기본 상한)에 그대로 흘려 /root/obs-log-sampling/final.log 를 만드세요. 그리고 /root/obs-log-sampling/final.txt 에 여덟 줄을 적으세요 — in_lines=, out_lines=, in_bytes=, out_bytes=, reduction_pct=<바이트 기준, 소수 두 자리>, error_requests_kept=<final.log 에 줄이 남아 있는 오류 요청 수>, trace_req=<2단계에서 고른 그 요청 id>, trace_lines=<final.log 에 남은 그 요청의 줄 수> 입니다.
8. /root/obs-log-sampling/policy.yml 을 YAML 로 쓰세요. levels 아래 DEBUG·INFO·WARN·ERROR 네 수준마다 retain_days(정수)와 sample_rate(정수) 를 둡니다. ERROR 의 sample_rate 는 1 이어야 하고 retain_days 는 DEBUG 보다 커야 합니다. DEBUG 의 sample_rate 는 2 이상이어야 합니다. rules 아래에는 keep_errors_whole_request: true, collapse_repeats: true, max_debug_per_sec(6단계에서 쓴 기본값)를 둡니다. estimate 아래에는 raw_bytes·kept_bytes·reduction_pct 를 7단계 결과와 같은 값으로 적고, 마지막에 note 로 이 정책의 맞바꿈을 40자 이상 한 문장으로 적으세요.
참고
- 작업 디렉터리는
/root/obs-log-sampling입니다. 없으면 먼저 만드세요. - 재료 로그는
/opt/lab/logsample/app.log입니다(약 8,280줄 · 795KiB). 고정된 파일이라 몇 번을 돌려도 같은 답이 나옵니다. 만든 방법은 같은 디렉터리의gen_log.py에 있습니다. - 줄 형식은
<시각> <수준> req=<id> handler=<경로> msg="<메시지>"이고 공백으로 나눈 둘째 칸이 수준입니다. - 필터는 표준 입력을 읽어 표준 출력에 쓰는 프로그램으로 만드세요 — 그래야 파이프로 이어 붙일 수 있습니다.
- Loki 는 켜지 마세요. 이 실습은 저장소에 넣기 전, 애플리케이션이 내보내는 줄 자체를 고르는 일을 다룹니다.
- 흔한 실수: 표본을 줄 번호로 뽑는 것. 한 요청이 반 토막 나 아무것도 재구성할 수 없습니다.
- 흔한 실수: 초당 상한을 수준 구분 없이 거는 것. 폭주하는 그 순간 ERROR 가 잘려 나갑니다.
- [Logs (OpenTelemetry)](https://opentelemetry.io/docs/concepts/signals/logs/) · [Logs Data Model (OTel 명세)](https://opentelemetry.io/docs/specs/otel/logs/) · [hashlib (Python 표준 라이브러리)](https://docs.python.org/3/library/hashlib.html) · [Effective Troubleshooting (SRE Book 12장)](https://sre.google/sre-book/effective-troubleshooting/) · [Monitoring (SRE Workbook 4장)](https://sre.google/workbook/monitoring/)
단계 8개
- 수준별로 얼마를 쓰고 있는지부터 센다
- DEBUG 를 끄면 무엇이 남고 무엇이 사라지나
- 줄이 아니라 요청을 표본으로 뽑는다
- 오류로 끝난 요청은 표본에서 빼지 않는다
- 표본률과 조사 가능성의 맞바꿈을 표로 만든다
- 반복을 접고 초당 상한을 건다
- 응용 ① — 두 필터를 이어 붙이고 조사가 되는지 확인한다
- 응용 ② — 수준·표본률·보존기간·비용을 정책 파일로 못박는다