FireDrago
[Moment] Spring Boot 로깅 전략: AOP, Logback, Docker Volume, AWS CloudWatch 적용기 본문
[Moment] Spring Boot 로깅 전략: AOP, Logback, Docker Volume, AWS CloudWatch 적용기
화이용 2025. 8. 17. 22:17문제 상황
개발 단계에서 로깅은 단순히 디버깅을 위해 콘솔에 출력하는 수준이면 충분했다.
하지만 운영 중에는 장애 원인 분석, 요청 흐름 추적, 성능 모니터링 등
로그가 단순한 출력 이상의 역할을 해야 했다.
도커 환경에서 애플리케이션을 실행하면 로그가 컨테이너 내부에만 남는다.
컨테이너가 재시작되거나 교체되면 로그는 그대로 사라져버리기 때문에
원인 분석이 불가능했다.
또한 로그를 특정 서버 안에서만 확인할 수 있다 보니,
서버에 일일이 접속해야 하는 불편함이 있었다.
무엇보다 로그가 단순 문자열로 쌓이는 형태라면 운영 중 발생하는 문제를 빠르게 분석하기가 쉽지 않다.
특정 요청이 어떤 흐름을 거쳤는지, 쿼리가 얼마만큼의 시간이 걸렸는지 등을 체계적으로 확인할 방법이 없었다.
이런 상황에서는 장애 대응 속도가 늦어질 수밖에 없었다.
Logback을 활용한 구조적 로깅
가장 먼저 필요했던 것은 로그를 체계적으로 기록하는 방식이었다.
이를 해결하기 위해 Logback을 도입했다.
Logback은 Spring Boot 기본 로깅 프레임워크이자 성능이 뛰어난 로깅 라이브러리로,
다양한 Appender와 Rolling 정책을 제공한다.
특히, 로그 파일을 날짜별로 분리하고, 일정 기간만 보관할 수 있어 디스크 관리에도 유리하다.
우리는 로그를 크게 두 가지로 나누었다.
- STDOUT 로그: 애플리케이션 전반의 기본 실행 로그
- Custom 로그: 비즈니스 로직과 관련된 세부 로그 (예: traceId, Hibernate SQL, 바인딩 파라미터 등)
이를 통해 단순 실행 흐름과 상세 분석 로그를 분리할 수 있었고,
장애 발생 시 어떤 요청이 어떤 SQL까지 실행했는지를 한눈에 추적할 수 있었다.
또한 traceId를 추가해 같은 요청이 여러 계층을 거쳐갈 때 흐름을 쉽게 파악할 수 있도록 했다.
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="CUSTOM_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<fileNamePattern>./logs/moment-custom.%d{yyyy-MM-dd}.log</fileNamePattern>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%X{traceId}] %-5level %logger{36} - %msg%n</pattern>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>./logs/moment-custom.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>1</maxHistory>
</rollingPolicy>
</appender>
<appender name="STDOUT_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<fileNamePattern>./logs/moment-stdout.%d{yyyy-MM-dd}.log</fileNamePattern>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>./logs/moment-stdout.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>1</maxHistory>
</rollingPolicy>
</appender>
<logger name="moment" level="DEBUG" additivity="false">
<appender-ref ref="CUSTOM_FILE"/>
</logger>
<logger name="org.hibernate.SQL" level="DEBUG" additivity="false">
<appender-ref ref="CUSTOM_FILE" />
</logger>
<logger name="org.hibernate.orm.jdbc.bind" level="TRACE" additivity="false">
<appender-ref ref="CUSTOM_FILE" />
</logger>
<root level="INFO">
<appender-ref ref="STDOUT_FILE"/>
</root>
</configuration>
- Appender 구분 (CUSTOM_FILE vs STDOUT_FILE)
- CUSTOM_FILE은 비즈니스 로직과 관련된 세부 로그를 기록한다.
- traceId, Hibernate SQL, 파라미터 바인딩 정보를 포함 → 문제 발생 시 요청 흐름과 데이터까지 추적 가능- -
- STDOUT_FILE은 애플리케이션 전반 로그를 기록 → 시스템 모니터링 용도
- CUSTOM_FILE은 비즈니스 로직과 관련된 세부 로그를 기록한다.
- RollingFileAppender + TimeBasedRollingPolicy
- 로그를 날짜별로 분리해 기록 (moment-custom.%d{yyyy-MM-dd}.log)
- maxHistory=1 설정으로 1일치만 유지 → 디스크 용량 관리
- 패턴 설정 (Encoder)
- %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%X{traceId}] %-5level %logger{36} - %msg%n
- 로그 발생 시간, 쓰레드, traceId, 로그 레벨, 로거 이름, 메시지 순으로 구조화
- traceId를 포함해 분산 환경에서도 요청 단위 추적 가능
- %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%X{traceId}] %-5level %logger{36} - %msg%n
- 로그 레벨과 로거별 설정
- moment 로거 → DEBUG
- Hibernate SQL → DEBUG, 바인딩 변수 → TRACE
- Root 로거 → INFO → 전체 시스템 로그 수준 조절 가능
AOP로 관심사 분리: Controller, Service, Repository 로깅
로그를 체계적으로 관리하기 위해서는 단순히 파일에 기록하는 것만으로는 부족하다.
Controller, Service, Repository 코드 곳곳에 직접 로깅을 삽입하면 코드가 복잡해지고,
로그 작성 방식이 개발자마다 달라져 일관성이 깨지기 때문이다.
그래서 AOP(Aspect-Oriented Programming)를 활용해 공통 로깅 로직을 모듈화했다.
1. Controller 로깅
Controller는 외부 요청과 응답을 담당하는 계층이므로, 요청 내용과 파라미터, 토큰 여부, 응답 데이터를 기록하도록 했다.
@Aspect
@Component
@Slf4j
@Profile({"test", "dev"})
public class ControllerLogAspect {
@Pointcut("@within(org.springframework.web.bind.annotation.RestController)")
public void allController() {}
@Before("allController()")
public void logControllerRequest(JoinPoint joinPoint) {
// 응답 요청 기록...
getRequest();
}
private void getRequest(JoinPoint joinPoint) {
HttpServletRequest request = getHttpServletRequest();
String queryParameters = getQueryParameters(request);
String body = getBody(joinPoint);
boolean hasToken = hasToken(request);
log.debug("request: [{}], parameters: [{}], hasToken: [{}]", body, queryParameters, hasToken);
}
@AfterReturning(pointcut = "allController()", returning = "responseBody")
public void logControllerResponse(Object responseBody) {
// 응답 정보 기록
getResponse();
}
private void getResponse(Object responseBody) {
log.info("response: [{}]", responseBody);
}
}
2. Service 로깅
Service 계층은 비즈니스 로직의 실행 시작과 종료 시점을 기록해 서비스 흐름 추적에 활용했다.
@Aspect
@Component
@Slf4j
@Profile({"test", "dev"})
public class ServiceLogAspect {
@Pointcut("@within(org.springframework.stereotype.Service) && !within(*..*QueryService)")
public void serviceWithoutQuery() {}
@Around("serviceWithoutQuery()")
public Object logService(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("start service: [{}]", joinPoint.getSignature().getName());
try {
return joinPoint.proceed();
} finally {
log.info("end service: [{}]", joinPoint.getSignature().getName());
}
}
}
- @Around → 메소드 실행 전/후 로그 기록
- QueryService 제외 → 불필요한 상세 로깅 최소화
- 운영 시, 서비스 흐름과 병목 구간 확인 가능
3. Repository 로깅
Repository 계층에서는 쿼리 실행 시간을 기록해 성능 모니터링과 병목 분석에 활용했다.
@Aspect
@Component
@Slf4j
@Profile({"test", "dev"})
public class RepositoryLogAspect {
@Around("execution(public * org.springframework.data.repository.Repository+.*(..))")
public Object logRepository(ProceedingJoinPoint joinPoint) throws Throwable {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
Object result = joinPoint.proceed();
stopWatch.stop();
long totalTimeMillis = stopWatch.getTotalTimeMillis();
log.info("query: [{}] totalTime: [{}] ms", joinPoint.getSignature().getName(), totalTimeMillis);
return result;
}
}
- 메소드 실행 시간을 StopWatch로 측정
- 쿼리 성능 분석 및 병목 지점 확인 가능
Docker Volume로 로그 보존
운영 환경에서는 컨테이너 내부에 로그를 남기면, 컨테이너가 재시작되거나 교체될 때 로그가 사라지는 문제가 발생한다.
Docker에서는 Volume을 사용해 로그를 호스트 머신에 마운트하고, 지속성을 확보할 수 있다.
아래는 환경 변수는 제거하고 로그 중심으로 구성한 Docker Compose 예시이다.
services:
app:
container_name: moment-app-server
image: momentbackend/moment-prod-images:${IMAGE_TAG}
ports:
- "8080:8080"
volumes:
- /home/ubuntu/moment/logs:/app/logs
- Volume 마운트
- /home/ubuntu/moment/logs (호스트) ↔ /app/logs (컨테이너)
- 컨테이너가 교체되더라도 로그 파일이 호스트에 남아 휘발성 문제 해결
AWS CloudWatch Logs 설정

운영 환경에서 서버마다 로그를 확인하는 것은 비효율적이다.
Ubuntu 기반 EC2 환경에서는 CloudWatch Logs 에이전트를 설치하고
Docker 로그를 수집하여 중앙 집중형 로그 관리가 가능하다.
1. 로그 그룹 생성
- AWS Management Console 접속 → CloudWatch → Logs → 로그 그룹 생성
- 로그 그룹 이름 예시: moment-app-logs
'프로젝트' 카테고리의 다른 글
| [치지직 채팅] 1. 치지직 채팅 웹소켓 프로토콜 분석 (0) | 2025.12.05 |
|---|---|
| [Moment] 프로젝트 쿼리 실행속도 50초 단축하기 (0) | 2025.10.19 |
| [Moment] GitHub Actions CI 9분에서 1분으로 단축하기 (Gradle, Docker 최적화) (0) | 2025.08.03 |
| [bobzip] nginx Request Entity Too Large 오류 (0) | 2024.08.02 |
| [bobzip] QueryDsl 활용하여 냉장고 재료로 레시피 검색하기 (0) | 2024.07.11 |
