LabHub
배우기 러닝패스 코스

Loki — 로그를 색인하지 않는 로그 저장소 · 파서로 본문에서 값을 꺼낸다 · 이론

파서가 조용히 아무것도 못 꺼낼 때

LabHub 에서 이어서 보기

한 줄 요약

Loki 는 본문을 색인하지 않으므로, 본문 속 값으로 거르려면 질의할 때마다 파서로 꺼내야 한다. 파서는 틀리면 __error__ 라벨을 붙이지만, 아무것도 못 꺼내도 오류를 내지 않는 파서가 있다.

왜 이게 필요했나

한 팀이 결제 서비스의 오류율을 Loki 로 재고 있었다. 질의는 {app="pay"} | logfmt | status="500" 이었고, 대시보드는 몇 달 동안 평화로웠다. 그런데 장애 회의에서 같은 시간대의 실제 5xx 건수를 세어 보니 대시보드 숫자의 세 배였다.

원인은 간단했다. 그 서비스의 일부 경로가 JSON 으로 로그를 찍고 있었다. logfmt 파서는 JSON 줄을 만나면 오류를 내지 않는다. 그냥 아무 라벨도 못 꺼내고 지나간다. 라벨이 없으면 status="500" 은 거짓이 되고, 그 줄은 조용히 빠진다. 대시보드에는 "데이터 없음" 도 "파싱 오류" 도 뜨지 않는다. 그냥 숫자가 작아질 뿐이다.

어떻게 동작하나

LogQL 의 파서는 네 가지다. 전부 선택자와 줄 필터 뒤에 붙고, 질의할 때마다 그 줄들을 다시 읽어 라벨을 만든다.

| 파서 | 읽는 형식 | 실패했을 때 |
| --- | --- | --- |
| logfmt | 키=값 이 공백으로 이어진 줄 | 오류 없이 아무것도 안 꺼낸다 |
| json | JSON 객체 한 줄 | __error__="JSONParserErr" 를 붙인다 |
| pattern | <이름> 자리표시자로 쓴 고정 모양 | 모양이 다르면 안 꺼낸다 |
| regexp | 이름 있는 잡기 그룹을 가진 RE2 | 안 맞으면 안 꺼낸다 |

그래서 조사는 늘 두 갈래로 해야 한다. 먼저 | json | __error__!=""깨진 줄이 얼마나 되는지 세고, 그 다음 | logfmt | status=""오류는 없지만 값이 안 나온 줄이 얼마나 되는지 센다. 두 숫자가 모두 0 이어야 그 스트림의 파서를 믿을 수 있다.

파싱 오류가 난 줄을 지우고 싶으면 | __error__="" 를 붙인다. 하지만 습관적으로 붙이면 안 된다 — 그 줄들이 바로 "형식이 다른 로그가 섞여 있다" 는 신호이기 때문이다. 먼저 세고, 원인을 알고, 그 다음에 지운다.

추출된 라벨은 그 질의 안에서만 산다. 저장된 것도 아니고 색인된 것도 아니다. 그래서 파서를 바꾸면 과거 자료도 함께 다시 해석된다 — 지표와 정반대다. 지표는 잘못 계측하면 과거가 영영 사라지지만, 로그는 본문만 남아 있으면 나중에 다른 눈으로 다시 읽을 수 있다. 이것이 "색인하지 않는다" 는 선택이 주는 가장 큰 이득이다.

값은 언제나 문자열이다. | dur_ms > 500 처럼 비교하려면 LogQL 이 숫자로 바꿔 주지만, 단위가 섞여 있으면(밀리초와 초가 한 필드에) 조용히 잘못된 답이 나온다. 숫자를 담은 필드는 이름에 단위를 넣는 편이 안전하다.

현장에서 만나는 모습

가장 흔한 사고는 "한 스트림에 두 형식" 이다. 애플리케이션이 JSON 으로 찍는데 그 앞의 프록시나 런타임이 평문 경고를 같은 stdout 에 섞어 넣는다. 스트림 라벨은 같으니 한 스트림이고, 질의는 한 파서만 쓸 수 있다.

해법은 질의가 아니라 배치에 있다. 형식이 다른 로그는 라벨을 달리해 다른 스트림으로 보낸다. 수집기에서 한 번 갈라 놓으면 질의는 단순해지고 파서 오류도 사라진다. 그게 안 되는 레거시라면, 적어도 파서마다 따로 세어 합치는 질의를 만들어 두고 그 사실을 대시보드 옆에 적어 둔다.

또 하나. pattern 파서는 regexp 보다 빠르고 읽기 쉬워서, 접근 로그처럼 자리가 고정된 줄에는 거의 항상 더 낫다. 정규식은 자리가 흔들리는 줄에만 쓴다.

다음 실습에서 할 것

세 형식이 섞인 스트림 하나와 형식이 깔끔한 스트림 하나를 파드의 진짜 Loki 에 넣고, 네 파서를 차례로 붙여 본다. json 이 오류를 내는 줄 수와 logfmt 가 조용히 지나간 줄 수를 각각 세고, patternregexp 로 레거시 줄에서 값을 꺼낸 뒤, 마지막에는 형식별로 따로 세어 합쳐야만 나오는 진짜 5xx 건수를 구한다.