Skip to content

Commit 21834c4

Browse files
authored
[feat/OPS-329] Sentry를 통한 모니터링 환경 구축. (#86)
* feat/OPS-329 : Sentry를 통한 모니터링 환경 구축. * feat/OPS-329: 추가로 프론트 요구 사항 반영.
1 parent 8ec5147 commit 21834c4

File tree

14 files changed

+126
-11
lines changed

14 files changed

+126
-11
lines changed

.github/workflows/test-server-ci.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ jobs:
2020
# ==================================
2121
ci:
2222
runs-on: ubuntu-latest
23+
env:
24+
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
2325

2426
steps:
2527
# 1. 소스 코드 체크아웃

build.gradle

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ plugins {
22
id 'java'
33
id 'org.springframework.boot' version '3.5.5'
44
id 'io.spring.dependency-management' version '1.1.7'
5+
id "io.sentry.jvm.gradle" version "5.12.0"
56
}
67

78
group = 'org.tuna.zoopzoop'
@@ -97,7 +98,10 @@ dependencies {
9798

9899
// Playwright for Java
99100
implementation 'com.microsoft.playwright:playwright:1.54.0'
100-
101+
102+
// Sentry (모니터링 용)
103+
implementation 'io.sentry:sentry-spring-boot-starter-jakarta:8.22.0'
104+
101105
// Apache Commons Codec
102106
implementation"commons-codec:commons-codec:1.19.0"
103107
}
@@ -111,3 +115,14 @@ dependencyManagement {
111115
tasks.named('test') {
112116
useJUnitPlatform()
113117
}
118+
119+
sentry {
120+
// Generates a JVM (Java, Kotlin, etc.) source bundle and uploads your source code to Sentry.
121+
// This enables source context, allowing you to see your source
122+
// code as part of your stack traces in Sentry.
123+
includeSourceContext = true
124+
125+
org = "whitedoggy"
126+
projectName = "zoopzoop-backend"
127+
authToken = System.getenv("SENTRY_AUTH_TOKEN")
128+
}

src/main/java/org/tuna/zoopzoop/backend/domain/auth/repository/RefreshTokenRepository.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@
55
import org.tuna.zoopzoop.backend.domain.auth.entity.RefreshToken;
66
import org.tuna.zoopzoop.backend.domain.member.entity.Member;
77

8+
import java.util.List;
89
import java.util.Optional;
910

1011
@Repository
1112
public interface RefreshTokenRepository extends JpaRepository<RefreshToken, Integer> {
1213
Optional<RefreshToken> findBySessionId(String sessionId);
1314
Optional<RefreshToken> findByMember(Member member);
15+
List<RefreshToken> findAllByMember(Member member);
1416
}

src/main/java/org/tuna/zoopzoop/backend/domain/auth/service/RefreshTokenService.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import java.time.LocalDateTime;
1212
import java.time.ZoneId;
1313
import java.util.Date;
14+
import java.util.List;
1415
import java.util.UUID;
1516

1617
@Service
@@ -51,4 +52,11 @@ public void deleteBySessionId(String sessionId) {
5152
refreshTokenRepository.findBySessionId(sessionId)
5253
.orElseThrow(() -> new BadCredentialsException("잘못된 요청입니다."));
5354
}
55+
56+
public void deleteByMember(Member member) {
57+
List<RefreshToken> tokens = refreshTokenRepository.findAllByMember(member);
58+
if (!tokens.isEmpty()) {
59+
refreshTokenRepository.deleteAll(tokens);
60+
}
61+
}
5462
}

src/main/java/org/tuna/zoopzoop/backend/domain/member/controller/ApiV1MemberController.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import org.springframework.http.ResponseEntity;
99
import org.springframework.security.core.annotation.AuthenticationPrincipal;
1010
import org.springframework.web.bind.annotation.*;
11+
import org.tuna.zoopzoop.backend.domain.auth.service.RefreshTokenService;
1112
import org.tuna.zoopzoop.backend.domain.member.dto.req.ReqBodyForEditMember;
1213
import org.tuna.zoopzoop.backend.domain.member.dto.req.ReqBodyForEditMemberName;
1314
import org.tuna.zoopzoop.backend.domain.member.dto.req.ReqBodyForEditMemberProfileImage;
@@ -25,6 +26,7 @@
2526
@Tag(name = "ApiV1MemberController", description = "사용자 REST API 컨트롤러")
2627
public class ApiV1MemberController {
2728
private final MemberService memberService;
29+
private final RefreshTokenService refreshTokenService;
2830
/// api/v1/member/me : 사용자 정보 조회 (GET)
2931
/// api/v1/member/edit : 사용자 닉네임 수정 (PUT)
3032
/// api/v1/member : 사용자 탈퇴 (DELETE)
@@ -145,6 +147,7 @@ public ResponseEntity<RsData<Void>> deleteMember(
145147
@AuthenticationPrincipal CustomUserDetails userDetails
146148
) {
147149
Member member = userDetails.getMember();
150+
refreshTokenService.deleteByMember(member);
148151
memberService.hardDeleteMember(member);
149152
return ResponseEntity
150153
.status(HttpStatus.OK)
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package org.tuna.zoopzoop.backend.global.config.sentry;
2+
3+
import io.sentry.SentryOptions;
4+
import org.springframework.context.annotation.Bean;
5+
import org.springframework.context.annotation.Configuration;
6+
7+
@Configuration
8+
public class SentryConfig {
9+
10+
@Bean
11+
public SentryOptions.BeforeSendCallback beforeSend() {
12+
return (event, hint) -> {
13+
if(event.getMessage() != null
14+
&& event.getMessage().getFormatted().contains("JWT 토큰")) {
15+
return null;
16+
}
17+
return event;
18+
};
19+
}
20+
}

src/main/java/org/tuna/zoopzoop/backend/global/exception/GlobalExceptionHandler.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,14 @@
33
import com.fasterxml.jackson.databind.JsonMappingException;
44
import com.fasterxml.jackson.databind.ObjectMapper;
55
import com.fasterxml.jackson.databind.exc.InvalidFormatException;
6+
import io.sentry.Sentry;
67
import jakarta.persistence.NoResultException;
78
import jakarta.validation.ConstraintViolationException;
89
import lombok.extern.slf4j.Slf4j;
910
import org.springframework.beans.factory.annotation.Autowired;
1011
import org.springframework.dao.DataIntegrityViolationException;
11-
import org.springframework.http.HttpStatus;
1212
import org.springframework.http.ResponseEntity;
1313
import org.springframework.http.converter.HttpMessageNotReadableException;
14-
import org.springframework.validation.FieldError;
1514
import org.springframework.web.bind.MethodArgumentNotValidException;
1615
import org.springframework.web.bind.annotation.ControllerAdvice;
1716
import org.springframework.web.bind.annotation.ExceptionHandler;
@@ -34,6 +33,7 @@ public class GlobalExceptionHandler {
3433

3534
@ExceptionHandler(NoResultException.class) // 자료를 찾지 못했을 경우.
3635
public ResponseEntity<RsData<Void>> handleNoResultException(NoResultException e) {
36+
Sentry.captureException(e);
3737
return new ResponseEntity<>(
3838
new RsData<>(
3939
"404",

src/main/java/org/tuna/zoopzoop/backend/global/security/SecurityConfig.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti
4646
"/actuator/health", // health 체크용
4747
"/dev/**" // ← 추가: dev 토큰 발급은 누구나 접근
4848
).permitAll()
49-
.anyRequest().authenticated()
5049
)
5150
.oauth2Login(oauth2 -> oauth2
5251
.authorizationEndpoint(authorization -> authorization

src/main/java/org/tuna/zoopzoop/backend/global/security/jwt/JwtAuthenticationFilter.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,21 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
2525
private final JwtUtil jwtUtil;
2626
private final CustomUserDetailsService userDetailsService;
2727

28+
// 필터를 적용할 URL 패턴
29+
private static final String API_PREFIX = "/api/v1";
30+
2831
@Override
2932
protected void doFilterInternal(HttpServletRequest request,
3033
HttpServletResponse response,
3134
FilterChain filterChain) throws ServletException, IOException {
35+
String path = request.getRequestURI();
36+
37+
// /api/ 로 시작하지 않으면 JWT 검증 건너뜀
38+
if (!path.startsWith(API_PREFIX)) {
39+
filterChain.doFilter(request, response);
40+
return;
41+
}
42+
3243
String token = getTokenFromRequest(request); // Authorization 헤더에서 JWT 토큰 추출
3344
log.info("[JwtFilter] Token from request: {}", token);
3445
log.info("[JwtFilter] Token valid? {}", jwtUtil.validateToken(token));

src/main/java/org/tuna/zoopzoop/backend/global/security/jwt/JwtUtil.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -86,15 +86,15 @@ public boolean validateToken(String token) {
8686
.parseSignedClaims(token);
8787
return true;
8888
} catch (ExpiredJwtException e) {
89-
log.error("JWT 토큰이 만료되었습니다: {}", e.getMessage());
89+
log.info("JWT 토큰이 만료되었습니다: {}", e.getMessage());
9090
} catch (UnsupportedJwtException e) {
91-
log.error("지원되지 않는 JWT 토큰입니다: {}", e.getMessage());
91+
log.info("지원되지 않는 JWT 토큰입니다: {}", e.getMessage());
9292
} catch (MalformedJwtException e) {
93-
log.error("잘못된 JWT 토큰입니다: {}", e.getMessage());
93+
log.info("잘못된 JWT 토큰입니다: {}", e.getMessage());
9494
} catch (SecurityException e) {
95-
log.error("JWT 서명이 잘못되었습니다: {}", e.getMessage());
95+
log.info("JWT 서명이 잘못되었습니다: {}", e.getMessage());
9696
} catch (IllegalArgumentException e) {
97-
log.error("JWT 토큰이 비어있습니다: {}", e.getMessage());
97+
log.info("JWT 토큰이 비어있습니다: {}", e.getMessage());
9898
}
9999
return false;
100100
}

0 commit comments

Comments
 (0)