2018년 8월 17일 금요일

grok 필터 작성 사례

로그스태시를 이용해서 로그를 전송하면 기본적으로 원본 로그는 message 필드에 저장된다.


이 상태에서는 분석할만한 게 없다. 인구 조사를 떠올려보자. 단순히 머릿수만 세면 대한민국 인구 5천만, 이러고 끝이다. 하지만 성별을 분리하면 성비 분석이 가능해지고, 나이를 추가하면 연령대별 인구 통계를 알 수 있다. 집계 대상을 다양화하면 다양한 분석이 가능해진다는 얘기.


로그 발생 개수 세는 수준을 벗어나려면 message 필드의 데이터를 세분화해야 한다. 이때 가장 많이 사용되는 게 정규표현식을 이용하는 grok 필터. 사례를 보자.

"필드":"구조를 갖는 로그 

<12>1 2018-08-14T01:24:40.62Z abc testServer 784 - - {"event_type":"Threat_Event","ipv4":"192.168.77.112","hostname":"test-pc","source_uuid":"3b6fsdff-a56f-48d1-96d1-2b65453684b4","occured":"14-Aug-2018 01:23:52","severity":"Warning","threat_type":"po tentially unwanted application","threat_name":"Win32/SlowPCfighter.A"

해당 로그에서 'event_type' 필드의 값을 추출해보자. 임의 문자를 1개 이상 검사하는 정규표현식 '.+'를 입력하자 전체 문자열이 검사된다.


event_type 필드값만을 별도로 추출하려면, 일단 그 값이 나타나기 전에 검사를 멈춰야 한다. 해당 로그는 "필드":"값" 구조를 갖기 때문에 "필드":" 문자열까지 검사한 후 멈추면 됨.

문자열 검사? NO! 문자열 구조 검사!

'.+' 이후, 'event_type":"'을 입력하자 전체 문자열 끝에 위치했던 검사점이 후퇴한다. 검사 과정은 총 300단계.


욕심없는 수량자

이때 끝까지 검사한 후 후퇴하는 방식 말고, 처음부터 하나씩 검사하면 더 빠른 검사가 가능하다. 방법은 '+' 수량자 뒤에 '?' 수량자를 붙이는 것. '?'는 단독으로 사용하면 검사 수량을 '없거나 1개'로 결정하지만 다른 수량자 뒤에 쓰이면 선행하는 수량자의 검사 범위를 최소로 제한한다.


결과는 같지만 검사 과정은 73단계로 줄었다.


정답이 없는 정규표현식

쓰는 사람 마음이란 얘기. 마지막 표현식 ":"은 특수문자이므로, '알파벳 및 숫자를 제외한 문자'를 검사하는 메타문자 '\W'로도 검사할 수 있다. 이어지는 문자열은 알파벳이기 때문에 필드값을 만난 표현식 '\W+'는 자연스럽게 검사 종료.


이제 필드값에 해당하는 'Threat_Event' 문장열만 검사하면 된다. 정규표현식에 'Threat_Event'를 입력하면 될까? 앞서 'event_type":"' 문자열을 정규표현식에 사용한 이유는 해당 문자열이 변화 범위가 고정된 문자열이기 때문이었다.

그러나 필드값 영역은 다양하게 변할 수 있다. 어떤 값이 오든 다 검사할 수 있어야 한다는 얘기. 다시 한번 구조를 보자. 필드값은 "" 사이에 위치한다. 그럼 "가 아닌 문자열을 검사해보면?

원하는 문자 검사 VS 원하지 않는 문자 회피

문자클래스를 이용한 표현식 '[^"]+'는 "이 아닌 모든 문자를 검사하며, "을 만나면 검사를 종료한다. 결과적으로 필드값만 검사.


마무리는 캡쳐그룹

이제 여기까지 검사 결과에서 필드 추출을 원하는 문자열만 캡쳐그룹으로 저장하면 된다.


다음은 로그스태시 연동 설정.

input {
 file { path => "D:\Elk\log\test.log"
        start_position => "beginning"
        sincedb_path => "/dev/null"
 }
}

filter {
 grok { match => { "message" => '.+?event_type\W+(?<event_type>[^"]+)' } }
}

output {
 elasticsearch { hosts => [ "localhost:9200" ] }
 stdout { codec => rubydebug }
}

참고로 grok 필터의 정규표현식 구분자로 " 대신 '를 사용한 이유는 표현식에 이미 "가 쓰였기 때문. '구분자 "'를 쓰려면 '표현식 "'를 예외처리(\") 해야 한다. 다음은 연동 결과.


추가 분류가 필요하다면 같은 방식으로 정규표현식을 추가해나가면 된다.

그런데 뭔가 쎄-하다?

grok 필터 정규표현식을 작성할 때는 message 필드에 저장된 문자열 전체를 검사한 후, 추출하려는 영역만 캡쳐그룹으로 저장해야 한다고 알고 있었는데, 사용된 정규표현식은 전체 문자열의 일부만을 검사하는데도 동작한다. 심지어 이렇게만 작성해도 동작하네?


5.X 버전에서는 분명 에러가 났었던 것 같은데, 6.X 버전에서 뭐가 바뀐건가? 아님 내 기억이 잘못됐나? 뭐 좋아졌구만(..) 질문 없었으면 계속 헛소리하고 다닐뻔했네. (e dd님 감사합니다)

문자열 처리의 강자 정규표현식

문자열 데이터를 처리할 일이 없다면 몰라도 그만이지만, 반대라면 도시락 싸들고 다니면서 사용을 권하고 싶다. 메타문자 10여 개 정도만 알아도 왠만한 작업은 다 할 수 있으니 외울 것도 많지 않다. 그냥 많이 써본 사람이 왕.

"업무 요령은 별게 있나요. 익숙해질 때까지 시간을 들이는 것" - '미생' 6수

댓글 없음:

댓글 쓰기

크리에이티브 커먼즈 라이선스