-
슬기롭게 로그(Log)를 쌓는 법Architecture 2020. 7. 22. 11:05
How to Make Effective Logging
모든 언어, 시스템과 프레임 워크를 통틀어서 공통적으로 가지고 있는 기능은 Log입니다. 우리는 응용프로그램이 의도한대로 동작하고 있는지 확인하기 위해서 또는 문제가 발생된 부분이 어디인지 찾기 위해서 로그를 적극적으로 활용하고 있습니다. 그런데 많은 개발자들이 시스템에 남기는 로그를 저마다의 생각과 개성을 반영하여 남기고 있습니다. 지금이라도 웹서버의 access.log를 열어보거나 Graylog같은 모니터링 시스템에서 로그를 확인해 보시기 바랍니다. 로그를 기반으로 시스템이 잘 운영되고 있다고 진단할 수 있나요? 아마도 꽤 오랜시간을 들여다 보거나 한숨이 절로 나오실 겁니다. 결국은 아무렇게 쌓은 로그는 많은 개발자들이 시스템을 운영하는 환경에서 문제를 진단하고 해결하는데 오히려 걸림돌로 작용되고 있습니다. 그래서 꼭 필요한 곳에 필요한 만큼 로그를 효율적으로 쌓는 방법에 대해서 이야기하고자 합니다.
코드 위치 기반 로깅 - Logging by Code Location
현재 가장 많은 사용하는 로그 적재 방법입니다. 코드의 위치를 기반으로 로그는 쌓고 있습니다. 클래스, 메소드에 진입하거나 결과를 리턴하기 직전등 위치를 기반으로 적재합니다. 아래의 코드 형태는 굉장히 일반적인 패턴을 보여줍니다.
class FreeBoardController { ... function List<ArticleDto> getList(int page) { Log.d("FreeBoardController, getList(), page="+page); List<ArticleDto> articles = boardService.getList(BOARD_NAME, page); Log.d("FreeBoardController, getList(), article count=" + articles.size()); return articles; } function void createArticle(@RequestBody ArticleBody articleBody) { boardService.postArticle(articleBody); } } class BoardService { function List<ArticleDto> getList(String boardName, int page) { ... Log.d("BoardService, getList(), boardName="+ boardName +", page="+ page +", article count="+ articleDtos.size()); return articleDtos; } function void postArticle(ArticleBody body) { ... } } // 2020-07-23 09:00:22.345 - FreeBoardController, getList(), page=1 // 2020-07-23 09:00:22.367 - BoardService, getList(), boardName=FREE, page=1, article count=20 // 2020-07-23 09:00:22.370 - FreeBoardController, getList(), article count=20
보시는 바와같이 하단의 로그에서 도움될 만한 정보는 사실상 없습니다. 단지 장애가 발생했을때 시간 순으로 어떻게 흘러갔는지 알아볼 수 있는 정도의 수준입니다. 그래서 시계열 로그 이상의 역할은 기대하기 어렵습니다. 여러 Request가 혼합됐을때 필요한 부분을 필터링해서 보는 것은 꽤 많은 잔머리가 필요합니다. 조금이나마 편리하게 로그를 남기려면 웹서버 스레드 또는 인증 세션등 시간 이외의 필터링할 수 있는 정보를 추가하는 방법이 있습니다.
기능 문맥 기반 로깅 - Logging by Feature Context
조금 생소한 내용일 수도 있지만 기능에 따라서 로그를 남기는 방식이다. 로그를 어떻게 남기느냐의 문제는 구현 관점에서 아무도 생각하지 않는 부분이다보니 그 중요성이나 효율성에 대해서 너무나 많이 다뤄지지 않은 것이 사실이다. 이건 어떤 경우에 사용하는 것일까? 저같은 경우에 안드로이드 개발을 할때 로컬 테스트를 하면서 로그를 기반으로 데이터가 잘 전달되고 흘러가는지 체크하는 과정에서 '기능 문맥 기반 로깅'을 사용했을때 엄청난 효율을 얻을 수 있었습니다.
class FreeBoardController { ... function List<ArticleDto> getList(int page) { ... } function void createArticle(@RequestBody ArticleBody articleBody) { ... Log.d("FreeBoardController, [CREATE_ARTICLE], ..."); boardService.saveArticle(articleBody); } function void modifyArticle(@RequestBody ArticleBody articleBody) { ... Log.d("FreeBoardController, [MODIFY_ARTICLE], ..."); boardService.saveArticle(articleBody); ... } } class BoardService { function List<ArticleDto> getList(String boardName, int page) { ... } function void saveArticle(ArticleBody body) { ... Log.d("BoardService.saveArticle, [CREATE_ARTICLE], content="+body.getContent()); Log.d("BoardService.saveArticle, [MODIFY_ARTICLE], key="+body.getKey()+", content="+body.getContent()); ... } } // 2020-07-23 09:00:22.345 - FreeBoardController, [CREATE_ARTICLE], ... // 2020-07-23 09:00:22.367 - BoardService.saveArticle, [CREATE_ARTICLE], content=... // 2020-07-23 09:00:22.367 - BoardService.saveArticle, [MODIFY_ARTICLE], key=..., content=...
위처럼 하나의 메소드가 여러가지 일을 해야하는 경우에는 더욱 빛을 발할 것입니다. 물론 SRP를 지키기 위해서 이런 로그가 많지 않게 해야겠죠. 하지만 이렇게 두가지 이상의 역할을 하는 경우에는 로그를 기능의 문맥별로 로그를 남긴다면 console이나 graylog에서 키워드 검색을 했을때 어떤 기능이 이상이 있는지 확인할 수 있을 것입니다.
Conclusion
위치 기반과 기능 문맥 기반으로 로그를 남기는 것을 이야기했습니다. 둘중에 어느것이 좋다라고 말할 수는 없습니다. 둘다 많은 장/단점을 가지고 있기 때문입니다. 하지만 오늘날 빅데이터 세상에서 로그를 어떻게 효율적으로 남기고 쌓느냐는 운영의 관점이 아니라 데이터 기반 비즈니스의 성공과 실패를 가를 수 있는 시작점이라고 생각합니다. 이젠 로그를 쌓는 것도 개성이 아닌 전략이 필요한 시대입니다.
장점 단점 코드 위치 기반 로깅 전후 맥락을 기반으로 오류의 위치를 유추할 수 있다. Value Stream 관점에서 로그를 파악하기 힘들다. 기능 문맥 기반 로깅 기능별, 레이어별로 코드의 흐름에 따라 로그를 파악할 수 있고, 키워드 검색이 가능하다. 기능에 따라 중복된 로그를 남길 수 있다. Thanks
Hans
'Architecture' 카테고리의 다른 글
Monolith를 MSA로 전환을 계획할때 필요한 세가지 (0) 2020.11.21 비즈니스 프로세스를 그리자. — BPMN 2.0 (2) 2020.10.24 AWS에서 효율적인 비용으로 시스템을 운영하기 (0) 2020.06.27 [MSA] 믿는 enum에 발등 찍힌다. (1) 2020.04.07 [MSA] 딜레마 - 처음부터 분리해? 말어? (0) 2020.04.01