Loki — A Log Store That Does Not Index Logs
I reported zero errors; there were thirty in that hour
한국어 원문으로 표시합니다.
목표
파서가 꺼낸 이름이 스트림 라벨과 부딪혀 밀려나는 것을 직접 재현하고, label_format·line_format·keep·drop 으로 결과를 원하는 모양으로 정리합니다.
왜 중요한가
Loki 에서 라벨은 두 곳에서 온다 — 수집할 때 붙여 색인된 스트림 라벨과, 질의할 때 파서가 본문에서 꺼낸 라벨이다. 이름이 겹치면 나중에 꺼낸 쪽에 _extracted 가 붙어 밀려나는데 경고도 오류도 없다. 그래서 level="error" 가 0건을 돌려주고, 사람은 그것을 '오류가 없다' 로 읽는다. 질의 시점의 이름 정리는 결과에만 작용하고 색인은 건드리지 않으므로 마음껏 써도 비용이 늘지 않는다 — 카디널리티가 걱정되는 값을 본문에 두고 질의할 때 꺼내 쓰는 설계가 성립하는 이유다.
단계
/root/lk-format에서 Loki 를 띄우고date +%s를/root/lk-format/anchor.txt에 적은 뒤python3 /opt/lab/d5/gen.py format "$(cat anchor.txt)"로 자료를 넣으세요. 그리고{app="payment"}의 한 시간 줄 수와, 그 스트림에 붙어 있는 스트림 라벨 이름들을/root/lk-format/01-boot.txt에 두 줄로 적으세요 —lines=<정수>와labels=<이름들을 쉼표로, 사전순>.{app="payment"} | logfmt | level="error"를 한 시간 구간으로 던져 결과가 몇 줄인지 보고, 이름이 밀려난 라벨로 다시 물어 몇 줄인지 세세요. 답은/root/lk-format/02-collision.txt에 세 줄로 적습니다 —naive=<정수>,extracted_name=<밀려난 라벨 이름>,correct=<정수>.label_format을 써서 본문의 수준 값이level이라는 이름으로 오게 만든 뒤level="error"로 거르세요. 질의는/root/lk-format/03-relabel.logql, 답은/root/lk-format/03-relabel.txt에lines=<정수>로 적습니다. 결과 줄 수는 2단계의correct와 같아야 합니다.{app="payment"}의 모든 줄을<svc>/<본문의 수준>/<dur_ms>형태로 다시 쓰는 질의를/root/lk-format/04-line.logql에 쓰세요. 슬래시 두 개로만 나누고 다른 글자는 넣지 않습니다. 그리고 결과 중 가장 이른 줄의 본문을/root/lk-format/04-line.txt에 한 줄로 적으세요.- 본문의 수준별로 줄 수를 세는 지표 질의를
/root/lk-format/05-bylevel.logql에 쓰세요. 결과는 정확히 두 계열이어야 하고 각 계열의 라벨은lvl하나뿐이어야 합니다. 값은/root/lk-format/05-bylevel.txt에info=<정수>와error=<정수>두 줄로 적습니다. - 5단계 질의에서
keep대신drop을 써서 같은 결과를 내는 질의를/root/lk-format/06-drop.logql에 쓰세요. 그리고/root/lk-format/06-drop.txt에 두 줄을 적습니다 —series=<계열 수>와dropped=<버린 라벨 이름들을 쉼표로, 사전순>. - 3단계에서
label_format으로 라벨을 만들었고 5단계에서는 새 이름으로 계열을 묶었습니다. 그 뒤 색인의 스트림 수가 달라졌는지 확인해/root/lk-format/07-index.txt에 두 줄로 적으세요 —streams=<정수>와changed=<yes|no>. 스트림 수는 series API 로 셉니다. /root/lk-format/08-panel.logql에 질의 하나를 쓰세요. 조건은 셋입니다 — (1) 본문의 수준이error인 줄만 돌려줄 것, (2) 돌려주는 본문이<dur_ms>ms <svc> <region>형태일 것(공백 두 개로만 나누고 다른 글자는 없음), (3) 줄 필터를 본문 다시 쓰기 앞 에 둘 것. 그리고 결과의 가장 이른 줄을/root/lk-format/08-panel.txt에 한 줄로 적으세요.
참고
- 작업 디렉터리는
/root/lk-format입니다. Loki 는 1단계에서 직접 띄웁니다. - 자료 생성기는
/opt/lab/d5/gen.py이고format자료를 씁니다. 채점기는 이 파일을 읽지 않습니다. - 템플릿은 Go 의 text/template 입니다. 필드 앞에 점을 찍고 중괄호 두 겹으로 감쌉니다. 질의문은 역따옴표로 감싸는 쪽이 안전합니다.
- 결과의 라벨 목록은
jq '.data.result[0].stream'으로 한 번 펼쳐 보는 습관을 들이세요. 이름이 밀려난 것은 그렇게 해야 보입니다. - 흔한 실수:
line_format을 줄 필터 앞에 두는 것. 그 뒤의 필터는 새 본문을 보게 됩니다. - 흔한 실수:
since=1h로 재는 것.anchor.txt의 기준 시각으로start·end를 주세요. - 로그 질의와 서식 · 지표 질의 · 라벨 · HTTP API
라벨과 본문이 어긋난 자료를 넣는다
/root/lk-format 에서 Loki 를 띄우고 date +%s 를 /root/lk-format/anchor.txt 에 적은 뒤 python3 /opt/lab/d5/gen.py format "$(cat anchor.txt)" 로 자료를 넣으세요. 그리고 {app="payment"} 의 한 시간 줄 수와, 그 스트림에 붙어 있는 스트림 라벨 이름들을 /root/lk-format/01-boot.txt 에 두 줄로 적으세요 — lines=<정수> 와 labels=<이름들을 쉼표로, 사전순>.
스트림 라벨은 결과의 stream 객체의 키입니다. Loki 3.x 는 service_name 을 자동으로 붙이니 그것도 목록에 들어갑니다. 사전순으로 정렬해 쉼표로 이으세요(공백 없이).
level="error" 가 0건을 돌려준다
{app="payment"} | logfmt | level="error" 를 한 시간 구간으로 던져 결과가 몇 줄인지 보고, 이름이 밀려난 라벨로 다시 물어 몇 줄인지 세세요. 답은 /root/lk-format/02-collision.txt 에 세 줄로 적습니다 — naive=<정수>, extracted_name=<밀려난 라벨 이름>, correct=<정수>.
결과 하나를 jq '.data.result[0].stream' 으로 펼쳐 키 목록을 보세요. 파서가 꺼낸 이름이 이미 있는 스트림 라벨과 부딪히면 Loki 가 접미를 붙여 밀어냅니다. 그 이름으로 다시 물으면 숫자가 나옵니다.
label_format 으로 이름을 되돌린다
label_format 을 써서 본문의 수준 값이 level 이라는 이름으로 오게 만든 뒤 level="error" 로 거르세요. 질의는 /root/lk-format/03-relabel.logql, 답은 /root/lk-format/03-relabel.txt 에 lines=<정수> 로 적습니다. 결과 줄 수는 2단계의 correct 와 같아야 합니다.
label_format 은 새이름=기존라벨 형태로 쓰거나 템플릿을 줄 수 있습니다. 이미 있는 이름에 덮어쓰는 것도 됩니다. 순서가 중요합니다 — 이름을 바꾼 뒤에 걸러야 합니다.
line_format 으로 줄을 다시 쓴다
{app="payment"} 의 모든 줄을 <svc>/<본문의 수준>/<dur_ms> 형태로 다시 쓰는 질의를 /root/lk-format/04-line.logql 에 쓰세요. 슬래시 두 개로만 나누고 다른 글자는 넣지 않습니다. 그리고 결과 중 가장 이른 줄의 본문을 /root/lk-format/04-line.txt 에 한 줄로 적으세요.
line_format 은 Go 템플릿을 받습니다. 필드 이름 앞에 점을 찍습니다. 수준은 2단계에서 찾은 밀려난 이름을 써야 합니다. 가장 이른 줄은 direction=forward 로 받으면 맨 앞에 옵니다.
keep 으로 계열을 묶는다
본문의 수준별로 줄 수를 세는 지표 질의를 /root/lk-format/05-bylevel.logql 에 쓰세요. 결과는 정확히 두 계열이어야 하고 각 계열의 라벨은 lvl 하나뿐이어야 합니다. 값은 /root/lk-format/05-bylevel.txt 에 info=<정수> 와 error=<정수> 두 줄로 적습니다.
label_format 으로 쓸 이름을 만들고, keep 으로 그 이름만 남긴 뒤, 바깥을 sum by (lvl) (count_over_time(... [1h])) 로 감쌉니다. keep 을 빼면 무슨 일이 생기는지도 한 번 보세요 — 계열 수로 바로 드러납니다.
drop 은 무엇을 버리고 무엇을 남기나
5단계 질의에서 keep 대신 drop 을 써서 같은 결과를 내는 질의를 /root/lk-format/06-drop.logql 에 쓰세요. 그리고 /root/lk-format/06-drop.txt 에 두 줄을 적습니다 — series=<계열 수> 와 dropped=<버린 라벨 이름들을 쉼표로, 사전순>.
drop 은 이름을 하나씩 적어 버립니다. 1단계에서 적어 둔 스트림 라벨 목록과 2단계에서 본 파서 라벨 목록을 합치면 무엇을 버려야 하는지 나옵니다. 남길 것이 적을 때는 keep, 버릴 것이 적을 때는 drop 이 짧습니다.
응용 ① — 라벨을 바꿔도 색인은 그대로다
3단계에서 label_format 으로 라벨을 만들었고 5단계에서는 새 이름으로 계열을 묶었습니다. 그 뒤 색인의 스트림 수가 달라졌는지 확인해 /root/lk-format/07-index.txt 에 두 줄로 적으세요 — streams=<정수> 와 changed=<yes|no>. 스트림 수는 series API 로 셉니다.
/loki/api/v1/series 에 match[]={app=~".+"} 를 주면 지금 색인에 있는 스트림 목록이 나옵니다. 질의 시점에 만든 라벨이 거기 있는지 보세요. 없다면 그것이 바로 "질의 시점의 라벨은 저장되지 않는다" 의 증거입니다.
응용 ② — 사람이 읽을 수 있는 오류 패널 질의
/root/lk-format/08-panel.logql 에 질의 하나를 쓰세요. 조건은 셋입니다 — (1) 본문의 수준이 error 인 줄만 돌려줄 것, (2) 돌려주는 본문이 <dur_ms>ms <svc> <region> 형태일 것(공백 두 개로만 나누고 다른 글자는 없음), (3) 줄 필터를 본문 다시 쓰기 앞 에 둘 것. 그리고 결과의 가장 이른 줄을 /root/lk-format/08-panel.txt 에 한 줄로 적으세요.
순서가 의미입니다 — line_format 뒤의 필터는 새 본문을 봅니다. 그래서 거르기를 먼저, 다시 쓰기를 나중에 해야 합니다. 수준으로 거를 때는 2단계에서 찾은 이름이나 3단계에서 만든 이름을 쓰면 됩니다.