Grok 은 이름 붙인 정규식 조각을 조합해 비정형 로그 한 줄을 필드로 나누는 표기법이다. %{패턴명:필드명} 형태로 쓰며, TIMESTAMP_ISO8601 · LOGLEVEL · IP · NUMBER · GREEDYDATA 같은 기본 패턴이 미리 정의돼 있다. 정규식을 매번 쓰는 것보다 읽기 쉽고 재사용하기 좋다.
NiFi 에서는 GrokReader 컨트롤러 서비스로 쓴다. 레코드 리더이므로 ConvertRecord · QueryRecord · PutDatabaseRecord 등 레코드 기반 프로세서에 붙는다. 단순히 값 몇 개만 뽑을 것이라면 ExtractText 로 정규식을 직접 쓰는 편이 가볍다.
| 속성 | 설명 |
|---|---|
| Grok Pattern File | 사용자 정의 패턴 파일 경로. 비우면 내장 패턴만 쓴다 |
| Grok Expression | 한 줄을 매칭할 식 |
| No Match Behavior | 매칭 실패한 줄 처리. append-to-previous-message · skip-line · raw-line |
| Schema Access Strategy | 대개 Use String Fields From Grok Expression |
여러 줄로 이어지는 스택 트레이스는 No Match Behavior 를 append-to-previous-message 로 두면 직전 레코드의 마지막 필드에 붙는다. 요청 본문이 여러 줄로 이어지는 로그도 같은 방식으로 다룬다.
%{TIMESTAMP_ISO8601:timestamp} %{LOGLEVEL:level} \[%{DATA:thread}\] %{NOTSPACE:logger} %{GREEDYDATA:message}
JAVACLASS 를 썼을 때 No definition for key 'JAVACLASS' found 가 나는 경우가 있다. 그 배포본의 내장 패턴 묶음에 해당 이름이 없다는 뜻이다. 두 가지로 해결한다.
첫째, 굳이 클래스 형태를 강제할 필요가 없으면 %{NOTSPACE:logger} 또는 %{GREEDYDATA:logger} 로 바꾼다. 로거 이름은 공백이 없으므로 NOTSPACE 로 충분하다.
둘째, 패턴 파일을 만들어 직접 정의하고 Grok Pattern File 에 지정한다.
JAVACLASS (?:[a-zA-Z$_][a-zA-Z$_0-9]*\.)*[a-zA-Z$_][a-zA-Z$_0-9]*
애플리케이션이 색을 입혀 로그를 남기면 이런 줄이 된다.
[2025-08-01 11:11:11]<ESC>[34m[INFO ]<ESC>[0;39m[http-nio-8080-exec-301][c.s.n.s.h.api.service.DataService:execute:37][TXID:] - Execute API Command
Grok 식에 제어문자를 그대로 넣으면 관리가 어렵다. 파싱 전에 ReplaceText 로 이스케이프 시퀀스를 지우는 편이 낫다.
| ReplaceText 속성 | 값 |
|---|---|
| Search Value | \x1B\[[0-9;]*m |
| Replacement Value | 빈 값 |
| Replacement Strategy | Regex Replace |
| Evaluation Mode | Line-by-Line |
정리한 뒤의 Grok 식은 다음처럼 쓴다.
\[%{TIMESTAMP_ISO8601:timestamp}\]\[%{LOGLEVEL:level}\s*\]\[%{DATA:thread}\]\[%{DATA:logger}\]\[TXID:%{DATA:txid}\] - %{GREEDYDATA:message}
대괄호는 정규식 메타문자이므로 이스케이프한다. [INFO ] 처럼 레벨 뒤에 공백이 붙는 형식이면 \s* 로 흡수한다.
패턴은 반드시 실제 로그 몇 줄로 검증한다. Grok Debugger 같은 도구나 NiFi 에서 소량을 흘려 확인한 뒤 적용하는 것이 안전하다.
GrokReader 로 읽고 JsonRecordSetWriter · AvroRecordSetWriter · ParquetRecordSetWriter 로 쓰면 형식 변환이 된다. 확장자만 바꾸면 되는 것이 아니라 Writer 를 바꿔야 실제 형식이 바뀐다.
파일 이름만 바꾸는 것은 UpdateAttribute 로 한다.
filename = ${filename:replaceLast('\.log$', '.parquet')}