Tag Archives: antlr

ANTLR을 사용한 웹로그 파서 만들기

컨텍스트

웹로그의 경우 아래와 같이 그 형식이 다양하다.

문자열을 파싱할 때 대개의 경우 구분자 주도 변환 기법을 사용한다. 즉, 파싱하려는 문자열에서 사용하지 않는 특수한 문자를 구분자로 선택한 후, 그 구분자를 사용해 문자열을 나누는 방식이다. 위와 같은 로그라면 공백을 사용해 문자열을 분할할 수 있지 않을까라는 생각이 먼저 떠오른다.

하지만 생각과는 달리, 구분자 주도 변환 기법은 이 경우에 그리 효과가 없다. sun one의 로그를 보면 알겠지만 URL 부분의 경우 쌍따옴표로 묶인 부분을 하나의 토큰으로 분할해야하지만, 쌍따옴표 안에 공백이 이미 사용되고 있기 때문이다.

구분자 대신 사용할 수 있는 방법으로 정규 표현식을 고려해볼 수 있다. 하지만 정규 표현식을 사용하게 되면, 정규 표현식 자체가 복잡할뿐만 아니라, 정규식으로 추출한 토큰은 그 순서에 따라 의미가 달라지게된다. 예를 들어 iis의 경우에는 두 번째 토큰이 IP인데 반해, sun one의 경우에는 URL과 관련된 정보다.

결국 웹로그 파서를 만드는 경우, 구분자 주도 변환 기법이나 정규식을 이용해 파서를 직접 만드는 방식 모두 효과가 없다.

해결책

문법을 작성하고, 문법을 기반으로 파서를 생성하는 구문 주도 변환 기법을 사용하고자 한다. 파서 생성기로는 널리 알려진 ANTLR을 사용한다. 그리고 파서의 결과로는 트리를 생성한다. 파서에서 시맨틱 모델을 바로 생성할 수도 있지만, 추상 구문 트리(AST)를 만든 후, 트리를 다시 시맨틱 모델로 변환하는 방식이 더 효과적이다. 웹로그를 파싱하는 경우 시맨틱 모델은 단순히 웹로그 도메인 객체다.

Step By Step

설치하기

ANTRL 사이트에서 ANTLRWorks를 다운로드한다. 현재 최신 버전은 1.4.3이다.

Window의 경우, 다운로드 받은 디렉토리로 이동해서 아래의 명령어를 실행한다.

Window 이외의 플랫폼을 사용중이라면, How to run ANTLRWorks를 참고한다.

문법 만들기

아래와 같이 문법 파일을 작성한다. ANTLR에서는 렉싱 규칙과, 파싱 규칙을 하나의 문법에서 작성한다.

위와 같이 문법파일을 작성한 후, Weblog.g라는 이름으로 저장한다. 이때 최상위의 문법명(Weblog)와 파일명은 같아야 한다.

렉싱 문법 테스트하기

작성한 렉싱 문법을 테스트한다. 하단의 Interpreter탭을 연다. 왼쪽의 텍스트 영역에 테스트할 입력 텍스트를 붙여넣는다. 여기에서는 아래의 웹로그를 테스트한다.

그리고나서, 명령버튼 영역에서 실행버튼을 클릭하면 아래와 같은 파스 트리를 볼 수 있다.

antlr_parse_tree

그림. ANTR 파스 트리

트리 생성 구문 추가하기

렉싱 규칙에 대한 테스트가 끝나면, 이제 트리를 생성하는 구문을 문법에 추가한다. ANTLR에서는 트리를 쉽게 생성할 수 있는 특수한 구문을 제공한다. 아래와 같이 문법을 수정한다.

렉서 코드와 파서 코드 생성하기

문법파일 작성이 완료되면, 아래의 명령어를 실행해서 렉서와 파서를 생성한다.

코드가 성공적으로 생성되면, 하위 폴더에 Weblog.tokens, WeblogLexer.java, WeblogParser.javar, 이렇게 세 파일이 생성된다.

또는 Generate 메뉴에서 [Generate Code] 메뉴를 선택한다. 이경우에는 문법파일이 위치한 디렉토리의 하위 디렉토리인 output에 생성된다. 디버깅 툴에서는 생성된 코드가 output 디렉토리에 있다고 가정하므로, 메뉴를 통해서 코드를 생성할 것을 추천한다.

파싱 규칙 테스트하기

트리 생성 구문까지 작성이 완료되면, 파싱 규칙을 테스트한다. 문법 파일을 수정했으므로, 코드를 새로 생성한다. 코드 생성이 끝나면, Run 메뉴에서 [Debug…] 메뉴를 선택한다.

antlr_debug_parse_tree

텍스트 영역에, 테스트하려는 로그 텍스트를 붙여넣은 후, OK 버튼을 누른다. 그리고 나서 [Go to End] 버튼을 클릭하면, 아래와 같이 파스 트리가 간소화시킨 AST가 정상적으로 만들어짐을 확인할 수 있다. (때에 따라 원격 서버에 접속할 수 없다는 메시지가 뜨는 경우가 있는데, 이 경우에는 ANTLRWorks를 재기동한다).

antlr_parse_tree_ast

생성할 코드에 속성 부여하기

아무런 속성을 지정하지 않을 경우, 렉서와 파서는 디폴트 패키지에 생성된다. 따라서 패키지를 설정하거나, 옵션을 주어 생성할 소스코드의 속성을 수정한다.

문법이 변경되었으므로, 코드를 다시 생성한다.

시맨틱 모델 클래스 만들기

여기에서는 단순히 웹로그를 저장할 도메인 클래스 하나면 충분하다. 여기에서는 별도의 도메인 클래스를 만들지 않고 HashMap<String, String>에 저장한다.

로더 프로그램 만들기

이제 렉서와 파서를 실행할 로더 프로그램을 만든다. 먼저 아래와 같이 이클립스에서 자바 프로젝트를 하나 생성한다. 그리고 다운로드받은 antlrworks-1.4.3.jar 파일을 라이브러리에 추가한다. 여기에서는 메이븐 프로젝트를 만들었고, 의존성 라이브러리에는 antlr-runtime과 junit4를 추가했다.

antlr_loader_package_structure

로더 클래스를 만들기 전, 테스트 케이스를 작성한다. 테스트케이스는 단순하다. 테스트를 실행하기 전, 로그데이터를 담고있는 파일을 읽어서 Reader 객체에 저장한다.

Reader 객체를 로더 객체를 생성하면서 생성자의 인자로 전달한다.

로더 클래스에서 가장 핵심이 되는 메서드는 run()으로, AST를 로드한 후 시맨틱 모델을 만드는 역할을 한다.

로그데이터를 담고 있는 샘플 파일을 만든다.

테스트케이스를 실행하면 성공했음을 알 수 있다.

추가할 사항

AST에서 자식 노드를 가져올 때 그 순서는 웹로그에 따라 다르다. 따라서 가져온 자식 노드가 어떤 데이터인지 알려면, 각 웹로그 유형별로 로그의 타입정보를 저장해야 한다. 그리고 나서 런타임에 로그 타입 정보를 읽어서, 몇 번째 노드가 무슨 데이터인지 인식하도록 프로그램을 개선해야 한다.

참고자료