Grafana 의 Loki 패널에 로그 한 줄이 통째로 나오면 읽기 어렵다. 로그 레벨과 호스트만 보고 싶다거나, 대괄호에 싸인 부분을 빼고 싶다거나, 공백으로 나뉜 특정 자리의 값만 뽑고 싶은 경우다. LogQL 은 파서 단계로 필드를 뽑고 line_format 으로 출력 줄을 다시 만드는 방식으로 이것을 한다.
파이프 뒤에 파서를 놓으면 그 줄에서 라벨이 추출된다. 로그 형식에 맞는 것을 쓴다.
{job="app"} | logfmt # level=info host=web01 msg="..."
{job="app"} | json # {"level":"info","host":"web01"}
{job="app"} | pattern `<_> <level> <msg>` # 자리 기준
{job="app"} | regexp `(?P<level>\w+) (?P<msg>.*)`
regexp 파서에는 반드시 이름 있는 캡처 그룹을 쓴다. (?P<name>...) 형태가 아닌 그룹은 무시된다. line_format "{{.1}}" 처럼 번호로 가져오는 문법은 LogQL 에 없다 — 이것을 쓰면 빈 문자열만 나온다.
자리만 맞으면 되는 로그는 pattern 파서가 훨씬 빠르고 읽기 쉽다. <_> 는 자리를 차지하되 버리는 표시다.
line_format 은 Go 템플릿으로 출력 줄을 다시 만든다. 추출된 라벨은 .이름 으로 참조한다.
{job="app"} | logfmt | line_format "{{.level}}"
{job="app"} | logfmt | line_format "{{.host}} {{.level}} {{.msg}}"
첫 번째, 세 번째, 다섯 번째 필드만 보고 싶다면 pattern 으로 자리를 잡고 이름을 붙인다.
{job="app"}
| pattern `<f1> <_> <f3> <_> <f5>`
| line_format "{{.f1}} {{.f3}} {{.f5}}"
값이 없는 필드는 빈 문자열이 된다. 기본값을 주려면 템플릿 함수를 쓴다.
{job="app"} | json | line_format `{{ .level | default "unknown" }} {{ .msg }}`
line_format 안에서 " 를 쓸 일이 많으면 큰따옴표 대신 백틱으로 감싼다.
줄 자체를 거르는 것과 추출한 라벨로 거르는 것은 다르다. 앞의 것이 훨씬 싸므로 가능하면 먼저 좁힌다.
{job="app"} |= "level=error" # 줄 필터. 색인 직후 적용되어 빠르다
{job="app"} | logfmt | level="error" # 라벨 필터. 모든 줄을 파싱한 뒤 걸러진다
대괄호가 들어간 줄을 빼려면 줄 필터에 부정 정규식을 쓴다.
{job="app"} !~ `\[.*\]`
줄은 남기고 대괄호 안쪽만 지우려면 replace 템플릿 함수를 쓴다. 정규식 치환은 regexReplaceAll 이다.
{job="app"} | line_format `{{ regexReplaceAll "\[[^\]]*\]" .__line__ "" }}`
.__line__ 이 원본 줄이다. 파서를 거치지 않고도 쓸 수 있다.
출력은 그대로 두고 라벨만 정리하려면 label_format · keep · drop 을 쓴다.
{job="app"} | logfmt | label_format lvl=level | keep lvl, host
{job="app"} | json | drop __error__
파싱에 실패한 줄에는 __error__ 라벨이 붙는다. 형식이 섞여 있는 스트림에서는 실패한 줄을 빼거나 실패만 따로 보는 것이 진단에 도움이 된다.
{job="app"} | json | __error__="" # 파싱 성공한 줄만
{job="app"} | json | __error__!="" # 실패한 줄만
파서와 line_format 은 색인이 아니라 스트림의 모든 줄에 대해 실행된다. 스트림 선택자와 줄 필터로 최대한 좁힌 뒤에 파서를 붙이는 것이 원칙이다.
# 나쁨 — 전부 파싱한 뒤 거른다
{job="app"} | json | level="error"
# 좋음 — 줄 필터로 먼저 줄인다
{job="app"} |= "error" | json | level="error"
json 파서에 필요한 필드만 지정하면 파싱 비용이 준다.
{job="app"} |= "error" | json level="level", msg="message"