Logs stopped at midnight and a restart made them flow again
한국어 원문으로 표시합니다.
목표
파일을 이어 읽는 수집기를 직접 만들고, 덧붙임·회전·절단을 차례로 일으키며 한 줄도 잃거나 겹치지 않는 것을 번호로 확인합니다.
왜 중요한가
수집기의 첫 일은 '어디까지 읽었는지' 를 기억하는 것이다. 그런데 바이트 위치 하나만 기억하면 자정의 로그 회전에서 조용히 멈춘다 — 새 파일은 작은데 기억해 둔 위치는 크기 때문이다. 오류도 경보도 없이 몇 시간이 비고, 재시작하면 다시 흐르기 때문에 원인도 안 남는다. 회전은 inode 가 달라지는 것으로, 절단은 크기가 읽은 곳보다 작아지는 것으로 알아챈다. 두 가지를 모두 봐야 하는 이유는 로테이션 방식이 두 가지이기 때문이다. 위치 기록이 사라졌을 때의 정책도 미리 정해 두어야 한다 — 중복과 유실 중 무엇을 감수할지는 공짜로 정해지지 않는다.
단계
/root/lp-tail에서python3 /opt/lab/d5/applog.py logfmt /root/lp-tail/app.log 200 "$(date +%s)" 1로 seq 1부터 200까지의 로그를 만드세요. 그리고app.log를 이어 읽어/root/lp-tail/collected.log에 덧붙이고 읽은 위치를/root/lp-tail/pos.txt에 남기는 수집기를 만들어 한 번 돌리세요.pos.txt에는inode=<정수>와offset=<정수>두 줄이 들어갑니다.python3 /opt/lab/d5/applog.py logfmt /root/lp-tail/app.log 100 "$(date +%s)" 201로 seq 201부터 300까지를 덧붙이고 수집기를 다시 돌리세요. 수집이 끝나면collected.log의 줄 수가 300이어야 하고 같은 seq 가 두 번 들어 있으면 안 됩니다.mv /root/lp-tail/app.log /root/lp-tail/app.log.1로 회전을 일으키고python3 /opt/lab/d5/applog.py logfmt /root/lp-tail/app.log 100 "$(date +%s)" 301로 새 파일에 seq 301부터 400까지를 쓰세요. 그리고 수집기를 돌려collected.log가 400줄이 되게 하세요.copytruncate방식을 흉내 내세요 —cp /root/lp-tail/app.log /root/lp-tail/app.log.2로 복사하고: > /root/lp-tail/app.log로 원본을 0으로 자른 다음 수집기를 한 번 돌립니다. 그리고python3 /opt/lab/d5/applog.py logfmt /root/lp-tail/app.log 100 "$(date +%s)" 401로 seq 401부터 500까지를 쓰고 수집기를 다시 돌려 그 구간이 빠짐없이 들어오게 하세요.- 지금
pos.txt의inode가 실제app.log의 inode 와 같고offset이 파일 크기와 같은지 확인하세요. 그리고/root/lp-tail/05-pos.txt에 세 줄을 적습니다 —pos_inode=<정수>,file_inode=<정수>,offset_equals_size=<yes|no>. - 현재 디렉터리를
/root/lp-tail-nopos로 통째로 복사하고, 거기서pos.txt를 지운 뒤 수집기를 한 번 돌리세요. 그리고/root/lp-tail/06-nopos.txt에 세 줄을 적습니다 —before=<복사 시점의 collected.log 줄 수>,after=<다시 돌린 뒤 줄 수>,duplicated=<두 값의 차>. 원래 디렉터리는 건드리지 마세요. - 원본 파일 전체(
app.log와 회전본들)에 들어 있는 seq 의 집합과collected.log의 seq 집합을 견주어/root/lp-tail/tally.txt에 네 줄을 적으세요 —source=<원본 줄 수>,collected=<수집본 줄 수>,missing=<원본에만 있는 seq 수>,duplicated=<수집본에서 두 번 이상 나온 seq 수>. /root/lp-tail/chaos.sh를 만들어 아래를 순서대로 하게 하세요 — (1) 회전(app.log를app.log.3으로 옮기고 새 파일에 seq 501부터 550까지), (2) 수집, (3) 복사 후 절단(cp app.log app.log.4다음: > app.log)과 그 직후의 수집, (4) seq 551부터 600까지를 쓰고 다시 수집. 스크립트를 돌린 뒤 seq 1부터 600까지가collected.log에 빠짐없이 한 번씩 들어 있어야 합니다. 결과를/root/lp-tail/08-chaos.txt에collected=<정수>,missing=0,duplicated=0세 줄로 적으세요.
참고
- 작업 디렉터리는
/root/lp-tail입니다. 이 실습은 목적지를 쓰지 않습니다 — 읽는 쪽만 다룹니다. - 로그를 만드는 '애플리케이션' 은
/opt/lab/d5/applog.py입니다. 줄마다seq=번호가 들어 있어 나중에 유실과 중복을 정확히 셀 수 있습니다. 채점기는 이 파일을 읽지 않습니다. - 수집기는 어떤 언어로 써도 됩니다. 채점기는
collected.log와pos.txt의 내용만 봅니다. stat -c %i <파일>이 inode 를,wc -c < <파일>이 크기를 알려 줍니다.- 흔한 실수: 매번 파일을 통째로 다시 읽는 것. 2단계에서 줄 수가 두 배가 되며 드러납니다.
- 흔한 실수: 위치를 갱신하기 전에 죽는 것. 이 실습에서는 다루지 않지만, 실제 수집기는 보낸 뒤에 위치를 적어 최소 한 번 전달을 보장합니다.
- 이 실습의 한계: 잘린 뒤 새 줄이 쌓여 크기가 옛 위치와 정확히 같아지면 크기 비교로는 절단을 알아낼 수 없습니다. 그래서 수집기는 자주 돌아야 하고, 실제 수집기들은 파일 앞부분의 내용을 함께 기억해 이 경우까지 가려냅니다. 여기서는 자른 직후에 한 번 도는 것으로 그 창을 피합니다.
- Fluent Bit — tail 입력 · Fluentd — 설정 파일 · Kubernetes — 클러스터 로깅 구조 · Vector — 개념
앱 로그를 만들고 처음 한 번 수집한다
/root/lp-tail 에서 python3 /opt/lab/d5/applog.py logfmt /root/lp-tail/app.log 200 "$(date +%s)" 1 로 seq 1부터 200까지의 로그를 만드세요. 그리고 app.log 를 이어 읽어 /root/lp-tail/collected.log 에 덧붙이고 읽은 위치를 /root/lp-tail/pos.txt 에 남기는 수집기를 만들어 한 번 돌리세요. pos.txt 에는 inode=<정수> 와 offset=<정수> 두 줄이 들어갑니다.
언어는 자유입니다. 핵심은 두 가지를 기억하는 것입니다 — 어느 파일이었는지(inode)와 어디까지 읽었는지(offset). stat -c %i 로 inode 를, wc -c 로 크기를 볼 수 있습니다. 수집기는 여러 번 돌려도 같은 줄을 두 번 쓰면 안 됩니다.
덧붙은 줄만 이어 읽는다
python3 /opt/lab/d5/applog.py logfmt /root/lp-tail/app.log 100 "$(date +%s)" 201 로 seq 201부터 300까지를 덧붙이고 수집기를 다시 돌리세요. 수집이 끝나면 collected.log 의 줄 수가 300이어야 하고 같은 seq 가 두 번 들어 있으면 안 됩니다.
여기서 통째로 다시 읽는 수집기는 600줄을 만들어 냅니다. offset 을 제대로 쓰고 있는지 pos.txt 와 wc -c app.log 를 견주어 보세요 — 두 값이 같아야 합니다.
회전 — 같은 이름의 다른 파일
mv /root/lp-tail/app.log /root/lp-tail/app.log.1 로 회전을 일으키고 python3 /opt/lab/d5/applog.py logfmt /root/lp-tail/app.log 100 "$(date +%s)" 301 로 새 파일에 seq 301부터 400까지를 쓰세요. 그리고 수집기를 돌려 collected.log 가 400줄이 되게 하세요.
위치만 기억하는 수집기는 새 파일에서 읽을 것을 못 찾습니다. 같은 이름이지만 다른 파일이 되었다는 것을 알아채야 합니다 — stat 이 알려 주는 값 하나로 판별됩니다. 알아챘다면 새 파일을 처음부터 읽습니다.
절단 — inode 는 그대로인데 작아졌다
copytruncate 방식을 흉내 내세요 — cp /root/lp-tail/app.log /root/lp-tail/app.log.2 로 복사하고 : > /root/lp-tail/app.log 로 원본을 0으로 자른 다음 수집기를 한 번 돌립니다. 그리고 python3 /opt/lab/d5/applog.py logfmt /root/lp-tail/app.log 100 "$(date +%s)" 401 로 seq 401부터 500까지를 쓰고 수집기를 다시 돌려 그 구간이 빠짐없이 들어오게 하세요.
이번에는 inode 가 그대로입니다. inode 만 보는 수집기는 아무 변화도 못 느끼고 옛 위치에서 읽으려 합니다. 파일 크기가 기억해 둔 위치보다 작아진 것이 유일한 단서입니다. 자른 직후에 한 번 도는 이유가 있습니다 — 수집기는 주기적으로 돌고, 새 줄이 쌓여 크기가 옛 위치를 다시 넘어서 버리면 그 단서마저 사라집니다.
기억에 무엇이 들어 있어야 하는가
지금 pos.txt 의 inode 가 실제 app.log 의 inode 와 같고 offset 이 파일 크기와 같은지 확인하세요. 그리고 /root/lp-tail/05-pos.txt 에 세 줄을 적습니다 — pos_inode=<정수>, file_inode=<정수>, offset_equals_size=<yes|no>.
stat -c %i app.log 와 wc -c < app.log 를 쓰면 됩니다. 두 값이 어긋나 있다면 수집기가 위치를 제대로 갱신하지 않은 것이고, 다음 회전에서 줄을 잃게 됩니다.
위치 기록이 사라지면 무슨 일이 생기나
현재 디렉터리를 /root/lp-tail-nopos 로 통째로 복사하고, 거기서 pos.txt 를 지운 뒤 수집기를 한 번 돌리세요. 그리고 /root/lp-tail/06-nopos.txt 에 세 줄을 적습니다 — before=<복사 시점의 collected.log 줄 수>, after=<다시 돌린 뒤 줄 수>, duplicated=<두 값의 차>. 원래 디렉터리는 건드리지 마세요.
cp -a . /root/lp-tail-nopos 로 복사합니다. 위치 기록이 없으면 수집기는 '처음부터' 또는 '끝부터' 중 하나를 골라야 하는데, 표본 수집기는 처음부터 읽습니다. 그래서 이미 보낸 줄이 다시 들어옵니다 — 그 수를 세는 것이 이 단계입니다.
응용 ① — 번호로 유실과 중복을 센다
원본 파일 전체(app.log 와 회전본들)에 들어 있는 seq 의 집합과 collected.log 의 seq 집합을 견주어 /root/lp-tail/tally.txt 에 네 줄을 적으세요 — source=<원본 줄 수>, collected=<수집본 줄 수>, missing=<원본에만 있는 seq 수>, duplicated=<수집본에서 두 번 이상 나온 seq 수>.
seq 는 줄마다 seq=00123 모양으로 들어 있습니다. grep -o 'seq=[0-9]*' 로 뽑아 sort 와 uniq 로 셉니다. 유실과 중복이 둘 다 0 이어야 수집기를 믿을 수 있습니다.
응용 ② — 회전과 절단이 섞여도 한 줄도 잃지 않는다
/root/lp-tail/chaos.sh 를 만들어 아래를 순서대로 하게 하세요 — (1) 회전(app.log 를 app.log.3 으로 옮기고 새 파일에 seq 501부터 550까지), (2) 수집, (3) 복사 후 절단(cp app.log app.log.4 다음 : > app.log)과 그 직후의 수집, (4) seq 551부터 600까지를 쓰고 다시 수집. 스크립트를 돌린 뒤 seq 1부터 600까지가 collected.log 에 빠짐없이 한 번씩 들어 있어야 합니다. 결과를 /root/lp-tail/08-chaos.txt 에 collected=<정수>, missing=0, duplicated=0 세 줄로 적으세요.
앞 단계에서 한 일을 스크립트로 묶는 것입니다. 회전과 절단 사이에 반드시 수집을 한 번 넣어야 합니다 — 회전된 파일의 꼬리를 읽을 기회가 그때뿐이기 때문입니다. 진짜 수집기가 파일 서술자를 잠시 더 붙들어 두는 이유도 같습니다.