[JAVA] 로깅과 AOP를 이용한 공통 로직 처리

대규모 트래픽 환경에서 서비스 장애를 신속하게 파악하기 위해서는 로깅(logging)이 필수입니다. 하지만 각 서비스마다 로깅 코드를 일일이 작성하면 유지보수가 어렵고 중복 코드가 늘어나게 됩니다. 이러한 문제를 해결하기 위해 스프링에서는 AOP(관점 지향 프로그래밍)을 통해 공통 로직을 깔끔하게 분리할 수 있습니다.

 

 

로깅의 목적과 한계

로깅의 가장 큰 목적은 문제 원인 추적시스템 동작 분석입니다. 하지만 다음과 같은 상황에서는 단순한 로깅 코드만으로는 한계가 있습니다.

  • 서비스 메서드마다 logger.info() 코드 중복
  • 로깅 포맷 불일치로 인한 분석의 어려움
  • 운영 환경별 로그 레벨 관리 미흡

이런 문제를 해결하기 위해 로깅 로직을 AOP로 통합하여 한 곳에서 일관성 있게 관리하는 것이 좋습니다.

 

AOP(Aspect Oriented Programming)란?

AOP는 공통 관심사(cross-cutting concern)를 분리하여 핵심 로직과 독립적으로 관리할 수 있도록 해주는 프로그래밍 기법입니다. 예를 들어, 다음과 같은 로직은 대부분의 서비스에서 공통으로 필요합니다.

  • 로깅
  • 권한 체크
  • 트랜잭션 처리
  • 성능 측정

이러한 공통 로직을 AOP로 구현하면, 코드 중복을 줄이고 핵심 비즈니스 로직의 가독성을 높일 수 있습니다.

 

AOP를 이용한 로깅 공통 처리 구현

1. 의존성 추가

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

2. Aspect 클래스 작성

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Aspect
@Component
public class LoggingAspect {

    private static final Logger log = LoggerFactory.getLogger(LoggingAspect.class);

    @Before("execution(* com.example.service..*(..))")
    public void beforeMethod(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().toShortString();
        log.info("[START] {}", methodName);
    }

    @AfterReturning(pointcut = "execution(* com.example.service..*(..))", returning = "result")
    public void afterMethod(JoinPoint joinPoint, Object result) {
        String methodName = joinPoint.getSignature().toShortString();
        log.info("[END] {} -> result: {}", methodName, result);
    }
}

위 예제는 com.example.service 하위 모든 메서드의 실행 전후를 자동으로 로깅합니다. 이를 통해 각 서비스에 개별적으로 로깅 코드를 작성하지 않아도 되고, 로깅 포맷을 중앙에서 통제할 수 있습니다.

 

실무 적용 시 고려사항

  • 성능: AOP는 프록시 기반으로 동작하므로, 포인트컷 설정은 최소화해야 합니다.
  • 민감 데이터 제외: 로그에 비밀번호, 토큰 등 개인정보를 절대 포함하지 않도록 필터링 필요
  • 환경별 로그 레벨 관리: 개발 환경에서는 DEBUG, 운영 환경에서는 INFO 이상으로 제한
  • 추적 ID(Trace ID): MSA 환경에서는 요청 단위로 Trace ID를 남겨야 로그 상관관계 분석이 용이함

 

응용: 실행 시간 측정 로깅 추가

AOP를 이용하면 단순 로깅 외에도 메서드 실행 시간을 측정할 수 있습니다. 다음 예시는 메서드의 성능 모니터링을 위한 간단한 구현입니다.

@Around("execution(* com.example.service..*(..))")
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
    long start = System.currentTimeMillis();
    Object result = joinPoint.proceed();
    long end = System.currentTimeMillis();

    log.info("[PERFORMANCE] {} executed in {} ms", joinPoint.getSignature(), (end - start));
    return result;
}

이 로직을 추가하면, 서비스의 병목 구간을 빠르게 식별할 수 있습니다.

 

주의할 점

  • 로깅이 과도하면 IO 부하가 발생할 수 있음 (비동기 로깅 고려)
  • 로그는 JSON 형태로 구조화하여 ELK, Loki 등 모니터링 시스템과 연동 권장
  • 예외 로그는 @AfterThrowing 어드바이스로 별도 처리하여 스택 트레이스를 남기되, 민감 정보는 마스킹

 


 

AOP를 통한 로깅 공통화는 단순한 기술적 편의성을 넘어 운영 효율성과 장애 대응 속도를 높여줍니다. 특히 대규모 시스템에서는 로그 일관성과 추적성이 중요하기 때문에, AOP 기반의 로깅은 거의 필수적인 설계 패턴입니다. 단, 과도한 로깅은 성능에 영향을 줄 수 있으므로 포인트컷 범위와 로그 레벨 관리를 철저히 해야 합니다.