AOP 는 코드의 재사용성을 늘려 중복된 코드를 줄이도록 하는 것으로, Aspect Oriented Programming의 약자이다.
주로 로깅 등 유틸성 기능에서 코드 중복을 줄이기 위해서 사용한다. 특히 로깅, 시간측정 할때.
사실 이 개념을 처음 봤다. 다른 코드에서는 없던 방식(?) 이었기 때문에. 스프링부트는 이런 부분이 잘 되어 있는거 같다.
C# 에선 로깅을 위해 이런 코드들이 많았다.
...
public static void WriteLog(string msg)
{
....
Console.Write(msg);
...
}
...
public void TestA()
{
...
WriteLog("aa");
....
}
...
public void TestB()
{
...
WriteLog("bb");
....
}
로깅을 하는 함수는 따로 만들었고, 각 함수에서 이를 호출하는것은 코드 중복이라고 생각하지 않았다. 그러나 스프링부트 에서는 이것 또한 중복된 코드로 보는것이다.
일단 그래들에 다음을 추가 한다.
implementation 'org.springframework.boot:spring-boot-starter-aop'
...
@Aspect
public class ClassA
{
//서비스 경로를 넣어주고, 해당 경로(하위까지) 에 적용됨
@Before ("execution(* com.example.service..*(..))")
public void MethodA()
{
...
System.out.println("시작전 로깅할 내용들");
...
}
@After ("execution(* com.example.service..*(..))")
public void MethodB()
{
...
System.out.println("종료 후 로깅할 내용들");
...
}
}
...
package com.example.service;
...
@Service
public class ClassB
{
//원하는 코드 작성
public void testMethod()
{
...
}
}
- 서비스인 ClassB 의 testMethod 가 호출될 경우에 ClassA 에 명시한 MethodA, MethodB의 각 로그들이 찍히게 된다.
- 만일 동일 패키지의 다른 서비스 클래스가 있다면, 해당 클래스의 메소드가 실행할때도 마찬가지로 로그들이 찍히게 된다.
- 로그 뿐 아니라 실행 시간 측정에서도 사용된다.
AOP를 이용하면 로깅 조차도 중복 코드 없이 빠르게 작업을 할 수 있을거 같지만 이거만으로 충분해 보이지는 않는다.
아마 기존방식과 이 방식 모두 사용해야 하지 않을까? 생각이 든다.
추가, Slf4j + logback 을 통해 로깅하기
springboot 에서 주소 slf4j를 이용해서 로깅을 하게 된다. 사용법은 간단한데, 일단 lombok 을 그래들에 추가하도록 한다.
dependencies {
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
}
로깅을 사용하고자 하는 클래스에 @Slf4j 어노테이션을 붙여주면 된다.
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@Slf4j
@Service
public class 클래스명 {
public void 메서드명(String 인자값) {
log.info("내용: {}", 인자값);
...
try
{
}
catch(Exception ex)
{
//stacktrace 포함
log.error("내용 : {}",인자값, e);
}
}
}
설정 예시는 다음과 같다
logging:
level:
root: INFO
com.example.myapp: DEBUG
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss} %-5level [%thread] %logger{36} - %msg%n%ex"
file:
name: logs/app.log
- %-5level : 좌측에 최소 5간 공간을 확보하여 로그레벨을 출력함 (INFO등) -를 빼면 우측정렬
- [%thread] : 로그를 출력한 스레드 이름 표시. 멀티스레드 환경에서 유용
- %logger{36} : 로그를 찍은 클래스 이름 출력 (36자 까지)
- %ex : stacktrace 정보 출력
그러나, 위의 properties의 설정 보다는 별도의 logback-spring.xml로 설정 하는것이 좋다.
- 저장위치 : resources 폴더 밑에 위치
- 파일 생성 타입 (예 : 하루마다 새로운 파일 생성) 및 파일 보존기간 등을 설정 할 수 있다.
- yaml 에서도 가능하다 하나, 기능이 제한적이라 xml에 별도로 하는것이 좋다고 한다.
<configuration>
<property name="LOG_PATH" value="logs"/>
<appender name="ROLLING" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/app.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 하루마다 새로운 로그 파일 생성 -->
<fileNamePattern>${LOG_PATH}/app.%d{yyyy-MM-dd}.log</fileNamePattern>
<!-- 보존 기간: 30일 -->
<maxHistory>30</maxHistory>
<!-- 압축해서 저장 가능 -->
<cleanHistoryOnStart>true</cleanHistoryOnStart>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} %-5level [%thread] %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="ROLLING"/>
</root>
</configuration>
- <fileNamePattern>: 로그 파일 이름 패턴, %d{yyyy-MM-dd} → 일별 파일 생성
- <maxHistory>: 로그 파일 보존 일수, 30일이면 30일 지난 로그 자동 삭제
- <cleanHistoryOnStart>: 애플리케이션 시작 시 오래된 파일 자동 삭제
- 상황에 따라 logger를 여러개 두어서 따로따로 저장할 수 있다.
- 예 : 일반 로거, 보안 로거 등
- 아래의 예는, 콘솔, 파일로 저장하는 로그 설정 예
- json형식으로 저장
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true">
<!-- Spring Profile 읽기 -->
<springProperty name="springProfile" source="spring.profiles.active" />
<property name="LOG_PATH" value="logs"/>
<!-- ============================= -->
<!-- 🔹 일반 로그 Appender -->
<!-- ============================= -->
<appender name="APP_JSON_CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
<providers>
<timestamp/>
<logLevel/>
<threadName/>
<loggerName/>
<message/>
<mdc/>
<stackTrace/>
<jsonProvider class="net.logstash.logback.composite.GlobalCustomFieldsJsonProvider">
<customFields>{"env":"${springProfile}","type":"app"}</customFields>
</jsonProvider>
</providers>
</encoder>
</appender>
<appender name="APP_JSON_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/app-${springProfile}.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/app-%d{yyyy-MM-dd_HH-mm}.log</fileNamePattern>
<maxHistory>7</maxHistory>
</rollingPolicy>
<encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
<providers>
<timestamp/>
<logLevel/>
<threadName/>
<loggerName/>
<message/>
<mdc/>
<stackTrace/>
<jsonProvider class="net.logstash.logback.composite.GlobalCustomFieldsJsonProvider">
<customFields>{"env":"${springProfile}","type":"app"}</customFields>
</jsonProvider>
</providers>
</encoder>
</appender>
<!-- ============================= -->
<!-- 🔹 보안 로그 Appender -->
<!-- ============================= -->
<appender name="SEC_JSON_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/security-${springProfile}.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/security-%d{yyyy-MM-dd_HH-mm}.log</fileNamePattern>
<maxHistory>7</maxHistory>
</rollingPolicy>
<encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
<providers>
<timestamp/>
<logLevel/>
<threadName/>
<loggerName/>
<message/>
<mdc/>
<stackTrace/>
<jsonProvider class="net.logstash.logback.composite.GlobalCustomFieldsJsonProvider">
<customFields>{"env":"${springProfile}","type":"security"}</customFields>
</jsonProvider>
</providers>
</encoder>
</appender>
<!-- ============================= -->
<!-- 🔹 Logger 정의 -->
<!-- ============================= -->
<!-- 일반 애플리케이션 로그 -->
<logger name="com.example.app" level="INFO" additivity="false">
<appender-ref ref="APP_JSON_CONSOLE"/>
<appender-ref ref="APP_JSON_FILE"/>
</logger>
<!-- 보안 관련 로그 -->
<logger name="com.example.security" level="INFO" additivity="false">
<appender-ref ref="SEC_JSON_FILE"/>
</logger>
<!-- Root Logger -->
<root level="INFO">
<appender-ref ref="APP_JSON_CONSOLE"/>
</root>
</configuration>
각 클래스 코드에서 로거를 생성해야 한다. 이렇게 할 경우, 로깅 내용에 클래스가 표시되므로, 어떤 클래스에서 로깅했는지 알 수 있다.
생성할때, 패키지명 별로 구분되므로 클래스별로 바꿔줘야 한다.
(기본적으로 @Slf4j 어노테이션을 쓰면, 빌드시 LoggerFactory.getLogger를 통해 1개의 로거를 생성해 준다. )
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
public class LoggingExample {
// 일반 애플리케이션 로그
private static final Logger appLogger = LoggerFactory.getLogger("com.example.app");
// 보안 로그
private static final Logger secLogger = LoggerFactory.getLogger("com.example.security");
....
}'Backend > SpringBoot' 카테고리의 다른 글
| 시스템 전역에서 핸들링 되지 않은 오류 감지하기 (1) | 2025.09.01 |
|---|---|
| 생명주기 Event들 활용 하기 (1) | 2025.09.01 |
| 자바스크립트에서 타임리프 사용하기 (+onclick 인자 전달) (0) | 2024.12.18 |
| 전반적인 프로젝트 구조 (0) | 2024.11.26 |
| @Mapper, @Mapping (0) | 2024.11.20 |