LabHub
学习 学习路径 课程

从日志里找原因

为看事故的三分钟,把一整天的日志读了个遍

在 LabHub 中继续学习

한국어 원문으로 표시합니다.

목표

회전까지 끝난 여섯 시간치 로그에서 사고 구간 3분만 꺼냅니다. 겹치지 않는 파일은 열지 않고, 남은 파일에서는 이진 탐색으로 바이트 구간을 찍어 그 사이만 읽고, 압축본은 앞에서부터 훑되 일찍 멈춥니다.

왜 중요한가

"3분만 보여 달라" 는 요청에 하루치를 통째로 읽는 것이 로그 작업에서 가장 흔한 사고입니다. 그런데 시각이 RFC 3339 표기로 굳어 있고 파일이 시간순이면 문자열 비교가 곧 시간 비교라, 파일을 읽지 않고 이진 탐색으로 구간의 시작 자리를 찍을 수 있습니다. 어려운 것은 탐색이 아니라 경계입니다. 구간을 반열림으로 잡아야 이어지는 구간이 겹치지 않고, 같은 시각의 줄이 여럿일 때 그 시각의 첫 줄을 찾아야 앞의 몇 줄이 조용히 빠지지 않습니다. 그리고 빠르다는 말만으로는 아무것도 증명되지 않으므로, 통째로 훑는 느린 방법과 한 번은 대조해야 합니다.

단계

  1. /root/slice/gen_slice.py 를 만들어 실행해 /root/slice/var/log/ 아래 app.log · app.log.1 · app.log.2.gz 를 만드세요.
  2. /root/slice/index.json — 회전본을 오래된 것부터 세우고 파일마다 담긴 구간을 적으세요.
  3. /root/slice/plan.json — 사고 구간과 겹치는 파일만 고르고 나머지는 이유와 함께 건너뛰세요.
  4. /root/slice/offsets.json — 이진 탐색으로 구간의 시작 바이트와 끝 바이트를 찍으세요.
  5. /root/slice/window_a.log — 찍어 둔 바이트 구간만 읽어 사고 구간을 뽑으세요.
  6. /root/slice/proof.json — 느린 방법과 대조해 결과가 같음을 증명하세요.
  7. /root/slice/window_b.log/root/slice/gz_scan.json — 압축본 안의 두 번째 구간을 일찍 멈추며 뽑으세요.
  8. /root/slice/slice_report.md — 방법과 근거를 보고서로 남기세요.

참고

회전까지 끝난 로그 묶음 재현하기

/root/slice/gen_slice.py 를 만들어 실행해 /root/slice/var/log/ 아래 app.log(57600줄) · app.log.1(57600줄) · app.log.2.gz(풀면 57600줄)를 만드세요.

세 파일은 같은 프로그램이 쓴 같은 형식이고, 회전으로 쪼개진 것뿐입니다. 초당 여덟 줄로 두 시간치씩 담고, 30초마다 같은 밀리초에 세 줄이 겹치게 하세요. 압축본은 파이썬 gzip.GzipFile 에 mtime=0 을 주어 쓰면 다시 만들어도 같은 파일이 나옵니다.

회전본을 시간순으로 세우고 담긴 구간 재기

/root/slice/index.jsonfiles(오래된 것부터 담은 배열, 항목마다 file · compressed · bytes · first_ts · last_ts)와 rule(그렇게 세운 규칙 한 문장, 20자 이상)을 적으세요. 시각은 줄의 앞 24자를 그대로 씁니다.

번호가 붙은 회전본은 숫자가 클수록 과거이고, 확장자가 없는 것이 지금 쓰는 파일입니다. logrotate 의 dateext 를 쓰면 이름이 날짜라 방향이 반대가 되니 규칙을 적을 때 함께 짚어 두세요. 비압축 파일의 마지막 줄은 파일 끝에서 몇 KB 만 읽어도 꺼낼 수 있습니다 — 통째로 읽지 마세요. 재료는 /root/slice/var/log/ 아래 세 파일입니다.

겹치지 않는 파일은 아예 열지 않기

/root/slice/plan.jsonwindow(start 는 2026-04-12T03:58:30.000Z, end 는 2026-04-12T04:01:30.000Z)와 open(구간과 겹치는 파일을 오래된 것부터 담은 배열), skip(나머지 파일을 file · reason 으로 담은 배열)을 적으세요.

2단계에서 잰 first_ts 와 last_ts 만 있으면 파일을 열지 않고도 겹치는지 알 수 있습니다. 구간이 반열림이므로 겹침 판정도 반열림입니다 — 파일의 시작이 구간의 끝보다 앞서고, 파일의 끝이 구간의 시작보다 앞서지 않으면 겹칩니다. 재료는 /root/slice/index.json 입니다. reason 은 10자 이상으로 적으세요.

