diff --git a/.github/workflows/dev-ci-cd.yml b/.github/workflows/dev-ci-cd.yml index b8bdf11..ccfbe00 100644 --- a/.github/workflows/dev-ci-cd.yml +++ b/.github/workflows/dev-ci-cd.yml @@ -72,6 +72,7 @@ jobs: echo "${{ secrets.APPLICATION_YML }}" > ./src/main/resources/application.yml echo "${{ secrets.APPLICATION_PROD_YML }}" > ./src/main/resources/application-prod.yml echo "${{ secrets.APPLICATION_OAUTH2_YML }}" > ./src/main/resources/application-oauth2.yml + echo "${{ secrets.LOGBACK_SPRING_XML }}" > ./src/main/resources/logback-spring.xml mkdir -p src/test/resources echo "${{ secrets.APPLICATION_TEST_YML }}" > ./src/test/resources/application-test.yml diff --git a/.github/workflows/dev-test.yml b/.github/workflows/dev-test.yml index 9c47718..3c96083 100644 --- a/.github/workflows/dev-test.yml +++ b/.github/workflows/dev-test.yml @@ -84,6 +84,9 @@ jobs: - name: application-oauth2.yml 설정 run: echo "${{ secrets.APPLICATION_OAUTH2_YML }}" > ./src/main/resources/application-oauth2.yml + - name: logback-spring.xml 설정 + run: echo "${{ secrets.LOGBACK_SPRING_XML }}" > ./src/main/resources/logback-spring.xml + - name: application-test.yml 설정 run: | mkdir -p src/test/resources diff --git a/build.gradle b/build.gradle index 190f23e..446292d 100644 --- a/build.gradle +++ b/build.gradle @@ -51,7 +51,6 @@ dependencies { testRuntimeOnly 'org.junit.platform:junit-platform-launcher' testImplementation 'org.springframework.security:spring-security-test' - // Swagger 관련 implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.3.0' @@ -61,6 +60,8 @@ dependencies { // elasticSearch implementation 'org.springframework.boot:spring-boot-starter-data-elasticsearch' + // Log 관련 + implementation("net.logstash.logback:logstash-logback-encoder:7.4") } diff --git a/docker/dev-docker-compose.yml b/docker/dev-docker-compose.yml index 185da60..dc62d00 100644 --- a/docker/dev-docker-compose.yml +++ b/docker/dev-docker-compose.yml @@ -62,3 +62,24 @@ services: interval: 10s timeout: 10s retries: 120 + + logstash01: + container_name: kkh-logstash + ports: + - "5044:5044" + depends_on: + es01: + condition: service_healthy + kibana: + condition: service_healthy + image: docker.elastic.co/logstash/logstash:${STACK_VERSION} + labels: + co.elastic.logs/module: logstash + user: root + volumes: + - "./logstash/logstash.conf:/usr/share/logstash/pipeline/logstash.conf:ro" + environment: + - xpack.monitoring.enabled=true + - ELASTIC_USER=elastic + - ELASTIC_PASSWORD=${ELASTIC_PASSWORD} + - ELASTIC_HOSTS=http://es01:9200 diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index a10c995..0c15c53 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -150,7 +150,7 @@ services: setup: condition: service_healthy image: docker.elastic.co/elasticsearch/elasticsearch:${STACK_VERSION} - container_name: elasticsearch + container_name: kkh-es labels: co.elastic.logs/module: elasticsearch volumes: @@ -207,7 +207,7 @@ services: es01: condition: service_healthy image: docker.elastic.co/kibana/kibana:${STACK_VERSION} - container_name: kibana + container_name: kkh-kibana labels: co.elastic.logs/module: kibana volumes: @@ -237,6 +237,29 @@ services: timeout: 10s retries: 120 + logstash01: + container_name: kkh-logstash + depends_on: + es01: + condition: service_healthy + kibana: + condition: service_healthy + image: docker.elastic.co/logstash/logstash:${STACK_VERSION} + labels: + co.elastic.logs/module: logstash + user: root + ports: + - "5044:5044" + volumes: + - certs:/usr/share/logstash/certs + - logstashdata01:/usr/share/logstash/data + - "./logstash/logstash.conf:/usr/share/logstash/pipeline/logstash.conf:ro" + environment: + - xpack.monitoring.enabled=true + - ELASTIC_USER=elastic + - ELASTIC_PASSWORD=${ELASTIC_PASSWORD} + - ELASTIC_HOSTS=https://es01:9200 + mongodb1: restart: always image: mongo:latest @@ -326,6 +349,8 @@ volumes: driver: local kibanadata: driver: local + logstashdata01: + driver: local networks: monstache-network: diff --git a/src/main/java/site/kikihi/custom/global/exception/GlobalExceptionHandler.java b/src/main/java/site/kikihi/custom/global/exception/GlobalExceptionHandler.java index 1780d79..468a27c 100644 --- a/src/main/java/site/kikihi/custom/global/exception/GlobalExceptionHandler.java +++ b/src/main/java/site/kikihi/custom/global/exception/GlobalExceptionHandler.java @@ -27,9 +27,12 @@ public class GlobalExceptionHandler { /// 공통 처리 메서드 - private ApiResponse handleCustomException(CustomException customException) { + private ApiResponse handleCustomException(CustomException exception) { - return ApiResponse.fail(customException); + /// 로그 발생 + log.error("[ERROR 로깅] : {}", exception.getMessage()); + + return ApiResponse.fail(exception); } /// 예외 처리 @@ -77,9 +80,6 @@ public ApiResponse handleJwtAuthenticationException(Exception e @ExceptionHandler(Exception.class) public ApiResponse handleException(Exception e) { - /// 로그 발생 - log.error(e.getMessage(), e); - /// 500 예외 코드 검색 ErrorCode errorCode = ErrorCode.INTERNAL_SERVER_ERROR; diff --git a/src/main/java/site/kikihi/custom/global/logging/LogFilter.java b/src/main/java/site/kikihi/custom/global/logging/LogFilter.java index c69a5ae..baa06d1 100644 --- a/src/main/java/site/kikihi/custom/global/logging/LogFilter.java +++ b/src/main/java/site/kikihi/custom/global/logging/LogFilter.java @@ -25,11 +25,13 @@ public class LogFilter extends OncePerRequestFilter { protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { /// 요청 로그 남기기 - String username = request.getUserPrincipal() != null ? request.getUserPrincipal().getName() : "anonymous"; + String ipAddress = request.getRemoteAddr(); String httpMethod = request.getMethod(); String uri = URLDecoder.decode(request.getRequestURI(), StandardCharsets.UTF_8); + String username = request.getUserPrincipal() != null ? request.getUserPrincipal().getName() : "anonymous"; - log.info("[HTTP 요청 로깅]: [{}] {} - 사용자: {}", httpMethod, uri, username); + /// 로그스태시로 넘길 로깅 출력 + log.info("[HTTP 로깅]: {}, [{}], {}, {}", ipAddress, httpMethod, uri, username); /// 로그 남기고 넘기기 filterChain.doFilter(request, response); diff --git a/src/main/java/site/kikihi/custom/global/logging/LoggingAspect.java b/src/main/java/site/kikihi/custom/global/logging/LoggingAspect.java index 78e193a..7fa2291 100644 --- a/src/main/java/site/kikihi/custom/global/logging/LoggingAspect.java +++ b/src/main/java/site/kikihi/custom/global/logging/LoggingAspect.java @@ -3,11 +3,10 @@ import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; -import java.util.Arrays; - @Aspect @Component @Slf4j @@ -17,25 +16,25 @@ public class LoggingAspect { private void applicationLayer() { } - @Before("applicationLayer()") - public void logMethodEntry(JoinPoint joinPoint) { - log.info("[서비스 로깅] 메서드 진입: {}.{}({})", - joinPoint.getSignature().getDeclaringTypeName(), - joinPoint.getSignature().getName(), - Arrays.toString(joinPoint.getArgs())); - } + @Around("applicationLayer()") + public Object logProcessTime(ProceedingJoinPoint joinPoint) throws Throwable { + long start = System.currentTimeMillis(); - @AfterReturning(pointcut = "applicationLayer()", returning = "result") - public void logMethodExit(JoinPoint joinPoint, Object result) { - log.info("[서비스 로깅] 메서드 종료: {}.{} => 반환: {}", + Object proceed = joinPoint.proceed(); // 실제 메서드 실행 + + long executionTime = System.currentTimeMillis() - start; + + log.info("[서비스 로깅] 메서드 실행 시간: {}.{} 실행 시간 = {}ms", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName(), - result); + executionTime); + + return proceed; } @AfterThrowing(pointcut = "applicationLayer()") public void logException(JoinPoint joinPoint) { - log.info("[서비스 로깅] 예외 발생: {}.{}", + log.info("[서비스 로깅] 예외 발생: {},{}", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName()); }