💡 개인적인 고찰일 뿐 정답이 아닙니다.
로그가 필요한 이유는 정말 많지만, 여기선 주로 디버깅 관점에서 분석하고 있습니다.
혹시나 더 나은 아이디어나 다른 관점이 있다면, 부디 언제든지 댓글로 남겨주시면 감사드리겠습니다.
제발.
1. Introduction
📌 Interview
👤: 로그는 언제 남겨야 하나요?
🙋♂️: 아직 많은 고민 중에 있지만, 분명한 건 Presentation과 외부 액터와 I/O가 발생하는 시점, 그리고 영속화에 변경이 발생할 때입니다!
👤: 영속성이 수정될 때마다 남기면 로그는 어디서 남기나요? 전부 서비스 계층에서 남기나요? UPDATE 연산할 때마다 로그로 남길 건가요?
🙎♂️: ...?
(공격적인 질문은 아니었다. 다만, 갑자기 질문이 쏟아져서 굉장히 당황했다.)
이 한심한 답변을 하고, 집으로 돌아가는 내내 이 방법에 대해 생각해보았다.
물론 저기서 역으로 질문을 해본다던가 했을 수도 있었겠지만, 머릿 속에서 '어라, 영속 상태가 변경되면, 로그 정보 내용이나 레벨이 다를 수는 있어도 무조건 남겨야 하는 거 아닌가..?'라는 의문이 꽂혀서 뇌가 정지해버렸다.
모두 서비스 계층에서 남길 것이냐는 질문은 AOP 로깅 처리나, DB 쿼리 로그 저장 등을 의도한 것일 수도 있을 듯 하다.
심각한 건 다른 질문에 대한 답변들은 긴장해서 실수했다고 쳐도, 이건 진짜 어떻게 답변했어야 좋았을 지 떠오르질 않는다.
그리고 이 문제는 오랫동안 날 괴롭혔던 난제였는데, 이대로 방치해서는 안 될 것 같다.
찢자, 찢어버리자.
이제 면접 합불 여부같은 건 중요하지 않다.
난 이 오래된 의문점을 조금이나마 해결하지 않으면, 편하게 잠을 이룰 수 없을 거 같다.
+) 여담
이 문제에 대해 정말 많은 현직 개발자 분들께 "언제 로그를 찍어야 한다고 생각하나요?"라고 여쭤봤더니, 하나같이 "전부 다"라고 답변해줌. ㅋㅋ
📌 A problem with no answer
난 지금까지 수많은 개념들을 나름대로 체계적으로 분석하고, 유의미한 결론을 이끌어 냈음에도 불구하고, 로그만큼은 정답을 구하지 못했다.
이 이유를 올바르게 정의하지 못 한다면, 이번 포스팅도 무의미하게 끝날 것 같아서 생각을 정리해보았다.
이상하지 않은가?
로그에 관련한 수많은 글들을 읽으면서, 로그 레벨, 로그 라이브러리, 로그 중요성 같은 내용은 지천에 널렸다.
그런데 정작 "그래서 로그는 어디 남겨야 하는데요?"라는 질문에 대한 답을 하면 "필요한 것에 찍어라"라는 답만이 돌아온다.
"그럼 일단 필요해보이는 곳에 전부 남겨야 하나요?"라는 질문을 하면, 이번에는 "로그는 너무 많아도 안 되고, 적어도 안 된다"라고 이야기한다.
다른 여러 질문들도 비슷하게 흘러간다. 그나마 좀 구체적으로 답해주는 경우가 있는데, 문제는 이 경우 사람마다 말이 달라진다.
- 보통 전문가 조차도 이렇게 추상적으로 이야기를 할 정도면, 정답이 없다는 이야기다. (물론, 과대 해석의 여지가 있습니다.)
- 정답이 없다면 그럼 어떻게 판단해야 하는 거지?
- 오직 경험과 끊임없는 분석이 필요하다는 말이다.
- 그걸 어떻게 하는데?
- 실제 사용자 트래픽 받아보면서, 온갖 에러에 대응하는 과정 속에 어떤 점이 디버깅을 힘들게 만드는 지 분석하고 알아보아야 한다.
그럼, 실제 트래픽을 받아보지 못한 나같은 사람은 언제나 로그를 온전히 이해하지 못하는 걸까?
솔직히 그럴 확률이 높다.
이론적으로 백날 연구해봐야, 실제 경험 한 번 보다 못 한 것은 사실이니까.
그럼에도 나름의 전략을 세우기 위해서는 사람들이 적어놓은 다양한 의견과 경험들을 종합적으로 분석해보는 것이 유의미하다고 판단되었다.
따라서, 이번 포스팅을 위한 전략은 다음과 같다.
- "로그는 왜 필요한가?"
- 그 가치를 조금이나마 이해해야, 다음을 이해할 수 있을 것이다.
- "로그는 언제 필요한가?"
- 도메인에 따라 똑같은 내용도 로그를 남길 필요가 있을 수도, 없을 수도 있고, 이는 회사마다 다를 수도 있다.
- 하지만 필수적으로 로깅을 해야하는 경우는 반드시 존재할 것이다.
- "로그는 어디서 기록해야 하는가?"
- 계층화된 서버 구조에서 각 서버는 로그를 남겨야 한다.
- 이를 수집하는 것은 중앙집중화 하더라도, 어쨌든 로그는 각 컴포넌트들이 자체적으로 판단하여 남겨야 할 것이다.
- 올바른 도구를 선택하는 것은 여정의 일부일 뿐이다. 개발자가 단 하나의 기술 스택으로 모든 것을 구현하지 않을 테니, "당면한 작업에 적합한 도구"를 선택하는 것이 중요하다.
- "로그는 무엇을 기록해야 하는가?"
- 어떤 내용을 남기는 것이 용이할 것인가? 디버깅을 위해서라면? AI 학습을 위해서라면? 보안 감사를 위해서라면?
- 특히, 가장 내가 고려해볼 수 있는 것은 개발을 위한 로그를 위해 무엇을, 어떻게 남길 지를 알아봐야 한다. 단순히 파일에 모든 로그를 뿌린다면, 굉장히 문제가 어려워진다.
2. Why?
📌 Value of Logs
보통 로그를 디버깅 목적이나, AI 학습 목적으로 사용한다는 것은 흔히들 알고 있는 사실이다.
하지만 예전에 개인정보 수집 및 이용 동의서를 작성하는 과정에서, 법적으로 반드시 유지하거나, 유지해선 안 되는 로그도 존재한다는 사실을 알게 된 적이 있었다.
이는 통신비밀보호법이나 정보통신망법을 봐도 알 수 있다.
즉, 디버깅이나 서비스 품질 개선을 위한 로깅 외에도, 법적으로 반드시 유지해야 하는 로그들도 존재한다는 것이다.
로그의 가치(목적)를 가장 폭 넓게 정의해보자면 다음과 같다.
- 소프트웨어 로그
- 보안 로그
- AI 학습 로그
- 감사 로그
📌 Software Log
- 목적: 시스템 운영 및 디버깅을 위한 정보 제공
- 애플리케이션 동작 상태를 추적하고, 예외 발생 시 원인 파악
- 성능 모니터링 및 병목 현상 분석
- 장애 발생 시 신속한 원인 분석 및 해결을 위한 단서 제공
- 예시
- API 요청 및 응답 로그
- 시스템 오류 및 예외
- 애플리케이션 시작 및 종료 이벤트
여기서 소프트웨어 로그를 하나로 퉁쳐버렸지만, 목적의 하위 카테고리 하나하나가 중요한 목적이 존재한다.
이를 나름대로 표로 표현해보았다.
로그 유형 | 주요 목적 | 예시 |
성능 모니터링 로그 | 시스템 성능 측정 및 최적화 | 응답 시간, 리소스 사용량 |
장애 및 복구 로그 | 장애 감지 및 원인 분석 | 장애 발생 시각, 자동 복구 진행 정보 |
트랜잭션 로그 | 데이터 무결성 및 트랜잭션 상태 추적 | DB 트랜잭션 성공/실패, 롤백 |
이벤트 로그 | 주요 이벤트 발생 기록 | 설정 변경, 기능 사용 내역 |
데이터 처리 로그 | 데이터 처리 과정 추적 및 오류 감지 | ETL(Extract & Transform & Load) 작업 상태, 데이터 변환 오류 |
API 호출 로그 | API 요청/응답 추적 및 성능 분석 | 외부 API 호출, 응답 코드 |
사용자 행동 로그 | 사용자 패턴 분석 및 이상 감지 | 페이지 방문, 클릭 이벤트 |
📌 Security Log
- 목적: 시스템 보안 감시 및 이상 징후 탐지
- 보안 위협(해킹, 내부자 위협) 감지 및 대응
- 사용자 인증 및 접근 제어 이력 관리
- 규제 준수를 위한 감사 기록 유지
- 예시
- 로그인/로그아웃 및 인증 실패 로그
- 방화벽 및 IDS(침입 탐지 시스템) 로그
- 권한 변경 및 보안 정책 수정 내역
📌 AI Learning Log
- 목적: 사용자의 행동 패턴을 파악하여 모델 개선
- AI 모델 학습을 위한 실제 사용자 데이터 수집 및 분석
- 다양한 상황에서 발생하는 패턴을 학습 데이터에 반영하여 모델 성능 향상
- 이상 행동을 감지하여 보안 강화
- 예시
- 음성 인식 AI: 사용자의 발화 로그를 수집하여 다양한 억양 및 발음을 반영
- 추천 시스템: 사용자의 클릭, 검색, 구매 이력을 저장하여 추천 모델 학습
📌 Audit Log
- 목적: 법적 준수 및 변경 이력 추적
- 기업 내부 또는 외부 규정 준수를 위한 변경 이력 관리
- 데이터베이스 변경 사항 추적 (ex: 금융, 의료 시스템)
- 비즈니스적으로 중요한 이벤트 기록
- 예시
- 사용자의 계정 삭제 또는 개인정보 변경 기록
- 결제 정보 변경(결제 승인/취소)
- 정보통신법 관련 항목 준수를 위한 사용자 행동 관리
3. When?
📌 For Debugging
스택 오버 플로우에서 가장 많은 추천을 받은 Justin의 견해에선, 로그를 언제 찍어야 하는 질문에 대한 본질적인 질문을 하고 있다.
Think about how the log message will be used.
즉, 문제가 발생했을 때에 로그 메시지를 어떻게 활용할 것인가를 고민하는 것에서 출발해야 한다는 것이다.
(아래 글머리 기호들은 단순히 스택 오버플로우 글을 정리한 내용일 뿐입니다.)
- 로깅의 목적을 혼동해선 안 된다.
- 로깅 vs 오류 처리
- 로그는 일반적으로무언가 문제가 발생했고, 그 이유가 불분명할 때만 사용된다.
- 예외가 발생했지만, 이에 대해 정상적인 처리가 이루어진 경우 로그를 남길 이유가 없다.
- 오류가 얼마나 자주 발생하는지 통계를 원하는 경우에는 사용하기도 한다. (카카오 페이 기술 블로그 글에서 DB 쿼리 호출 순위를 매겨봤다는 내용을 본 적이 있다.)
- 로깅 vs 감사(auditing)
- 감사를 위한 로그는 법적/보안적인 이유로 데이터 변경 내역을 추적하는 것이지, 디버깅을 위한 것이 아니다.
- 디버깅을 위한 로그와 감사를 위한 로그는 사용 목적, 저장 방식, 보존 정책 등 다방면에서 다르다.
- 다만, 많은 시스템에서 두 가지가 겹치는 경우가 많다. (예를 들어, 로그인 기록은 운영 & 디버깅 목적으로도 사용되지만, 보안 & 법적 규제 준수를 위해서도 필요하다.)
- 로깅 vs 오류 처리
- 디버깅을 위한 로깅은 순전히 문제의 원인을 진단하기 위해 존재한다.
- 불필요한 로깅이 너무 많으면, 소프트웨어 성능과 로그 식별, 저장 공간 관리에 어려움을 겪게 될 수 있다.
- 무언가를 기록할지 말지 확신이 서지 않는다면, 일단 기록하는 것이 낫다.
일단 위 내용을 정리하자면, 다음과 같을 것이다.
💡 예상치 못한(적절한 오류 처리가 수행되지 않는) 문제에 대해서 스택 추적을 기록하라.
하지만 이것도 결국 언제 정할 지 잘 판단해서 남기라는 말이라..뭔가 기준점을 잡기 모호하다.
그리고 로그는 문제가 있을 때만 사용한다? 난 이 의견에 동의하지 않는다. (단순히 디버깅을 위한 목적의 로그라 하더라도.)
위 주장대로라면, DEBUG, ERROR에 속하는 에러 레벨만 사용해도 충분하나, INFO 레벨의 로그는 문제가 없는 경우조차 필요한 경우가 존재하기 때문이다.
🙋♂️ (소신 발언) 이상적인 상태의 소프트웨어 로깅 또한 필수적이다.
- 문제가 발생하기 전에, 원인을 사전 탐지하기 위한 로그도 필요하다.
- 문제가 발생한 후에만 결과에 대한 로그를 확인한다면, 예방할 기회를 놓치게 될 수 있다.
- ex: 서비스 응답 시간이 평소보다 증가했지만, 아직 장애 수준은 아닌 경우
- 단순 트래픽 스파이크(일시적으로 트래픽이 급증하는 현상)일 수 있지만, 어쨌든 조치 필요 여부를 판단할 수 있는 것도 로그 덕분이다.
- 서버 구성에 따라 트래픽 모니터링은 Proxy 서버나 GW에서 수행하고 있을 수 있다. 애플리케이션 레벨에서 봤을 땐, 비즈니스 처리(DB 조회, 비즈니스 로직 수행 시간 등) 속도에 대한 성능 측정이 주된 관심사일 것이다.
- 이러한 로그는 문제가 발생하기 전에 이상 징후를 감지할 수 있도록 돕는다.
- 문제가 발생한 후에만 결과에 대한 로그를 확인한다면, 예방할 기회를 놓치게 될 수 있다.
- 장애가 발생했을 때, 단서를 위한 로그가 필요하다.
- 장애가 발생한 시점의 로그를 trace로 뿌리면 만사 해결이 될 것 같지만, 그렇지 않을 수도 있다.
- INFO 로그로 이상적인 시스템 상태를 보여주면, 장애 전후의 차이를 분석하기가 더 수월하다.
- ex1: 장애 발생 직전에 세션 개수가 급격히 증가한 로그가 있다면, 장애 원인이 트래픽 급증 때문인지 파악할 수 있다.
- ex2(실제 사례): 스웨거에서 요청 시 타입 미스 매치로 long 타입 ID의 bit가 손실되는 바람에 조회가 정상 동작하지 않는 경우가 있었다. Presentation 로그를 통해 문제를 빠르게 식별할 수 있었다.
- ex3(실제 사례): 분산 락이 정상 동작하지 않아 실패한 서비스가 있었는데, CPU 사용량이 급증하면서 수행 시간이 증가하면서 DB commit이 이루어지기 전에 key가 방출되는 상황이 있었다. 비즈니스 수행 시간 로그 덕에 이상을 빠르게 감지할 수 있었다.
- 성능 측정 및 튜닝을 위해서도 로그는 필요하다.
- 디버깅 로그는 단순히 오류를 찾는 목적 뿐만 아니라, 성능 분석 및 최적화에도 필요하다.
- 예를 들어, SQL 실행 시간을 로깅하면 느린 쿼리를 사전에 발견하고 최적화할 수 있다.
- 만약, 로그를 문제가 발생했을 때만 남긴다면, 성능 분석을 위한 로그도 필요 없다는 뜻이 된다.
- 디버깅 로그는 단순히 오류를 찾는 목적 뿐만 아니라, 성능 분석 및 최적화에도 필요하다.
좋다, 여기까진 나름 잘 분석한 거 같다.
그럼 대체 INFO 레벨의 정보들은 언제 남겨야 하는지에 대한 본질적인 의문이 풀리질 않았다.
📌 For Information
⚠️ 정말정말정말 주관적으로 생각한, 반드시 "INFO" 레벨의 로깅이 필요한 지점을 구상해본 것입니다. (감사 로그는 제외)
1️⃣ Presentation Layer
- 목적
- 클라이언트와-서버 간의 요청/응답 데이터 추적
- 특정 요청이 실패했을 때 원인 분석
- 예시
- 주요 API 요청 & 응답 정보 (가능하다면 헤더 정보까지 모두 포함)
- 비정상적인 요청 (서버 계층 구조에 따라, 어디서, 어떤 로그들을 수집해야 할 지 전략이 다양)
- WAS: 잘못된 API 호출, 예상치 못한 HTTP 상태 코드 등
- Proxy/GW: 주로 보안과 직결된 로그들
- 트래픽 제한 초과 요청(429 예외)
- 차단된 요청(403, WAF 탐지 로그)
- 리디렉션(301, 302)
- 비정상적인 User-Agent, 허용되지 않은 Origin (CORS 관련 로그)
- 대역폭 제한으로 인한 응답 지연 등
- 주의점
- 응답 body를 모두 출력하면, 로그가 너무 방대해질 우려가 있으므로 필터링이 필요하다.
- 응답 코드, 헤더 정보 정도만 로깅해도 괜찮을 거 같음.
- 만약, 각 계층이 중복 로그를 남기지 않겠다고 한다면, 각 서버 계층들이 로그를 연계하여 분석이 가능한 설계가 필요하다. (시스템 복잡도 증가)
- 요청, 응답에 민감한 정보(ex: 비밀번호)를 포함해서는 안 된다.
- 예외가 발생했을 때, 너무 상세한 정보를 응답으로 노출시키지 않도록 주의해야 한다.
- 응답 body를 모두 출력하면, 로그가 너무 방대해질 우려가 있으므로 필터링이 필요하다.
- 방법
- 일반적으로 WAS의 Controller 계층, 클라이언트의 Repository에 로그 관련 계층을 추가하거나, AOP 기능으로 분리하는 것이 좋다. (어차피 거의 패턴이 비슷하기 때문)
- Proxy나 API GW등은 요청 필터 로깅 활성화해서, 별도 보안 로그로 관리하는 것이 안전할 듯.
2️⃣ I/O
- 목적
- 외부 파일 시스템 혹은 Third-Party 시스템과의 아웃바운드 요청/응답 기록
- 느린 응답, 높은 오류율, 반복적인 호출 등의 이슈 감지
- 통신 실패 시 원인 분석
- 비정상적인 요청(ex: 예상치 못한 도메인 요청 -> 사용자 요청을 그대로 url로 잡는 경우 취약, 보안 위협 탐지) 감지
- 예시
- DB, Cache, MessageQueue 등 주요 인프라와의 통신 수행 시 오류 발생 이유 (파일 시스템(ex: S3: FTP 등), WebSocket 통신, gRPC 호출 등 포함)
- 외부 API 혹은 인프라 호출 경로와 파라미터 기록
- 응답 정보의 상세함은 통신 대상 및 데이터 중요도에 따라 따라 상이하다고 생각
- OWASP - API10:2023 - Unsafe Consumption of APIs를 생각해보면, 보안 위협을 방지하기 위한 로그 장치를 구상해볼 필요가 있음.
- 주의점
- 상호작용이 매우 빈번한 경우(ex: 대량 배치 작업)에는 너무 많은 I/O 로그가 성능 저하를 유발할 수 있으므로, 샘플링 적용에 대한 고려가 필요하다.
- Third-Party API 응답 데이터를 너무 상세히 남길 경우 개인정보 유출로 작용할 수 있으므로, 상호작용을 하는 Actor 별로 상세한 로그 계층을 설계하는 것이 필요할 수도 있다.
- 방법
- 서버라면 주로 Repository 혹은 Client 계층에서 사용하고 있을 텐데, 각 인프라에 맞춘 AOP를 적용하는 것이 안전할 것 같다.
- 상호작용 하는 외부 Actor가 무엇인지에 따라, 다른 로그 정책을 적용할 수 있는 구조를 고려해보는 것이 좋을 수도 있다. (모든 Client 요청/응답에 대한 정보를 AOP로 통일하는 것이 적절하지 않다고 판단된다면!)
3️⃣ Persistence
- 목적
- 데이터 변경 사항 추적 및 무결성 보장
- 비즈니스 크리티컬 이벤트(ex: 주문 상태 변경, 결제 성공 등) 모니터링
- WAL: 트랜잭션 롤백 및 장애 발생 시 복구 가능성 확보
- 예시
- 핵심 비즈니스 데이터들의 변경 상태 확인 (ex: 주문 상태 변경, 사용자 역할 변경)
- 주의점
- 디버깅을 위한 로그인지, 감사를 위한 로그인지 구분이 필요
- 디버깅: 영속화된 데이터들이 올바른 상태 규칙을 유지하고 있는지 파악하기 위함
- 감사: 보안 및 법적 규제를 준수하기 위함
- 단순 조회 시엔 영속화의 상태 변경이 발생하지 않으므로, 굳이 로깅이 필요하지 않을 듯
- 모든 영속화된 데이터 변경 시점에 로그를 남겨야 하는가?
- 대량 배치 작업
- 하루에 수십만~수백만 건의 데이터 변경이 발생할 수 있으므로, 전체 배치 작업의 요약 정보만 남기는 게 득일 수 있음.
- 캐시 갱신
- Redis같은 캐시에 데이터를 업데이트하는 것은 성능 최적화에 가까움.
- 캐시 정책이 쓰기 작업 시 DB와 일시적으로 동기화되지 않는 것을 허락하지 않는다면, 별도의 캐싱을 수행하지 않는 것이 나을 듯하다.
- 트랜잭션 내부에서 임시 변경
- 사용자가 결제 진행 도중 실패하거나, 좌석 예약은 했는데 결제 안 하고 이탈하여 롤백하는 경우엔 성공만 INFO로 기록
- 실패는 WARN 레벨로 기록하는 게 나을 수도 있음.
- 너무 자주 변경되는 값
- 사용자의 마지막 로그인 시간, 페이지 조회수 증가 같은 데이터는 의미도 없고, 서버에 부담만 줌
- 로그인 실패 횟수를 카운트 한다면, 특정 이벤트가 누적되었을 때만 로깅하는 것이 나을 수도 있음. (ex: 로그인 실패 5회 초과했을 때만 로깅)
- 정말 필요하다면, 모니터링 시스템에서 집계하도록 구성하는 것이 좋을 듯.
- 테스트를 위한 로그
- 개발할 때 시스템 동작을 확인하기 위해 로그를 남겼을 수 있으나, 운영 환경에서 필요없다면 지우는 것이 좋다. (안 그러면 운영 로그랑 섞여서 노이즈가 발생한다.)
- 대량 배치 작업
- 디버깅을 위한 로그인지, 감사를 위한 로그인지 구분이 필요
- 방법
- 애플리케이션 서비스 계층(Service Layer)에서 처리
- 서비스 계층에서 모든 로깅을 처리하면, 중복 코드와 중복 로그가 많아지고 유지보수가 어려워짐.
- 변경되었다고 로그를 기록했는데, 트랜잭션이 롤백되면 일관성 문제가 발생할 수 있음.
- 주요 비즈니스 로직(ex: 주문 상태 변경, 결제 완료, 사용자 역할 변경 등)과 연관된 변경 사항을 기록할 때 사용하는 것이 적합하다.
- 데이터 액세스 계층(Repository Layer)에서 처리
- JPA/Hibernate를 사용하면, Spring Data JPA의 @EntityListeners를 활용하여 Entity의 Lifecycle 내 변경사항을 감지할 수 있다. (아니, 이걸 이렇게도 사용할 수 있었다고..???)
- 각 Entity별 변경 사항을 추적하기 쉽다.
- 비즈니스 컨텍스트를 반영한 세부 로깅은 어렵다.
- AOP를 활용한 로깅
- 모든 서비스 클래스 혹은 특정 메서드들만 가로채서 로깅하면, 중복 로그를 줄이고, 코드 유지/보수가 쉽다.
- 단, 각 서비스별 맞춤형 변경 사항을 기록하기가 매우 어렵다.
- 데이터베이스 트리거 활용
- 애플리케이션이 아닌 DB에서 직접 변경을 감지하여 로깅
- WAL(Write-Ahead Logging)같은 데이터베이스에 커밋되기 전에 변경 사항을 로깅하고, 시스템 장애 발생 시 Tx가 완전히 완료되거나 롤백될 수 있도록 일관성을 보장하는 방법도 있다.
- 애플리케이션 로직과 독립적이기 때문에 관리 복잡도와 외부 시스템 의존도 증가
- 애플리케이션 서비스 계층(Service Layer)에서 처리
4️⃣ User Behavior
- 목적
- 사용자 활동 분석 및 UX 개선 (주로 클라이언트 측에서 수집)
- A/B 테스트 및 피처 플래그 기반 실험 데이터 수집
- 이상 행동 탐지 (ex: 비정상적 로그인 시도, 봇 활동 탐지)
- 고객 지원 및 디버깅을 위한 히스토리 기록
- 사용자 맞춤형 서비스 개발 (ex: 사용자 맞춤형 AI 학습)
- 예시
- 예전에 지인이 해외 숙박 앱에서 결제를 했는데, 갑자기 취소가 되어서 로그를 요청한 적이 있었다. 한참을 싸우다가 사측에서 로그 데이터를 보여줬는데, 다음 데이터들을 모두 저장하고 있었다. (사진을 올리면 뭔가 문제가 될 거 같아서 안 올렸습니다.)
- 로그 시간, 페이지 타입 이름, 액션 이름, 액션 타입, 액션 요소 값, 액션 퍼널(funnel) 상태, 예약 아이디, 사용자 아이디, ip 주소
- 내가 봐도 사용자가 어떤 행동과 화면 전환을 했는지 명확할 정도로 모든 정보를 기록하고 있었는데, 나도 저렇게 하면 되겠다 싶었다.
- 예전에 지인이 해외 숙박 앱에서 결제를 했는데, 갑자기 취소가 되어서 로그를 요청한 적이 있었다. 한참을 싸우다가 사측에서 로그 데이터를 보여줬는데, 다음 데이터들을 모두 저장하고 있었다. (사진을 올리면 뭔가 문제가 될 거 같아서 안 올렸습니다.)
- 주의점
- 과도한 이벤트 로깅 방지
- 개인정보 보호
- 이벤트 기반 분석 시스템 연계 고려 필요 (ex: Google Analytics)
- 방법
- 클라이언트 로깅: 사용자의 클릭 및 화면 전환 이벤트 (ex: Google Analytics)
- 서버 로깅: 주로 보안 관점에서 추적하는 경우가 많다고 생각함. (단, SSR 방식이라면 얘기가 또 달라질 듯)
- 이벤트 기반 로깅: 특정 사용자 이벤트 발생 시 비동기 처리 (ex: Kafka, Webhook)
5️⃣ System Resource
(쓰고보니, 여기 항목들은 대부분 WARN 혹은 Error 레벨이 되지 않을까 싶습니다.)
- 목적
- 서버 상태 모니터링 및 성능 분석
- 장애 감지 및 예방
- 리소스 사용량 기반 자동 확장(Auto Scaling) 전략 수립
- 예시
- CPU: CPU 사용량이 일정 임계값 초과 시
- 메모리: 메모리 사용량이 부족하거나, OOM 발생 가능성이 의심되는 경우
- 네트워크 트래픽: 비정상적 트래픽 급증 시
- 주의점
- 24h 실시간 정보를 수집하므로, 모니터링 시스템과 연계하는 것을 고려하는 것이 안전
- 보통 여기서 문제가 발생하면 심각한 오류로 이어질 가능성이 높기 때문에, 신속 대응이 가능하도록 알림(Notification) 시스템과 연동 조치 필요
- 방법
- Application: Spring Boot Actuator같은 도구로 health check
- OS: 서버 시스템 메트릭 수집. (ex: top, htop, vmstat 등)
- 외부 모니터링 시스템: Prometheus & Grafana 같은 전문 모니터링 툴 연동
- 클라우드 제공 모니터링: AWS CloudWatch, GCP Stackdriver 등
7️⃣ Application Parameter & Log Client's Configuration on the Application Startup
- 목적
- 애플리케이션 시작 시 주요 설정 값 검증
- 환경 설정(Configuration) 문제 조기 감지
- 애플리케이션 실행 조건(ex: 메모리 할당, 연결 정보 등) 기록
- 예시
- 환경 변수 정보: .env, application.properties 값 확인
- DB Connection 설정 정보: DB URL, Pool 크기 등
- 캐시 설정 정보
- 서드파티 API 정보 로드: API 사용 가능 여부 검증
- 로깅 설정 값: Log Level, Log Rotation 정책
- 주의점
- 민감한 환경 변수는 노출되지 않도록 방지해야 한다.
- 런타임 환경에서 설정 변경 가능 여부를 확인해두는 것이 좋다. (안 그러면, 설정 바뀌었는데 인지를 못해서 추적이 어려울 수 있다.)
- 설정 오류 감지 시, WARN 혹은 ERROR 레벨 로그로 남겨야 한다.
- 방법
- 애플리케이션 시작할 때, 주요 환경 변수 및 설정 값 로깅
- Spring Cloud 같은 도구를 사용 중이면, Config 변경 사항을 감지하여 로깅
- 필수 설정 값 누락 시, 애플리케이션 실행 중단하고 후속 조치 프로세스 진행
📌 Security
MITRE의 ATT&CK 분류법은 적의 전술과 기술을 14개의 섹션으로 나누어, 어떻게 공격이 이루어지고, 어떤 로깅 소스를 사용하여 해당 이벤트를 추적할 수 있는 지 정리해놓았다.
보안을 위한 로그를 선별하기 위해서는 어떤 공격이 존재하는 지 미리 알 필요가 있다. (마치 전쟁의 첩보전같은 느낌같다.)
서버에 어떠한 유형의 공격이 들어올 지 예상 가능하다면, 해당 공격이 수행되고 있음을 알기 위한 정보를 수집해야 하기 때문이다.
1️⃣ 정찰(Reconnaissance)
- 이 기술은 공격 성공 가능성을 높이기 위해서, 환경 및 네트워크에 대한 정보를 수집.
- 방화벽 및 경계 보안 장치의 능동적 스캔은 탐지될 수 있으나, 오픈 소스 정보 수집은 탐지가 어렵다.
2️⃣ 자원 개발 (Resource Development)
- 공격자가 공격을 정당화하거나, 성공 확률을 높이기 위해 인프라나 디지털 자산을 개발 및 구매하는 것
- 예를 들어, 합법적인 웹사이트를 복제하여 피싱 공격을 시도하거나, 디지털 인증서 및 도메인을 구매할 수도 있음.
- 타사의 인프라 및 컴퓨팅 자원을 해킹하여 공격에 활용하는 경우도 존재
- 계정 탈취 시, 유출된 계정 정보를 사용하여 로그인 시도가 발생할 수 있다.
3️⃣ 초기 접근 권한 획득 (Initial Access)
- 공격자는 수집한 정보를 바탕으로 본격적인 침입을 시도한다.
- 피싱, 하드웨어 공격(키로거, 네트워크 도청 장치) 등 다양한 접근 방법 존재
- 이메일 의존도가 높은 기업은 피싱을 우선 고려해야 하며, 내부 위협 요소가 크다면 물리적 보안도 강화해야 한다.
- 원격 서비스(VPN, RDP) 노출 시, 사람의 개입 없이도 공격자가 접근 가능
- 공격자가 시스템에 침입할 수 있는 진입점을 미리 파악하고, 로그 기록을 검토하여 보안 강화를 고려해야 한다.
4️⃣ 공격자가 제어하는 코드 실행 (Execution)
- 악성 코드 실행 방식: 이메일 첨부파일, 외부 서비스에 업로드된 파일 등
- 사용자 개입 없이 실행 가능하며, 예를 들어 입력 검증이 부족한 시스템에서 악성 파일이 실행될 가능성이 존재
- 최종 사용자 기기의 보안을 강화하여 매크로나 서명되지 않은 PowerShell 실행을 차단하는 것이 중요
- 악성 스크립트 실행을 차단하고, 실행 시도를 기록하여 공격 시도를 탐지할 수 있어야 함.
5️⃣ 지속성 유지 (Persistence)
- 공격자가 실행한 코드가 종료되거나 시스템이 재부팅되더라도 접근 권한을 유지하려 하는 경우
- 예를 들어, 부팅 프로세스에 악성 코드를 추가, 악성 계정을 통한 권한 위임, 브라우저 확장 프로그램 변조 등이 존재
- 로그 감시를 통해 계정 생성, 이상한 위치에서의 로그인, 부팅 시 실행되는 애플리케이션을 모니터링해야 함.
6️⃣ 권한 상승 (Privilege Escalation)
- 공격자가 기본 계정에서 관리자 권한을 획득하려 시도하는 경우
- 프로세스 취약점을 악용, 코드 인젝션 등을 통해 권한 상승이 이루어질 수 있음.
- 시스템 내 취약한 프로세스 및 계정 구조를 분석하여 보안성을 강화해야 한다.
- 권한 상승과 관련된 정상적인 관리 작업과 악성 행위를 구분하기 위해 로그 패턴을 분석해야 한다.
7️⃣ 방어 회피 (Defense Evasion)
- 공격자가 실행, 지속성 유지, 권한 상승 후 탐지를 회피하는 것
- 악성 파일의 이름을 정상적인 프로세스와 동일하게 설정하거나, 시스템 라이브러리를 변조하는 방법 등이 존재
- Windows에서는 레지스트리 수정, 합법적인 프로세스 하이재킹 등을 사용하여 악성 코드를 숨기는 경우가 존재
- 프로세스 해시값을 수집하여 위장된 실행 파일을 탐지하는 것이 중요
8️⃣ 자격 증명 액세스 (Credential Access)
- 공격자는 계정 정보를 탈취하여 추가 공격을 시도할 수도 있다.
- 무차별 대입 공격, 중간자 공격, 네트워크 패킷 감청 등을 활용 가능
- 탈취된 인증 정보는 credential stuffing 공격에 사용될 수 있다. (보통 한 사이트에서 사용한 ID/PW를 똑같이 사용하는 경우가 많으므로, 한 곳에서 ID/PW를 탈취하고 다른 서비스에 요청해보는 것)
- 정상적인 로그인 패턴을 분석하여, 의심스러운 로그인 시도를 탐지하는 것이 중요
9️⃣ 발견 (Discovery)
- 공격자가 시스템 및 네트워크 정보를 파악하여 공격 성공 확률을 높이는 것
- DNS 조회, USB 장치 연결 탐색 등 합법적인 기능을 악용할 수 있다.
- 특정 부서에서 비정상적인 시스템 명령 실행 여부를 모니터링하여 의심스러운 활동을 탐지해야 한다.
1️⃣0️⃣ 횡적 이동 (Lateral Movement)
- 공격자가 감염된 시스템에서 다른 시스템으로 이동하여 공격을 확장하는 것
- 감염된 계정을 사용하여 여러 장치에 로그인하거나, 원격 데스크톱 기능을 악용할 수 있다.
- 내부 네트워크에서 갑작스러운 다수의 로그인 시도를 감지하여 의심스러운 움직임을 파악해야 한다.
1️⃣1️⃣ 데이터 수집 (Collection)
- 공격자가 특정 정보를 탈취하기 위해 데이터를 선택적으로 수집하는 것
- 화면 캡처, 키 입력 기록, 클립보드 복사 등의 방법이 사용될 수 있다.
- 정기적인 백업 패턴과 유사한 방식으로 데이터를 유출할 수도 있으므로, 이상 징후를 모니터링 해야 한다.
1️⃣2️⃣ 명령 및 제어 (Command and Control)
- 공격자가 감염된 시스템을 원격으로 제어하여 추가 공격을 수행하는 것
- DNS 요청을 활용한 명령 전달이 일반적이며, 웹 트래픽이나 암호화된 채널을 사용할 수도 있다.
- 새롭게 등록된 도메인에 대한 DNS 요청을 모니터링하여, 악성 코드의 통신을 탐지할 수 있다.
1️⃣3️⃣ 침투 (Exfiltration)
- 공격자가 탈취한 데이터를 자신이 통제하는 인프라로 전송하는 것
- 네트워크 트래픽 분석을 통해 비정상적인 데이터 전송을 탐지할 수 있다.
- 이동식 저장장치(USB) 사용을 제한하고, 사용 여부를 로그로 기록해야 한다.
1️⃣4️⃣ 영향 (Impact)
- 공격의 초치종 목표에 따라 시스템 및 데이터 조작, 중단, 파괴가 발생할 수 있다.
- 산업 스파이 활동과 랜섬웨어 공격은 목적이 다르므로 각 시나리오에 맞는 보안 대책이 필요하다.
- 로그 분석을 통해 데이터 삭제 시도, 펌웨어 변조 등의 공격 징후를 감지해야 한다.
- 공격의 영향을 완전히 차단할 수는 없지만, 침해 원인을 분석하고 재발 방지를 위한 대응책을 마련할 수 있다.
4. Where?
📌 Where should logs be recorded?
- 로깅은 모든 계층의 교차적 관심사(Cross-cutting Concern)다.
- 로깅은 특정 계층(application, infra, db 등)에서만 고려해야 하는 개별적인 요소가 아니라, 모든 계층에서 공통적으로 신경 써야 하는 요소다.
- 특정 계층에서만 신경 쓴다고 해결되지 않으며, 시스템 전체적으로 일관된 방식으로 처리되어야 한다.
- Microsoft의 Chapter 17: Crosscutting Concerns - Logging and Instrumentation을 참고해보면 좋다.
- 로그 기록과 로그 수집은 다르다.
- 중앙 집중화된 로그 시스템을 사용하더라도, 로그는 각 컴포넌트가 자체적으로 판단하여 기록해야 한다.
- 로그는 문제가 발생한 지점에서 남기고, 되도록 중복된 로그가 발생하지 않도록 하는 것이 좋다.
- 로그의 목적에 따라 기록 위치가 달라진다.
- 애플리케이션 로직 (비즈니스 흐름 추적)
- 시스템 레벨 (운영 및 장애 대응)
- 데이터베이스 레벨 (데이터 일관성 및 트랜잭션 추적)
혹시나 중앙 집중화된 로그 설계가 궁금하다면, 해당 포스트에서 사례를 확인해볼 수 있다.
📌 System Architecture
💡 '로그를 어디에 남겨야 할까?'보다는 현재 작업 중인 위치의 역할을 이해하는 게 필요하지 않을까?
내가 면접에서 로그를 어디서 남길 거냐는 질문에 곤혹을 치뤘던 건, 사고의 접근 방법이 잘못되었던 건 아닐까 생각한다.
처음에는 만약 사용자 로그인 요청이 발생하면, 이 요청에 대한 각 정보들을 어떻게 적절하게 분배해야 하는지 고민했었다.
그러나 해당 파트를 쓰면서 '그냥 현재 작업 중인 위치에서 결정하면 되는 게 아닌가?'라는 생각이 얼핏 들었다.
쉽게 정리해보자면,
- 이론 상 사용자 로그인 요청이 발생하면, Proxy 단에서 접근에 대한 로그를 남기고, WAS 단에서 비즈니스 로직에 대한 로그를 남기면 될 것이다.
- 그런데 만약 내 서비스에 Proxy 서버가 없다면? WAS에서 Proxy 역할까지 수행 중이라면? 그럼 그냥 WAS에서 둘 다 남겨야 한다.
이를 좀 더 구체적으로 정리해보자.
- 단일 서버 (Monolithic Architecture)
- 하나의 애플리케이션 서버에서 모든 비즈니스 로직을 처리하고, 단일 DB를 사용
- 애플리케이션 내부에서 비즈니스 로직의 흐름을 로깅하고, DB에서 감사 로그를 기록
- 예시
- 애플리케이션에서 API 요청/응답, Entity 상태 변경 로그로 기록
- DB에서 중요 데이터 변경 사항(사용자 역할 변경, 결제 상태 변경) 로깅
- 마이크로서비스 (Microservices Architecture)
- 여러 개의 독립적인 서비스가 API를 통해 통신
- 각 서비스가 자체적으로 로깅하되, 중앙 집중 로그 시스템으로 통합되어 있다고 가정
- 예시
- 주문 서비스에서 주문 생성 요청 로그를 남김
- 결제 서비스에서 결제 성공/실패 이벤트를 로그로 남김
- API GW에서 모든 요청/응답을 로깅하여 트레이싱 가능하도록 설정
- DB에서도 중요한 상태 변화를 별도로 로깅
- 서버리스 (Serverless Architecture)
- 애플리케이션 로직이 단기적으로 실행되는 함수 단위 동작
- 로컬 파일 시스템에 로그를 남길 수 없으므로, 외부 서비스에 기록해야 함
- 예시
- AWS Lambda 함수에서 실행 로그를 CloudWatch로 전송
- DB에서 변경 스트림을 감지하여 S3에 감사로그 기록
결국 서버 아키텍처가 어떻고, 또 그 안에서 어떻게 구성했냐에 따라 천차만별로 나뉜다.
하지만 결국 중요한 것은 각 계층에 위치한 서버들의 역할을 구분하는 것이 중요하다고 본다.
"사용자 로그인"이 발생했을 때 GW - Proxy - Auth Service - DB가 플로우에 참여한다면, 각 계층의 역할과 관심사를 잘 식별해서 어떤 로그를 남길지 판단하면 자연스레 해결될 일일 것이다.
📌 Application vs DB
영속화 상태의 변경, 즉 쿼리의 로깅은 어디서 해야 할까?
해당 로그는 크게 3가지 영역에서 남길 수 있다.
- Service Layer: 영속화 상태의 변경 로그 (비즈니스 로직 관점)
- Repository Layer: DB로 전달한 query 자체를 로깅
- DB: 실제로 수행된 query 로그
🤔 정말 위 3가지를 모두 기록해야 하는 것일까?
언뜻 보면, 중복 로그가 쌓일 우려가 있으며, 정말 모든 쿼리에 대한 로그를 축적해야 할 필요가 있는 지에 대한 의구심이 생기기 마련이다.
예를 들어, 사용자가 서비스 내에서 결제를 수행했다고 가정해 보자.
- Service Layer: 주문 생성 요청을 받아 처리하고 로깅
- Repository Layer: 주문 정보를 DB에 저장하는 SQL을 로깅
- DB: 실제 실행된 SQL을 별도로 로깅
그러나 Application에서 쿼리를 기록하지 않아도 DB에서 쿼리를 기록한다면, 애플리케이션에서 남긴 로그가 정말 의미있는 것일까?
💡 Service Layer의 로깅은 제외하고 생각한다.
Repository나 DB의 쿼리 로그와는 달리, Service Layer에서의 영속성 변경에 대한 로깅은 비즈니스 로직과 관련된 로깅이 될 확률이 높다고 생각한다.
다시 위의 예시를 들어보자면, Repository나 DB는 쿼리 그 자체를 저장할 테지만,
Service Layer의 로그는 timestamp, thread-id, level, service-name, message 정보같은 사용자 행동과 애플리케이션 관점의 로깅이 메인이 될 것이다.
이는 DB의 일관성을 지키기 위한 로깅보다는 이상적인 환경의 비즈니스 로직의 흐름을 파악하는 것이 핵심이라고 판단했다.
1️⃣ Application과 DB에서 로그의 사용 목적 차이
DB에서의 쿼리 로그는 일관성 보장을 위함이 최우선일 것이다.
- 데이터 복원: 데이터베이스 시스템에 이상이 생기거나, 일부 노드가 갑자기 죽어버린다던가 그런 상황이 닥쳤을 때, 로그를 통해 복원 가능해진다.
- 감사 로그: 법적으로 반드시 유지해야 하고, 수정과 접근을 엄격히 제한해야 하는 감사 로그의 경우 DB 로그는 필수적이다.
- 트러블슈팅 & 모니터링: Slow Query Log같은 것을을 남기면, 성능 튜닝과 이상 탐지에 용이하다.
그렇다면, Application에서의 쿼리 로깅은 유의미하다고 볼 수 있을까? (그냥 DB 열어보면 되잖아.)
지금 당장 떠올릴 수 있는 차이는
- Application의 Repository Layer에서 Query 로그는 일반적으로 파일에 쓰일 것이고, 이는 모니터링 도구들과 연계하여 분석이 용이하다.
- DB가 빈번하게 호출될 수록, 쓰기 연산이 증가하여 성능 병목 지점으로 작용할 수 있으며, 그만큼 공간을 많이 잡아먹는다. (Why you shouldn't log into db)
- 반면, 파일 쓰기는 구현도 쉽고, 빠르고, 인간 친화적인 뷰를 제공할 방법이 다양하지만, 로그 드라이브의 디스크 공간이 부족하거나, 파일이 잠길 경우 로깅 때문에 서비스가 중단될 우려가 있다.
- Application에서는 쿼리 뿐만 아니라, 추가적인 메타데이터를 함께 기록하여 비즈니스 맥락을 담을 수 있다.
- 개발자는 Application 로그만 확인해도 N+1이나, 실제로 전송되고 있는 쿼리, 쿼리가 전송되는 시점들을 쉽게 파악 가능해진다.
하지만, 이 부분에 있어 내가 완전히 접근하고 있을 수 있다는 생각을 떨쳐버릴 수가 없다.
(예를 들어, 자사에서 DB 접근 권한이 굉장히 엄격해서 쉽게 접근이 어렵다면, Application 로그가 유의미해질 수도 있지 않을까?)
Application의 Query는 정말로 필요한가? 그렇다면 왜, 언제 필요한 것일까?
2️⃣ Application에서 Query 실행할 때 로깅을 해야 할까?
🙋♂️ 이상적인 환경이라면, 양쪽 모두 로깅을 하는 것이 좋다(고 생각합니다...)
집단 지성의 힘을 빌려보자. (그런데 내가 말한 DB 로깅은 MySQL Genral/Slow Log 같은 것들을 말한 거였는데, 대부분 애플리케이션에서 DB 커넥션 열고 로그 저장하는 걸 말하고 있는 거 같다.)
👇 너무 난잡해서 숨겨버린 정리본들
- Stackoverflow - Logging into text file or database?
- gbjbaanb: "일반적으로 텍스트 파일 로깅이 DB 로깅보다 빠르다. DB 로깅의 이점은 로그 항목을 그룹화하는 데 사용할 수 있는 컨텍스트인 경우, 질의를 통한 검색이 매우 쉽다. 이상적인 방법은 로컬 파일로 먼저 기록하고, 나중에 필요한 경우 검사를 위해 DB로 마이그레이션 하는 것이다. 하지만 감사(auditing)는 완전히 다르게 취급해야 한다. 오래 보관해야 하며, 일반적인 로깅보다 빈도가 낮고, 접근이 엄격하게 제한되어야 하므로 DB에 쓰는 것이 바람직하다."
- Chrisophe: "감사 로그는 장기간에 걸쳐 추적 가능성을 보장하고, 법적 요구 사항을 준수하는 정당성을 보장해야 하므로 DB에 쓰는 것이 일반적인 관행이다. 하지만 모니터링 로그, 보안 로그 같은 부류는 성능 및 볼륨 제약을 고려해야만 한다. 따라서 쓰기가 더 빠르고, 오프라인 보관이 쉬우며, 외부 모니터링 도구와 통합하기 쉬운 파일 쓰기를 일반적으로 선호한다."
- Stackoverflow - Is it better to log to file or database?
- StuartLC: "파일 시스템에 먼저 로깅하고, 그 다음 지연이 되는 한이 있더라도 db에 로깅하는 것이 좋다. 파일 시스템에 쓰는 이유는 네트워크, db, 보안 문제와 같은 외부 인프라 제어에 문제가 발생해도, 서버의 하드 디스크에서 데이터를 복구할 수 있다면 복구가 가능하기 때문이다. 그리고 파일 시스템의 로그는 db에 기록이 확인되는 즉시 삭제하면 되므로, 파일 시스템 크기나 유지 시간이 길지 않아도 된다. 하지만 운영 관점에서 서버 팜, 클러스터 환경이라면, 로그 검색의 99% 사용 사례는 여전이 중앙 db를 사용하는 것이다."
- Three things you should never put in your database
- "겉보기에 괜찮아 보이고, 복잡한 쿼리를 사용해야 할 수도 있다고 주장하는 사람들이 설득하는 것 같다. DB에 로그를 저장하는 게 끔찍한 생각은 아니지만, 다른 프로덕션 데이터와 같은 DB에 저장하는 건 끔찍하다. 로깅을 보수적으로 진행할 때, 사용자가 리소스를 놓고 경쟁하는 모든 작업에 대해 로그 INSERT를 생성해보라. 프로덕션 데이터베이스가 불타오를 것이다. 대신 Splunk, Logguly, 혹은 일반적인 rotating flat files 같은 것들을 사용해라."
이 외에도 5개 정도 더 읽어봤는데, 상반된 의견들이 너무 많아서 그냥 접었다. 🫠
그래서 그냥 이쯤에서 내 개인적인 견해를 정리해보려 한다.
- Application vs. DB 로깅은 "무엇을 기록해야 하는가"의 문제다.
- DB는 실제 실행된 쿼리 중심, 트랜잭션 복원과 데이터 무결성 보장, 그리고 쿼리 튜닝을 위한 정보가 핵심
- Application은 비즈니스 맥락을 포함한 로깅, API 호출과 DB 요청 사이의 흐름 분석이 핵심
- 그렇다면 두 로그는 언제 비교해야 하는가?
- 단순히 "이벤트가 발생했다"는 정보가 필요하다면, DB 로깅만으로 충분할 수 있다.
- 하지만, 다음과 같은 경우엔 Application 로그를 고려해야 할 필요가 있다.
- 비즈니스 흐름을 분석해야 한다면? (API 요청과 실제 실행된 SQL을 대조해야 한다면?)
- 예기치 않은 동작을 추적해야 한다면? (N+1문제, 트랜잭션 실패, 장애 대응 등)
- 모니터링 및 성능 최적화가 필요하다면? (Slow Query, 병목 탐지 등)
- DB 접근이 제한적이라면? (보안/규제 이슈)
- 서비스 상황에 따라 적절히 선택해야 한다...는 너무 당연한 말이지만 🫠
- 로그를 필요로 하는 사람이 누구고, 어디서 가장 쉽게 데이터를 얻을 수 있는가?
- 단순한 Query 실행 내역이면 DB 로그가 훨씬 낫다.
- 비즈니스 흐름을 이해하려면 Application 로그가 훨씬 낫다.
👇 처음 썼던 정리가 너무 길어져서 숨겼습니다. 같은 내용이긴 하지만, 지우진 않고 숨김처리 해뒀습니다.
- 일단 가능하다면, 양쪽 모두 로깅을 하면 좋다. (DB에는 반드시 저장이 되어야 한다.)
- 비즈니스 관점
- Application 로깅의 장점은 모니터링 툴과 통합이 쉽고, 쓰기 속도가 빠르며, 비즈니스 맥락을 담을 수 있다는 것이다.
- 단순 쿼리 로깅은 무의미하고, API 호출과 실제 실행된 SQL을 대조하여 비즈니스 흐름을 분석해야 하는 경우에 로깅이 필수적이다.
- 성능 문제: N+1 문제 등과 같은 병목 지점 원인 분석 목적. (ex: `orderRepository.findByUserId(userId)` 호출했더니, 쿼리 100번 실행되면 Lazy Loading 문제임을 빠르게 식별할 수 있다.)
- 데이터 무결성 및 트랜잭션 문제: Application에서 실행한 Query 로그와 DB에서 실제로 실행된 Query 로그 비교 목적
- 장애 대응 및 비정상적 요청 추적: 정상적인 플로우에서 호출되어선 안 되는 쿼리 호출, 혹은 예상보다 많은 쿼리가 호출되는 경우를 판단
- 느린 쿼리 추적 및 튜닝: 특정 조건에서만 응답 시간이 늦어지는 경우 등을 파악
- (확실하진 않지만) 대기업이나 보안이 중요한 환경에서는 DB 접근이 제한되는 경우가 많을 것이고, 가능하다 하더라도 DB 로그와 컨텍스트를 대조하는 것은 상당히 어려움이 많을 것이다. (이런 것까지 한 곳에서 통합해주는 모니터링 도구가 있다면 또 모르겠다.)
- 성능 관점
- DB에서 모든 Query를 남기면, 높은 트래픽 서비스의 성능 병목 지점으로 작용할 수 있다.
- 대신 Application에서 필요한 Query만 선별적으로 기록하는 것이 효율적일 수 있다. (단, 이렇게 하면 서버의 디스크를 고려해야 한다.)
📌 User Behavior
이 내용을 쓸까말까 고민하다가, 일단 간략하게 적어두기만 한다.
사용자 행동을 분석하기 위한 로그는 일반적으로 클라이언트 측 애플리케이션에 적용될 것이다.
서버 측은 로컬 디스크에 작성해두고 로그 수집기가 수거하도록 구성할 수 있겠지만, 클라이언트의 경우는 하나의 로깅 시점에 여러 개의 로그 툴을 호출하는 구조가 될 수밖에 없다.
이를 위해서, 간단한 plugin framework 형태의 로거를 구축하라는 의미다.
[SwiftUI] MVVM 패턴 구조의 앱에서 Analystics 계층 분리하기 (+ `24.07.31 아키텍처 개선 과정 추가)
📕 목차1. Introduction2. Architecture Design & Implementation3. Firebase Service Implementation1. Introduction 📌 문제 상황 ✨ Analytics Layer 분리 by psychology50 · Pull Request #135 · CollaBu/pennyway-client-ios작업 이유 Google Analy
jaeseo0519.tistory.com
예전 포스팅에서는 이벤트 기반 분석 시스템만을 고려했지만, 여기서 한 단계 더 계층을 늘리면 일반 로깅까지 수행되도록 만들 수 있을 것이다.
5. What?
📌 Good Logging Practice
거의 대부분 해당 ppt 내용을 정리한 내용입니다. (아닌 것도 있습니다.)
- 좋은 로그란?
- 필요한 정보가 있다.
- 의미가 명확하다.
- 편리하게 데이터를 얻을 수 있다.
- 필요한 정보가 있는 로그
- 목표가 있는 로그
- 목표를 한 문장으로 정의 (ex: 재방문율 집계가 필요하다)
- 하나의 지표에 대해서 다양한 각도의 고민 (ex: 추후 재방문율 집계시 '국가별', '직업별', '나이별' 필터링이 필요할 것이다.)
- 목표에 따라 남겨야 할 이벤트와 그 항목들을 정의 (ex: 목표 우선순위에 따라, 점진적으로 항목을 추가하는 방향 또한 고려 가능)
- 일관성 있는 로그
- 사용자가 피드 기능을 사용할 수 있는 경우, 사용자는 피드의 구성 요소를 가진다. 다음과 같이 로그를 남기면, 일관성이 깨진 경우에 해당한다.
- 피드 작성에 포함되는 로그 요소: timestamp, user_id, user_name, feed_id
- 피드 수정에 포함되는 로그 요소: timestamp, user_id, feed_id
- 두 이벤트 모두 사용자에 대한 로그지만, 사용자에 대한 항목 구성이 다르기 때문
- 사용자가 피드 기능을 사용할 수 있는 경우, 사용자는 피드의 구성 요소를 가진다. 다음과 같이 로그를 남기면, 일관성이 깨진 경우에 해당한다.
- 믿을 수 있는 로그
- 로그가 의도한 시점에서 발생했을 것이다.
- 의도한 대로 데이터가 남았을 것이다. (의도하지 않은 데이터가 포함되거나, 어뷰징에 의한 변조가 발생하면 로그에 대한 신뢰가 깨지고, 로그 시스템 복원이 힘들다.)
- 목표가 있는 로그
- 의미가 명확한 로그
- 이름에 대한 합의와 규약
- 의미를 충분히 표현 가능해야 한다.
- 나중에 수정하는 것은 큰 비용이 발생할 수 있으므로, 길더라도 구체적인 이름 사용
- 이름을 정의하기 위한 최소 규약(Naming Convention) 정의
- 이벤트를 구별하는 기준
- 상태의 시작과 종료
- 의미 단위로 명확하게 구분 가능
- 이벤트 종류가 많아질 수 있음.
- 로그 항목 구성의 차이가 큰 경우 적합
- 상태 업데이트
- 로그 항목에서 상태 변화 종류를 파악할 수 있음
- 동시성 문제를 고려해야 함
- 같은 구성 요소에 대해 상태만 변경되고, 항목 구성이 거의 비슷하며, 이력 변화가 중요한 경우 적합
- 상태의 시작과 종료
- 표현력
- 약간의 데이터 용량 절감을 위해 축약된 표현을 사용하지 말 것. (압축을 고려하는 것이 낫다.)
- 경우에 따라 데이터 타입에 대한 고려가 필요.
- 빈 값의 의미를 명확하게
- 단순히 null을 채우면, 정보가 누락된 것인지, 실제로 빈 값인지 알 수 없음.
- 이름에 대한 합의와 규약
- 편리하게 데이터를 얻을 수 있는 로그
- 로그 형식
- 필요한 요소는 사람이 읽을 수 있는 형식 & 컴퓨터가 읽기 쉽도록 구조화된(structed) 형식
- 일반적으로 JSON, key/value 형태로 많이 사용
- 메타데이터
- 서비스를 구성하는 정보에 대해, 식별자와 상세 정보가 매핑된 데이터
- 잘못 쓰면 관리 이슈 및 생산성 조회가 발생하므로, 되도록 join이 필요없도록 만드는 것이 좋다.
- 다음과 같은 경우 메타데이터 사용을 고려하는 것이 적절하다.
- 정적 데이터가 과도하게 많고, 정의했던 목표에 필요하지 않은 경우
- 변경 주체가 서비스 제공자에게 있는 경우에 한하여
- 메타데이터가 불가피하게 많아질 경우. (단, 메타데이터를 효율적으로 관리하고 사용 가능한 도구가 필요해짐.)
- 문맥 식별자(Context Identifier)
- 하나의 행동으로 일어난 여러 개의 로그는 같은 문맥 식별자를 갖도록 한다.
- 인과관계나 선후관계를 파악하는 데 도움을 준다.
- ex: 사용자 경험치 획득 로그의 context_id와 퀘스트 완료 로그의 context_id가 일치 → "플레이어가 퀘스트 완료를 통해 경험치를 획득했음을 파악 가능"
- 고유 식별자(Unique Identifier)
- 중복 로그를 쉽게 식별할 수 있다.
- 특정한 로그 하나를 기준으로 잡기 편리하다.
- 로그 형식
- 추가 고려 사항
- 서비스에 줄 수 있는 영향 고려
- 로그가 서버에 부담이 발생할 정도라면, 기술적인 한계를 해결하거나, 적절한 타협점을 찾아야 함.
- 로그에 필요한 값을 채우는 것이 서버에 부담 → 캐싱 or 메타데이터
- 모든 사용자에 대해 n초마다 발생하는 로그 → 샘플링 or 변경 시에만 로깅
- 로그 로테이션
- 오래된 로그를 보관하고, 새 로그를 생성하여 디스크 공관 과부화를 방지해야 한다.
- 보존 정책은 로그 중요도에 따라 보관하는 기간을 지정하여, 규정 준수와 스토리지 최적 사용을 보장해야 한다.
- 로그가 서버에 부담이 발생할 정도라면, 기술적인 한계를 해결하거나, 적절한 타협점을 찾아야 함.
- 타임스탬프
- 모든 로그 메시지는 타임스탬프를 추적해야 한다. (물론 프로세스 아이디, 스레드 아이디, 트랜잭션 아이디, 로그 레벨, 메시지도 사실상 필수가 아닐까 싶다.)
- 일부 작업은 ms 단위까지 추적해야 하지만, 어떤 작업은 min, hr, 또는 day 단위로 추적해도 될 것이다.
- 모범 사례는, 전체 조직에 표준을 적용하여 로그를 메트릭이나 이벤트와 같은 원격 측정 데이터 유형에 매칭시키는 것이다.
- 서비스에 줄 수 있는 영향 고려
- 로그 품질 관리
- 서비스 구성요소별 로그 관리
- 특정 구성요소에 대한 로그를 쉽게 파악 가능하며, 일관성을 유지하는 데 도움
- ex: [사용자 - 피드] 상호작용 이벤트라면, 사용자(user_id, user_name)와 피드(feed_id, feed_title) 구성요소를 모두 포함해야 한다.
- 로그 문서화
- 로그 목표와 의미
- 로그의 정확한 발생 시점
- 각 항목들의 의미와 데이터 타입
- 항목의 추가/변경/삭제 이력
- 특이사항 기록
- 로그가 사용되는 곳
- 로그 유실 모니터링
- 이유(버그 또는 시스템 이슈 등)와 언제, 얼마나, 어떤 데이터가 유실되었는지 기록
- 서비스 구성요소별 로그 관리
🤔 너무 많이 또는 적게 기록하지 마세요.
위 제목과 같은 답변을 너무 많이 읽어서 질려가던 찰나, 재밌는 글을 읽었다.
물론, 해당 글에서도 이런 말이 어리석게 들릴 수 있을 거라 언급하지만, 나름의 방법을 알려주고 있다.
이 문제에 대한 정답을 알기 위해서는 개발 중에 가능한 한 많이 로깅을 하라고 한다. (프로그램 디버깅을 위한 로깅을 이야기하는 것이 아니다.)
그리고 프로덕션 환경에서 생성된 로그를 보며, 로그를 줄이거나 늘려나가는 식으로 개선해보도록 권장하고 있다.
📝 참고 자료
- 좋은 로그란 무엇인가?
- Expert Guide to Logging Best Practice
- Logging Best Practice: The 13 You Should Know
- 14 Monitoring and Logging Best Practice and Standards for Monitoring
- 10 Ways to Take Your Error Logs Up a Level
📌 Structed Log
굉장히 재밌는 내용이다.
Structed Logging에 대한 내용을 고민해본 적이 없는 것은 아니지만, 비교적 최근에 그 필요성을 깨달아서 구체적으로 생각을 해보지 않았던 내용이다. (감사합니다. sematext 😂)
위 로그는 실제로 내 운영 서버에서 가져온 내용이다.
이러한 로그는 적어도 사람이 읽기 쉽기는 하겠지만(사실 나도 읽기 쉽진 않다.), 문제는 로그를 탐색하는데 상당히 오랜 시간이 걸린다.
그렇다고 해서 모니터링 도구를 통합하기에도, 로그가 구조화되어 있지 않아 쿼리가 사실상 불가능하다.
같은 내용을 담고 있는 두 가지 로그 메시지를 살펴보자.
[15/Nov/2021:04:47:53 -0500] 192.168.56.87 [31221] [/home/menu] [2335] ERROR: record id=23434 not found in table
{
"hostName": "192.168.0.1",
"pid": "31221",
"path": "/home/menu,"
"error_number": 2335
"message": "ERROR: record id=23434 not found in table."
}
위의 비정형 로그보다 아래의 구조화된 로그가 훨씬 읽기 쉬우며, 탐색에 용이하고, 모니터링 툴에 통합하기도 유리하다.
📌 Log Level
시작하기 전에 자백하자면, 난 지금껏 WARN 레벨을 완전히 잘못 사용하고 있었다.
심지어 WARN 레벨이 의미하는 바가 뭔지 알고 있었으면서도, 로그를 하도 활용할 일이 없다보니 대충 썼다.
반성하는 마음에서 다시 정리하자.
우선, 로그 레벨이 중요한 이유는 크게 세 가지다.
- 필터링: 로그의 의미를 가장 빠르게 파악할 수 있는 수단
- 노이즈: 잘못된 로그 레벨은 정말 필요한 로그를 분석하지 못하게 할 수 있다. (WARN 레벨이어야 할 로그를 INFO로 잡거나, DEBUG가 너무 많아서 정말 심각한 에러를 놓치거나)
- 경보 피로: 덜 중요한 로그 수준에 대해서는 효과적으로 무시하여, 개발자가 24h 비상 대기조 근무를 서는 일을 줄일 수 있다. (사실 하긴 해야 하는데, 비상 대기조 근무하는 내내 5분 단위로 사이렌이 울리진 않을 것이다.)
✒️ 레벨에 대한 정의
- DEBUG
- TRACE 수준에 비해 세부적인 내용은 아니지만, 개발 및 테스트 단계에서 기능의 정상 동작을 확인하는 레벨
- 운영 환경에서는 남기지 않고, 로컬/개발 환경에서 동작의 검증을 위해 사용한다.
- INFO
- 애플리케이션이 특정 상태에 진입했음을 알리는 레벨 (정상 작동에 대한 정보)
- 애플리케이션 상태, 설정 또는 외부 리소스와의 상호 작용 같은 상태 확인 이벤트를 나타낸다.
- ex1: API의 사용자 권한 검증의 성공 여부 및 성공 시 사용자 정보
- ex2: 애플리케이션 시작/종료 시점, 중요 작업의 성공/실행/실패 시점 등
- 순전히 정보 전달 목적이며, 정기적으로 확인할 필요가 없더라도 누락이 되어서는 안 된다.
- WARN
- 애플리케이션에서 예상치 못한 일이 발생하거나, 문제가 생겼거나, 잠재적으로 프로세스를 방해할 수 있는 상황에 사용하는 레벨
- 애플리케이션이 실패한 것은 아니지만, 지속적인 관찰과 개선이 필요한 항목
- ERROR
- 애플리케이션에서 하나 이상의 기능이 정상 동작하지 않은 경우에 사용하는 레벨
- 심각한 오류 혹은 예외 상황에 남기며, 즉시 조치가 필요함을 의미한다.
🙋♂️ WARN 레벨은 대체 언제 써야 하는 건가요?
여전히 긴가민가 하지만, 그냥 소신 발언 한다는 마음으로 적어보려 한다.
향로님의 포스팅에서는 다음과 같이 이야기하고 있다.
- 사용자가 ID/PW를 오입력하여 로그인에 실패한다면 → WARN
- 로그인이 5번 연속 실패하는 등의 특정 기준치를 넘길 경우 → ERROR
하지만, 사용자가 ID/PW를 오입력해서 이를 거절하는 것도, 5번 연속 실패했을 때 막는 것도 정상 플로우의 일부가 아닌가?
내가 이해한 게 맞다면, 로그 레벨은 다음과 같아야 한다.
왜냐하면, WARN과 ERROR는 개발자가 어떠한 조치를 해주길 요구하는 의미지만, 이건 예상한 시나리오에 대해 적절한 처리를 이미 수행하고 있는 거니까.
- 사용자가 ID/PW를 오입력하여 로그인에 실패한다면 → INFO (예상한 동작이니까)
- 로그인이 5번 연속 실패하는 등의 특정 기준치를 넘길 경우 → INFO (예상했고, 무사히 잘 막고 있음)
- 이미 로그인이 5번 연속 실패한 사용자가 계속 시도하는 경우 → 일반적으로는 INFO, 짧은 시간 내 과도한 시도가 감지된다면 WARN (근데 이런 걸 감지하려면 리소스를 얼마나 더 써야 하는 걸까)
- 비정상적인 로그인 시도 또는 보안 위협, 기준치를 넘겼는데 통과하는 경우 → ERROR
그런데 왜 사용자 ID/PW 오입력에 대해 WARN 레벨을 권장하신 걸까??? 진짜 모르겠다.
(아마도 사용자의 입력이 진짜로 실수한 것일 수도 있지만, SQL injection 같은 악의적인 공격의 시도일 수도 있기 때문이 아닐까.)
그래도 또 굉장히 재밌는 인사이트를 얻은 부분은, 일정 비율 실패하는 것이 일상인 부분 또한 고려해야 한다는 점이다.
- 분당 1만건 트래픽이 발생하는 외부 API 호출 실패율이 0.1%라도 분당 10개의 ERROR 로그가 발생하는 경우
- 개발자가 어떻게 제어할 수 없으며, 한 두 번의 실패가 큰 영향을 주지 않는다면 WARN 레벨로 설정
- 결제 등 비즈니스 적으로 치명적인 API인 경우엔 ERROR
- 단, 클라이언트 측의 실수(잔고 부족, 인증 실패 등)는 WARN
- 외부 결제사 문제면 ERROR로 남기고, 결제사가 정상이 될 때까지는 자체 조치를 취해야 함.
- 재시도 전략이 없는 상태에서 하루 1번, 한 달에 1번 정도로 적은 횟수 시도하는 경우 ERROR
📝 참고 자료
📌 Performance & Readability
카카오 기술 블로그의 "Kotlin 환경에서 로그를 기록할 때 불필요한 문자열 연산을 방지하는 방법"에서는 성능 관점까지 고려하고 있다.
그보다 더 재밌는 건, DataSet Blog의 포스팅에서도 다루고 있는 log 포맷의 가독성 문제도 개선해놓았다.
Spring Boot 기반에서 로그를 작성하면 보통 다음과 같이 작성하게 된다.
log.info("User {} plays {} in game {}", userId, card, gameId);
위 방법은 가독성이 좋은 편이 아니고, 순서를 실패하도라도 감지가 어렵다.
그렇다고, 정규식을 쓰면 더 복잡해진다.
/User (d+) plays (.+?) in game (d+)$/
카카오 페이 블로에서는 이러한 문제를 kotlin-logging을 이용하여, 다음과 같이 개선했다.
logger.info { "User $userId plays $card in game $gameId" }
6. Conclusion
📌 Summary
글이 너무 방대해져서, 간략하게 요약해보았다.
- 로그는 "왜" 필요한가?
- 소프트웨어를 위한 로그, 보안 로그, AI 학습 로그, 감사 로그 등
- 로그는 "언제" 기록해야 하는가?
- 로그의 목적에 따라 다르다.
- 소프트웨어를 위한 로그
- 적어도 Presentation 영역, 외부 시스템 연동, 주요 영속화 데이터 변경, 사용자 행동, 시스템 리소스 변화, 애플리케이션 시작 시 설정 정보 등은 남겨야 한다고 생각한다.
- 영속화 데이터 변경의 경우, 가능하다면 모두 기록해두고 점진적으로 개선, 데이터 무결성이 중요한 경우(결제, 계정 변경 등)엔 애플리케이션과 DB 양쪽에서 로깅을 고려해야 한다. (그래야 DB가 맛탱이 가도, 애플리케이션 로그로 복구가 가능하기도 할 것 같고)
- 보안 로그
- 보안 취약점으로 작용할 수 있는 부분들 전체
- AI 학습 로그
- 정말 모르겠다. AI 엔지니어들이 필요한 로그를 주지 않을까
- 감사 로그
- 선택권이 없다. 그냥 써라.
- 로그는 "어디에" 작성해야 하는가?
- 애플리케이션 로그: 비즈니스 맥락 포함, 로깅 방법(하드 코딩, AOP 등), 로깅 위치(콘솔, 파일, NoSQL, RDBMS 등), 모니터링 도구 연계
- DB 로그: 실제 쿼리 실행, 데이터 일관성 보장
- 서버 아키텍처 따라: 모놀리식/MSA/서버리스 별 전략 수립
- 균형 잡힌 접근: 각 계층의 특성과 목적에 맞게 로깅
- 로그는 "무엇을" 담아야 하는가?
- 구조화된 로그: JSON/key-value 형식으로 기계 처리 용이 & 인간이 읽기 쉬운 구조 채택
- 레벨 구분: DEBUG, INFO, WARN, ERROR 레벨에 대한 협약
- 필수 정보 포함: 타임스탬프, 식별자(ex: thread-id), 문맥 정보
- 일관성: 서비스 구성요소별 로그 포맷 표준화
📌 Thoughts
원래 저번 주 목요일 쯤엔 포스팅할 계획이었는데, 계속 포폴 업데이트하고, 졸업식이니 뭐니 해서 많이 늦어졌다.
그 사이 면접 탈락 소식 받은 건 덤 🥲
그래도 어찌 보면, 내가 저번 포스팅에서 충분히 고려하지 않은 점에 대한 블로그 리뷰를 받은 셈 아닌가?
덕분에 로그에 대해 더 깊게 공부할 토픽을 얻을 수 있어서 너무 좋았다.
하지만 여전히 명쾌한 해답을 내지는 못 했다.
여전히 어렵고, 어떻게 해야 좋은 로그라고 할 수 있는 지에 대한 명확한 틀을 잡기엔 역부족이었던 것 같다.
다음에 다른 곳에서 면접 볼 일 있으면, 마지막 질문 때 이거 여쭤봐야겠다...