개요
API 요청에 따라 springboot에서 로그를 수집하고,
발생하는 로그를 ES의 인덱스에 추가하려고 한다.
전체적인 로직은 다음 포스팅들을 참고하였다.
https://prohannah.tistory.com/182
https://awse2050.tistory.com/72
API 로그 수집 준비
AOP의 경우는 해당 포스팅에서 설명하였으니, 생략한다.
https://csg1353.tistory.com/91
AOP 설정
package com.sch.sch_elasticsearch.aop;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Aspect
@Component
@RequiredArgsConstructor
public class LoggingAspect {
private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class);
private final ObjectMapper objectMapper;
@Pointcut("execution(* com.sch.sch_elasticsearch.domain..*.*(..)) " +
"&& @annotation(com.sch.sch_elasticsearch.aop.SaveLogging)" )
public void pointCut() {}
@AfterReturning("pointCut()")
public void logAfterMethodReturn(JoinPoint joinPoint) {
// SaveLogging 어노테이션이 붙은 메서드의 로그를 DEBUG 레벨로 기록
// 실제로 아래 내역대로 로깅하면 안 된다. 테스트용으로 시험했다.
logger.debug("포인트컷");
}
}
다음과 같이 모든 메서드가 실행된 후 PointCut을 실행하게 하였다.
이 과정에서 필요한 부분만 로그를 수집하기 위해, 전용 로그를 생성하였다. (SaveLogging)
package com.sch.sch_elasticsearch.aop;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SaveLogging {
//포인트컷 저장용 어노테이션입니다.
}
Logback이란?
Logback은 SLF4J(Simple Logging Facade for Java)를 위한 구현체 중 하나로, 자체적인 로깅 라이브러리다.
Logback은 로그를 관리하는 전체적인 기능을 제공하며, 이를 위한 설정 파일을 XML 형식으로 작성한다.
간단히 정리하자면,
- Logback: 로깅을 위한 자체 라이브러리로, 로그 레벨 설정, 출력 형식, 파일 로깅, 콘솔 로깅 등 다양한 기능을 제공
- XML 설정 파일: Logback의 동작을 설정하는 데 사용되며, 로그 패턴, 로그 레벨, 로그 파일의 저장 위치 등을 정의
- SLF4J: 로깅에 대한 단순화된 인터페이스를 제공.
흔히 롬복으로 사용하는 '@Slf4j' 를 어노테이션으로 선언하면, 클래스에 자동으로 SLF4J(Simple Logging Facade for Java)의 Logger 객체가 생성된다.
Logback 설정 예시
`src/main/resources` 디렉토리에 Logback의 설정 파일 logback-spring.xml을 생성하고 설정한다.
기본적으로 모든 콘솔 로그를 출력한다. 하지만 저장용 로그는 DEBUG를 사용하고, 이는 해당 경로에 로그 파일 형태로 저장된다.
현재 파일 경로는 테스트용으로 하드코딩했으나, 실제로는 변경해야 한다.
<configuration>
<!-- 콘솔 로깅 : 모든 로그를 콘솔에 출력 -->
<appender name="Console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>[%d{yyyy-MM-dd HH:mm:ss}][%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- 특정 조건 (DEBUG 레벨)의 로그만 파일에 저장, AOP에서 특정 저장용 로그만 DEBUG 설정할 것-->
<appender name="ApiLogFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>DEBUG</level> <!-- 로그 레벨을 DEBUG로 변경 -->
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<file>C:\Users\SSAFY\Desktop\openTripAPI.log</file> <!-- 파일 경로 -->
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>[%d{yyyy-MM-dd HH:mm:ss}][%thread] %-5level %logger{36} - %msg%n</pattern> <!-- 패턴 추가 -->
</encoder>
<!-- 하루에 한번 압축 후 보관, 최대 30일, 1GB까지 보관 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>C:\Users\SSAFY\Desktop\openTripAPI.%d{yyyy-MM-dd}.gz</fileNamePattern> <!-- 파일 경로 -->
<maxHistory>30</maxHistory>
<totalSizeCap>1GB</totalSizeCap>
</rollingPolicy>
</appender>
<!-- 기본 로그 설정 -->
<root level="INFO">
<appender-ref ref="Console"/>
</root>
<!-- 특정 조건 (예: DEBUG 레벨)의 로그만 파일에 저장하도록 설정 -->
<logger name="com.sch.sch_elasticsearch.aop" level="DEBUG" additivity="false">
<appender-ref ref="ApiLogFile"/>
</logger>
</configuration>
이제 로거가 콘솔에 찍히고, 내 log 파일에 저장될 것이다.
물론 이 설정을 적용하기 위해, 서버 구동 시 application.yml에 이를 적용하여 알 수 있도록 해야 할 것이다.
Application.yml
logging:
level:
org.springframework.data.elasticsearch.client.WIRE: TRACE
config: classpath:logback-spring.xml
로그 저장 결과
실제 프로젝트에 맞게 Logback과 AOP 적용
logback.xml
<configuration>
<!-- 콘솔 로깅 : 모든 로그를 콘솔에 출력 -->
<appender name="Console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>[%d{yyyy-MM-dd HH:mm:ss}][%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- 특정 조건 (DEBUG 레벨)의 로그만 파일에 저장, AOP에서 특정 저장용 로그만 DEBUG 설정할 것-->
<appender name="ApiLogFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>DEBUG</level> <!-- 로그 레벨을 DEBUG로 변경 -->
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<file>내 로그파일 보관할 경로</file> <!-- 파일 경로 -->
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS},%thread,%logger{36},%msg,%X{ip}%n</pattern> <!-- 저장될 로거 패턴 -->
</encoder>
<!-- 하루에 한번 압축 후 보관, 최대 30일, 1GB까지 보관 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>파일 이름 패턴.gz</fileNamePattern> <!-- 파일 경로 -->
<maxHistory>30</maxHistory>
<totalSizeCap>1GB</totalSizeCap>
</rollingPolicy>
</appender>
<!-- 기본 로그 설정 -->
<root level="INFO">
<appender-ref ref="Console"/>
</root>
<!-- 특정 조건 (예: DEBUG 레벨)의 로그만 파일에 저장하도록 설정 -->
<logger name="com.sch.sch_elasticsearch.aop" level="DEBUG" additivity="false">
<appender-ref ref="ApiLogFile"/>
</logger>
</configuration>
aop 파일(LoggingAspect)
package com.sch.sch_elasticsearch.aop;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
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.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
@Aspect
@Component
@RequiredArgsConstructor
public class LoggingAspect {
private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class);
private final ObjectMapper objectMapper;
//Domain 패턴에 적용 및 내 커스텀 Annotation이 설정된 파일만 포인트컷으로 적용
@Pointcut("execution(* com.sch.sch_elasticsearch.domain..*.*(..)) " +
"&& @annotation(com.sch.sch_elasticsearch.aop.SaveLogging)" )
public void pointCut() {
}
//포인트컷 전 데이터 수집
@Before("pointCut()")
public void logBeforeMethod() {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attributes != null) {
HttpServletRequest request = attributes.getRequest();
MDC.put("ip", request.getRemoteAddr()); //MDC에 추가하여 ip를 콘솔 로그에 담아낼 계획이다.
}
}
//지정된 포인트컷 표현식에 맞는 메서드가 성공적으로 반환될 때 실행, xml에서 DEBUG만 저장하기 때문에 DEBUG 형태로 출력
@AfterReturning("pointCut()")
public void logAfterMethodReturn(JoinPoint joinPoint) {
String className = joinPoint.getSignature().getDeclaringTypeName(); //클래스
String methodName = joinPoint.getSignature().getName(); //메서드
logger.debug("{},{}", className, methodName); //클래스와 메서드를 로그 파일에 저장
logger.info("[LoggingAspect] " + joinPoint.getSignature().toShortString()); //콘솔 출력
}
}
'프로젝트 > 여행지 오픈 API' 카테고리의 다른 글
[Elasticsearch] 회고 및 리뷰 (2) | 2023.11.24 |
---|---|
[Logstash] 로그스태시로 로그 뽑아서 저장하기 (0) | 2023.11.23 |
[Kibana] Kibana 데이터시각화 구현 (0) | 2023.11.21 |
[Elasticsearch] 네트워크 오버헤드 효율 비교 및 개선(쿼리 변경) (1) | 2023.11.20 |
[ElasticSearch] 최종 인덱스, 중복 문제와 오탈자 검색의 고민 (1) | 2023.11.13 |