본문 바로가기

Java

[Java] 누가 날 부른거요? 날 호출한 클래스 알아내기.

회사에서 검색 서비스 개발 및 운영을 담당하고 있는데요.. 업무 중 하나가..
솔루션으로 구매하여 사용하는 엔진의 RAW-LEVEL의 API를 우리 개발자들이
사용하기 편하게 한번 감싸서 검색 API를 개발해 제공하는 업무가 있습니다. 

주로 성능보다는 재사용성이나 사용하는 입장에서 쓰기 편하게
그리고 검색 서버의 IP등의 프로퍼티 값들의 변경에 쉽게 대응 할 수 있고
디버깅을 쉽게 할 수 있도록 하는데 중점을 두고 API를 만들어 제공하고 있는데요..

검색 API라는 것이 단순히 쇼핑몰의 검색결과 페이지에서만 사용되는 것이 아니고
카테고리, 기획전, 리뷰, 모바일, 오픈API등 굉장히 많은 곳에서 사용되게 마련이지요..

그러다보니 의도하지 않은 검색 쿼리들이 유입되는 경우가 있거나
의도적인 악성 검색 쿼리들이 유입되는 경우도 있습니다.

제가 처음 이곳에 왔을 때 이렇게 엔진으로 유입되는 쿼리들을 보고 어디에서 유입되는 쿼리인지
확인 할 수 있는 방법이 전혀 없었습니다. 검색 쿼리를 보고 짐작을 한 뿐이었죠..
("키워드가 있으니 통합검색 페이지겠구나.... 카테고리번호와 키워드가 있으니.. 통합검색아니면 카테고리 페이지겠구나..등)

한번은 검색엔진으로 하루에 검색 request가 50배 이상 들어오는 경우가 있었습니다.
짐작으로 무한루프가 돌고 있는 것 같다라고 생각은 하였고, 실제로 검색 API를 사용하는 곳의 로직에 오류가 있어서
무한루프를 돌면서 검색 API를 호출하고 있었었지만 당시에 이 쿼리가 어디에서 들어오는지 확인 할 방법이 없었습니다.

위의 검색 쿼리를 보고 추론하는 방법으로 예상되는 모든 소스를 뒤져서 수정을 했었죠.

이런 일들을 좀 겪다보니 검색 API를 호출하는 클래스는 대부분 Controller 레벨이고
이 클래스의 이름과 검색 API를 호출하는 메서드를 알면 어느 URL을 통해서 들어오는지도
추적 할 수 있게다 싶은 생각이 들었습니다.

누가 날 부른거요??
 
현재 스레드가 수행되고 있는 클래스의 이름을 확인 하는 법은 비교적 간단합니다.

this.getClass().getName();


하지만 필요한건 나를 호출한 클래스입니다.

우선 검색을 위해 제공되는 API를 SearchService라 하고 이를 호출하는 Controller를
SearchController라 하겠습니다.

  SearchService.java


SearchController.java

SearchConroller의 main 메서드를 실행하면 [search...........] 라고 찍힐 것 입니다.

일반적인 사용패턴이 위와 같기 때문에 SearchService의 search 메서드에 검색 API를 호출한 클래스를 뽑아내는
로직이 들어간다면 어디서 API를 사용하는지 모두 알 수 있을 것 같았습니다.

이 부분을 Throwable을 이용하여 구현하였습니다.


SearchService.java

위 main 메서드를 실행하면 결과는 아래와 같이 나옵니다.

search............
throwable.SearchService[search](11)
throwable.SearchController[search](12)
throwable.SearchController[main](7)


실제로 필요한 것은 바로 이전 호출한 클래스와 메서드의 정보였기 때문에 StackTraceElement[]에서 1번째 index를 뽑아서 (0 아닙니다.) 이를 로깅하도록 하였습니다.

로그파일의 사이즈가 커지고 IO도 다소 늘어날 것이고
어쩌면 new Throwable이라는 것 자체가 부하를 가져 올 수도 있을 것입니다. (사실 이부분은 지금도 어느정도의 영향이 있는지
잘 모르겠습니다.)
하지만 이를 적용하여 얻은 운영상의 이점에 비하면 굉장히 작은 단점이었습니다.

이 로직을 적용한 이후 굉장히 많은 악성쿼리들의 유입 경로를 찾아내어 수정하고 그 패턴을 확인하여
validation을 강화 할 수 있었던 것은 보너스였겠죠.. :D