Skip to content

Commit

Permalink
Merge pull request #174 from softeerbootcamp4th/feature/#157-monitoring
Browse files Browse the repository at this point in the history
Feature/#157 monitoring
  • Loading branch information
k000927 authored Aug 19, 2024
2 parents 3b2e003 + f62d18c commit 96f349c
Show file tree
Hide file tree
Showing 14 changed files with 177 additions and 55 deletions.
7 changes: 5 additions & 2 deletions Server/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,7 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-aop'
implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'

implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0'

implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.3.0")
runtimeOnly 'com.h2database:h2'

testImplementation 'org.springframework.boot:spring-boot-starter-test'
Expand All @@ -49,6 +48,10 @@ dependencies {
annotationProcessor('org.projectlombok:lombok')
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.13.3'

// prometheus
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'io.micrometer:micrometer-registry-prometheus'

}

tasks.named('test') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication
@EnableAspectJAutoProxy
@EnableScheduling
public class CasperEventApplication {

public static void main(String[] args) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,36 @@

import JGS.CasperEvent.domain.event.dto.ResponseDto.rushEventResponseDto.RushEventResponseDto;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.time.LocalDateTime;

@Service
@EnableScheduling
@RequiredArgsConstructor
@Slf4j
public class RushEventScheduler {

private final RushEventService rushEventService;
private final RedisTemplate<String, RushEventResponseDto> rushEventRedisTemplate;

// 매일 12시에 스케줄된 작업을 실행합니다.
@Scheduled(cron = "0 0 12 * * ?")
// 매일 0시 1분에 스케줄된 작업을 실행합니다.
@Scheduled(cron = "0 1 0 * * ?")
public void fetchDailyEvents() {
// 오늘의 날짜를 구합니다.
LocalDate today = LocalDate.now();

// EventService를 통해 오늘의 이벤트를 가져옵니다.
RushEventResponseDto todayEvent = rushEventService.getTodayRushEvent(today);
RushEventResponseDto todayEvent = rushEventService.getTodayRushEventFromRDB();

// 가져온 이벤트에 대한 추가 작업을 수행합니다.
// 예: 캐싱, 로그 기록, 알림 발송 등
rushEventRedisTemplate.opsForValue().set("todayEvent", todayEvent);

// 로그 출력
log.info("선착순 이벤트 스케줄러 실행: {}", LocalDateTime.now());
log.info("가져온 이벤트 날짜: {}", todayEvent.startDateTime());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public class RushEventService {
@Transactional
public RushEventListResponseDto getAllRushEvents() {
// 오늘의 선착순 이벤트 꺼내오기
RushEventResponseDto todayEvent = getTodayRushEvent();
RushEventResponseDto todayEvent = getTodayRushEventFromRedis();

// DB에서 모든 RushEvent 가져오기
List<RushEvent> rushEventList = rushEventRepository.findAll();
Expand Down Expand Up @@ -67,13 +67,13 @@ public RushEventListResponseDto getAllRushEvents() {

// 응모 여부 조회
public boolean isExists(String userId) {
Long todayEventId = getTodayRushEvent().rushEventId();
Long todayEventId = getTodayRushEventFromRedis().rushEventId();
return rushParticipantsRepository.existsByRushEvent_RushEventIdAndBaseUser_Id(todayEventId, userId);
}

@Transactional
public void apply(BaseUser user, int optionId) {
Long todayEventId = getTodayRushEvent().rushEventId();
Long todayEventId = getTodayRushEventFromRedis().rushEventId();

// 이미 응모한 회원인지 검증
if (rushParticipantsRepository.existsByRushEvent_RushEventIdAndBaseUser_Id(todayEventId, user.getId())) {
Expand All @@ -90,7 +90,7 @@ public void apply(BaseUser user, int optionId) {

// 진행중인 게임의 응모 비율 반환
public RushEventRateResponseDto getRushEventRate(BaseUser user) {
Long todayEventId = getTodayRushEvent().rushEventId();
Long todayEventId = getTodayRushEventFromRedis().rushEventId();
Optional<Integer> optionId = rushParticipantsRepository.getOptionIdByUserId(user.getId());
long leftOptionCount = rushParticipantsRepository.countByRushEvent_RushEventIdAndOptionId(todayEventId, 1);
long rightOptionCount = rushParticipantsRepository.countByRushEvent_RushEventIdAndOptionId(todayEventId, 2);
Expand All @@ -104,7 +104,7 @@ public RushEventRateResponseDto getRushEventRate(BaseUser user) {
// 해당 요청은 무조건 응모한 유저일 때만 요청 가능하다고 가정
@Transactional
public RushEventResultResponseDto getRushEventResult(BaseUser user) {
RushEventResponseDto todayRushEvent = getTodayRushEvent();
RushEventResponseDto todayRushEvent = getTodayRushEventFromRedis();

// 최종 선택 비율을 조회
// TODO: 레디스에 캐시
Expand Down Expand Up @@ -145,9 +145,10 @@ public RushEventResultResponseDto getRushEventResult(BaseUser user) {
return new RushEventResultResponseDto(rushEventRateResponseDto, rank, totalParticipants, isWinner);
}

@Transactional
// 오늘의 이벤트를 DB에 꺼내서 반환
public RushEventResponseDto getTodayRushEvent(LocalDate today) {
public RushEventResponseDto getTodayRushEventFromRDB() {
LocalDate today = LocalDate.now();

// 오늘 날짜에 해당하는 모든 이벤트 꺼내옴
List<RushEvent> rushEventList = rushEventRepository.findByEventDate(today);

Expand All @@ -163,10 +164,13 @@ public RushEventResponseDto getTodayRushEvent(LocalDate today) {
}

// 오늘의 이벤트 꺼내오기
private RushEventResponseDto getTodayRushEvent() {
private RushEventResponseDto getTodayRushEventFromRedis() {
RushEventResponseDto todayEvent = rushEventRedisTemplate.opsForValue().get("todayEvent");

// Redis에 오늘의 이벤트가 없으면 DB에서 가져와서 Redis에 저장한 후 반환.
if (todayEvent == null) {
throw new CustomException("오늘의 이벤트가 Redis에 없습니다.", CustomErrorCode.TODAY_RUSH_EVENT_NOT_FOUND);
todayEvent = getTodayRushEventFromRDB();
rushEventRedisTemplate.opsForValue().set("todayEvent", todayEvent);
}

return todayEvent;
Expand Down Expand Up @@ -234,7 +238,7 @@ public void setTodayEventToRedis() {

// 오늘의 이벤트 옵션 정보를 반환
public MainRushEventOptionsResponseDto getTodayRushEventOptions() {
RushEventResponseDto todayEvent = getTodayRushEvent();
RushEventResponseDto todayEvent = getTodayRushEventFromRedis();
Set<RushEventOptionResponseDto> options = todayEvent.options();

RushEventOptionResponseDto leftOption = options.stream()
Expand All @@ -255,7 +259,7 @@ public MainRushEventOptionsResponseDto getTodayRushEventOptions() {

public ResultRushEventOptionResponseDto getRushEventOptionResult(int optionId) {
Position position = Position.of(optionId);
RushEventResponseDto todayEvent = getTodayRushEvent();
RushEventResponseDto todayEvent = getTodayRushEventFromRedis();
Set<RushEventOptionResponseDto> options = todayEvent.options();

if (options.size() != 2) {
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package JGS.CasperEvent.global.config;

import JGS.CasperEvent.domain.event.service.adminService.AdminService;
import JGS.CasperEvent.global.interceptor.RequestInterceptor;
import JGS.CasperEvent.global.jwt.filter.JwtAuthorizationFilter;
import JGS.CasperEvent.global.jwt.filter.JwtUserFilter;
import JGS.CasperEvent.global.jwt.filter.VerifyAdminFilter;
Expand All @@ -15,6 +16,7 @@
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;


Expand Down Expand Up @@ -93,5 +95,10 @@ public FilterRegistrationBean jwtAuthorizationFilter(JwtProvider provider, Objec
return filterRegistrationBean;
}

@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new RequestInterceptor())
.addPathPatterns("/**"); // 모든 경로에 대해 인터셉터 적용

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
@Getter
public enum CustomErrorCode {
NO_RUSH_EVENT("선착순 이벤트를 찾을 수 없습니다.", 404),
NO_LOTTERY_EVENT("선착순 이벤트를 찾을 수 없습니다.", 404),
NO_LOTTERY_EVENT("추첨 이벤트를 찾을 수 없습니다.", 404),
NO_RUSH_EVENT_OPTION("해당 밸런스 게임 선택지를 찾을 수 없습니다.", 404),
INVALID_PARAMETER("잘못된 파라미터 입력입니다.", 400),
CASPERBOT_NOT_FOUND("배지를 찾을 수 없습니다.", 404),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package JGS.CasperEvent.global.interceptor;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import java.util.UUID;

@Component
@Slf4j
public class RequestInterceptor implements HandlerInterceptor {

private static final String REQUEST_ID = "requestId";

@Override
public boolean preHandle(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull Object handler) {
// UUID를 사용해 고유한 requestId 생성
String requestId = UUID.randomUUID().toString();

// MDC에 requestId 추가하여 로깅 시 포함되도록 설정
MDC.put(REQUEST_ID, requestId);

String requestURI = request.getMethod() + " " + request.getRequestURL();

String queryString = request.getQueryString();

log.info("Request [{}{}]", requestURI, queryString);

// 요청의 헤더에 requestId 추가 (선택 사항)
response.addHeader(REQUEST_ID, requestId);

return true; // 다음 인터셉터나 컨트롤러로 요청 전달
}

@Override
public void afterCompletion(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull Object handler, Exception ex) {
log.info("Response {} [{}]", response.getStatus(), handler);

// 요청이 완료된 후 MDC에서 requestId 제거
MDC.remove(REQUEST_ID);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ public class JwtAuthorizationFilter implements Filter {
"/event/rush", "/event/lottery/caspers",
"/admin/join", "/admin/auth", "/h2", "/h2/*",
"/swagger-ui/*", "/v3/api-docs", "/v3/api-docs/*",
"/event/lottery", "/link/*", "/event/total"
"/event/lottery", "/link/*", "/event/total",
"/actuator", "/actuator/*"
};
private final String[] blackListUris = new String[]{
"/event/rush/*", "/event/lottery/casperBot"
Expand Down
11 changes: 10 additions & 1 deletion Server/src/main/resources/application-prod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,13 @@ client:
url: ${CLIENT_URL}
localUrl: ${LOCAL_CLIENT_URL}
shortenUrlService:
url: ${SPRING_SERVER_URL}
url: ${SPRING_SERVER_URL}

management:
endpoints:
web:
exposure:
include: prometheus, health, info
metrics:
tags:
application: ${spring.application.name}
4 changes: 3 additions & 1 deletion Server/src/main/resources/application.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
spring:
application:
name: hybrid-jgs
jpa:
show-sql: true
show-sql: false
# 데이터베이스 설정은 공통으로 지정하지 않음
67 changes: 67 additions & 0 deletions Server/src/main/resources/logback.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<configuration>
<!-- 콘솔 출력 설정 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg [%X{requestId}]%n</pattern>
</encoder>
</appender>

<!-- INFO 로그 파일 설정 -->
<appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/info.log</file> <!-- 프로젝트 디렉토리 내 logs 폴더 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>logs/info.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg [%X{requestId}]%n</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFO</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>

<!-- ERROR 로그 파일 설정 -->
<appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/error.log</file> <!-- 프로젝트 디렉토리 내 logs 폴더 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>logs/error.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg [%X{requestId}]%n</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>

<!-- WARN 로그 파일 설정 -->
<appender name="WARN_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/warn.log</file> <!-- 프로젝트 디렉토리 내 logs 폴더 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>logs/warn.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg [%X{requestId}]%n</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>WARN</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>

<!-- 루트 로거 설정: 콘솔과 파일에 모두 출력 -->
<root level="INFO">
<appender-ref ref="STDOUT" />
<appender-ref ref="INFO_FILE" />
<appender-ref ref="ERROR_FILE" />
<appender-ref ref="WARN_FILE" />
</root>
</configuration>
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,13 @@ void fetchDailyEvents_ShouldCacheTodayEventInRedis() {
);

given(rushEventRedisTemplate.opsForValue()).willReturn(valueOperations);
given(rushEventService.getTodayRushEvent(today)).willReturn(todayEvent);
given(rushEventService.getTodayRushEventFromRDB()).willReturn(todayEvent);

// when
rushEventScheduler.fetchDailyEvents();

// then
verify(rushEventService).getTodayRushEvent(today);
verify(rushEventService).getTodayRushEventFromRDB();
verify(rushEventRedisTemplate.opsForValue()).set("todayEvent", todayEvent);
}
}
Loading

0 comments on commit 96f349c

Please sign in to comment.