-
Notifications
You must be signed in to change notification settings - Fork 0
Description
📝 서비스 로그 수집/관리
🎯 스터디 목표
현재 운영 중인 서비스에서의 로그 수집, 관리하는 방식에 대해 각 파트별로 정리하고,
서비스 운영 방식과 로그 데이터의 중요성에 대해 학습한다.
📖 핵심 내용:
1. 서비스에서 로그 수집하는 방법
- 로그 출력에는 logback 을 공통으로 사용한다. xml 을 정의하여 로그 형식, 출력형태 등을 설정할 수 있다.
1-1. 서비스 로그 케이스 분류
- 백엔드 서비스에서의 로그 발행하는 케이스는 크게 4가지로 구분할 수 있다.
1) Request/Response 로그 출력
백엔드로 들어오는 모든 요청과 응답이 기록된다.
AOP 를 사용해 controller 에 들어오는 모든 request와 반환 받은 response를 남기도록한다.
2) 비지니스 로직 사이에 출력하는 log.info/error/debug
비지니스 로직 사이에 디버깅/트랙킹 용도로 자유롭게 발행하는 로그를 말한다.
서비스 전반에서 어디든지 발행할 수 있다.
3) 감사로그
감사에 필요한 로그를 Spring Boot Event 를 사용하여 발행한다.
감사 로그 저장이 필요한 특정 시점에 이벤트를 발행하고, 해당 이벤트를 감지하여 감사로그를 출력한다.
4) 디비 쿼리
p6spy 라이브러리를 사용해 쿼리를 사용되는 쿼리를 출력한다.
1-2. 로그 템플릿 정의
- 트랙킹을 위해 필요한 정보들을 담아 로그 템플릿을 정의한다.
<property name="CONSOLE_LOG_PATTERN"
value="[%d{yyyy-MM-dd HH:mm:ss}:%-3relative] %clr(%-5level) %clr(${PID:-}){magenta} %clr(---){faint} %clr([%15.15thread]){faint} [order:%MDC{orderId} | menu:%MDC{menuId} | user:%MDC{user}] %clr(%-40.40logger{36}){cyan} %clr(:){faint} %msg%n"/>- 아래의 부분이 MDC 값을 꺼내 정보를 삽입하는 필드이다 .
[order:%MDC{orderId} | menu:%MDC{menuId} | user:%MDC{user}]
1-3. 케이스 별 템플릿 적용 방법
1) Request/Response 로그 출력
해당 케이스의 경우 이미 AOP를 정의해 사용하고 있기 때문에 해당하는 AOP 메서드 로직에 MDC 값을 추가해주도록 변경했다. MDC 값을 추가하는 private 메서드를 만들어 @Around("onRequest()") 에 동작하는 logAction AOP에서 이를 호출하도록 구현했다.
private void addToMDC(Map<String, Object> parameters) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && authentication.isAuthenticated()) {
MDC.put("user", authentication.getName());
}
if (parameters.containsKey("orderId")) {
MDC.put("orderId", String.valueOf(parameters.get("orderId")));
}
if (parameters.containsKey("menuId")) {
MDC.put("menuId", String.valueOf(parameters.get("menuId")));
}
if (parameters.containsKey("email") && MDC.get("user") == null) {
MDC.put("user", String.valueOf(parameters.get("email")));
}
}2) 비지니스 로직 사이에 출력하는 log.info/error/debug
해당 케이스는 로그를 출력하는 시점을 하나로 통일해주는 것이 가장 중요했다. 서비스 전반에서 수시로 찍고 있는 로그들에 대해 관점을 통일하고 템플릿을 일괄 적용할 수 있는 구조를 만들었다.
템플릿을 미리 정의 했기 때문에 필요한 정보는 명확한 상태이다. 여기에 유의미한 정보가 담길 수 있는 로깅 대상만을 선정하여 템플릿을 적용할 수 있도록 했다. 커스텀 어노테이션을 정의하고 해당 어노테이션을 joinpoint 로 하는 AOP 를 만들어 적용했다.
커스텀 어노테이션 정의
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Trace {
}클래스 레벨에서 사용할 수 있는 @Trace 커스텀 어노테이션을 정의한다.
AOP 정의
@Around("execution(* com.example.service..*(..)) && @within(Trace)")
public Object addLogMdc(ProceedingJoinPoint joinPoint) throws Throwable {
// MDC 추가 로직
}service 패키지 하위의 @Trace 가 달린 클래스의 메소드 호출에 addLogMdc AOP 가 동작할 수 있도록한다.
3) 감사로그
이벤트 발생에 따라 로그를 발행하는 방식 역시 2번 케이스와 동일하다. 이벤트를 감지해 실제 로그를 출력하는 클래스에 @Trace 를 달아 AOP로 관점을 일치시켜주도록 했다.
4) 디비 쿼리
디비 쿼리에는 템플릿에 들어갈 정보가 남겨있지 않으므로 트래킹 대상에서 제외하였다.
2. 로그 시스템에 적재하는 방법
현재 구성
현재 서비스에는 ECK(Elastic Cloud on Kubernetes)를 적용하여 ElasticSearch + Kibana 가 구동되고 있다.
Elastic Cloud on Kubernetes Quickstart
- ElasticSearch : 로그 검색 및 분석을 위한 엔진
- Kibana : 시각화 도구
로그 수집에는 filebeat 를 사용한다.
동적으로 컨테이너 ID를 참조하여 해당 컨테이너의 로그 파일을 지정하고, 그 .log 파일을 동적으로 발견, 수집을 진행하도록 구성되어 있다.
Filebeat에서 필드 매핑 (TODO)
MDC 등의 값을 추가했기 때문이 이를 맵핑하여 필드별로 출력할 수 있도록 새로운 정의를 추가해야 한다.
Filebeat 설정에서 dissect 또는 grok 프로세서를 사용해 로그를 파싱하고, 필요한 정보를 필드로 매핑해주도록 yml 파일을 아래와 같이 수정할 예정이다.
위에서 Logback.xml 에 정의한 로그 형식은 아래와 같이 표현할 수 있다.
[<타임스탬프>:<상대시간>] <레벨> <PID> --- [<스레드>] [order:<orderId> | menu:<menuId> | user:<user>] <로거> : <메시지>
이 형식을 yaml 에 정의하고 prefix 를 달아 컬럼을 정의하도록 한다.
processors:
- dissect:
tokenizer: "[%{timestamp}:%{relative}] %{log.level} %{pid} --- [%{thread}] [order:%{orderId} | menu:%{menuId} | user:%{userId}] %{logger} : %{message}"
field: "message"
target_prefix: "fields"
- drop_fields:
fields: ["message"] # 원본 메시지 제거fields.timestamp: 로그 타임스탬프
fields.relative: 상대시간
fields.log.level: 로그 레벨 (e.g., INFO, ERROR)
fields.pid: 프로세스 ID
fields.thread: 스레드 이름
fields.orderId: MDC에서 가져온 orderId
fields.menuId: MDC에서 가져온 menuId
fields.userId: MDC에서 가져온 userId
fields.logger: 로거 이름
fields.message: 로그 메시지
변경 시 주의해야할 점
다른 서비스들과 로그 형식을 모두 일치시킨 것은 아니기 때문에 특정 컨테이너에서만 동작할 수 있도록 조건부 설정이나 공통 설정을 조정해야한다.
- 특정 컨테이너에만 적용: 컨테이너 이름이나 라벨을 기준으로 when 조건을 추가한다. 이 방식은 로그 형식이 매우 다르거나 특정 서비스에만 필드 매핑이 필요한 경우 적합할 것이라고 생각된다.
- 공통 설정 사용: 대부분의 로그가 유사한 패턴이라면 공통 프로세서를 정의하고, ignore_failure를 추가해 다른 패턴의 로그도 문제없이 처리되도록 설정할 수 있다.
3. 메트릭 수집
현재 메트릭 정보의 수집은 metricbeat 만을 사용하고 있다.
Spring Boot 에서도 Actuator 를 통해 메트릭 정보를 전송할 수 있는데, 두 메트릭의 차이점을 비교하고 k8s 와 연동하여 수집될 수 있도록 구성하는 방법에 대해 정리했다.