이진 탐색으로 구간의 시작과 끝을 바이트로 찍기

/root/slice/offsets.jsonoffsets(3단계에서 고른 비압축 파일마다 file · start_offset · end_offset 을 담은 배열, 오래된 것부터)를 적으세요. start_offset 은 시각이 구간 시작 이상인 첫 줄의 시작 바이트이고, end_offset 은 구간 끝 이상인 첫 줄의 시작 바이트입니다(없으면 파일 크기).

파일 크기를 반으로 접어 그 바이트로 seek 하면 거의 늘 줄 한가운데입니다. 한 줄을 읽어 버려 줄 경계에 선 뒤 그 줄의 앞 24자로 판정하세요. 같은 시각의 줄이 셋 있으므로 '조건을 만족하는 아무 줄' 이 아니라 '조건을 처음 만족하는 줄' 을 찾아야 합니다 — 조건을 만족하면 범위의 오른쪽 끝을 그 줄의 시작으로 당기세요. 재료는 /root/slice/plan.json 입니다.

찍어 둔 바이트 구간만 읽어 사고 구간 뽑기

/root/slice/window_a.log 에 사고 구간 [2026-04-12T03:58:30.000Z, 2026-04-12T04:01:30.000Z) 의 줄을 시간순으로, 원문 그대로 쓰세요. 파일은 오래된 것부터 이어 붙입니다.

4단계에서 찍은 두 오프셋 사이를 그대로 읽어 내면 됩니다. end_offset 이 구간 밖 첫 줄의 시작이라 그 앞까지가 정확히 반열림 구간입니다. 줄을 다시 파싱하거나 다듬지 마세요 — 원문 바이트를 그대로 옮겨야 다음 단계의 해시 대조가 성립합니다. 재료는 /root/slice/offsets.json 입니다.

느린 방법과 대조해 결과를 증명하기

/root/slice/proof.json 에 window · fast_lines · slow_lines · sha256_fast · sha256_slow · match(불리언) · bytes_read_fast · bytes_read_slow 를 적으세요. bytes_read_fast 는 4단계의 두 오프셋 사이 바이트의 합이고, bytes_read_slow 는 압축본까지 풀어 세 파일을 통째로 읽을 때의 바이트입니다.

느린 방법은 세 파일을 전부(압축본은 풀어서) 훑으며 구간의 줄을 모으는 것입니다. 시간은 재지 마세요 — 기계마다 흔들립니다. 대신 줄 수와 sha256 해시를 견줍니다. 해시는 뽑힌 줄을 이어 붙인 바이트에 대해 계산하고, 두 방법의 값이 같아야 match 가 참입니다. 재료는 /root/slice/window_a.log 와 /root/slice/offsets.json 입니다.

압축본에서는 일찍 멈추는 것이 답이다

두 번째 질문은 [2026-04-12T00:20:00.000Z, 2026-04-12T00:23:00.000Z) 입니다. /root/slice/window_b.log 에 그 구간의 줄을 원문 그대로 쓰고, /root/slice/gz_scan.json 에 window · file · total_lines · lines_read · lines_kept 를 적으세요. lines_read 는 멈출 때까지 실제로 푼 줄 수입니다.

이 구간은 압축본 안에 있습니다. gzip 스트림은 앞의 내용에 기대어 풀리므로 임의 지점으로 건너뛸 수 없고, 이진 탐색을 걸면 같은 자리를 몇 번이고 다시 푸는 꼴이 됩니다. 앞에서부터 한 번만 훑되 구간의 끝을 지나는 줄을 만나면 그 자리에서 빠져나오세요. total_lines 는 얼마나 아꼈는지 적기 위한 값이라 한 번은 끝까지 세어야 합니다. 재료는 /root/slice/var/log/app.log.2.gz 입니다.

방법과 근거를 문서로 남기기

/root/slice/slice_report.md## 무엇을 물었나 ## 왜 통째로 읽지 않아도 되었나 ## 압축본은 무엇이 달랐나 ## 결과를 어떻게 증명했나 네 절로 적으세요. 사고 구간의 줄 수 · 빠른 방법이 읽은 바이트 · 압축본에서 푼 줄 수를 숫자로 포함해야 합니다.

이 문서를 읽을 사람은 다음에 같은 질문을 받는 사람입니다. 그 사람이 알아야 하는 것은 명령이 아니라 전제입니다 — 왜 이진 탐색이 성립했는지, 어떤 파일을 왜 열지 않았는지, 압축본에서 무엇이 달랐는지, 결과를 무엇과 대조했는지. 숫자는 지어내지 말고 /root/slice/proof.json 과 /root/slice/gz_scan.json 에서 꺼내 쓰세요.