Skip to content

Commit af0abe0

Browse files
committed
feat: 도메인 이벤트 구조화 로깅 추가
- VoteService: vote.cast, vote.change 이벤트 로깅 - AuthService: user.signup, user.login 이벤트 로깅 (신규/기존 사용자 구분) - CommentService: comment.create, comment.like, comment.unlike 이벤트 로깅 - AdminQuestionService: question.publish, question.close 이벤트 로깅 - UserRepository: existsByProviderIdAndDeletedAtIsNull 메서드 추가 - logstash-logback-encoder의 kv() 메서드로 JSON 필드 추가 - Athena SQL 분석을 위한 event 필드 표준화
1 parent 659e1d0 commit af0abe0

5 files changed

Lines changed: 70 additions & 43 deletions

File tree

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.ifu.ifu_server.domain.auth.service;
22

3-
import com.ifu.ifu_server.domain.auth.dto.TokenResponse;
3+
import static net.logstash.logback.argument.StructuredArguments.kv;
4+
45
import com.ifu.ifu_server.domain.user.entity.User;
56
import com.ifu.ifu_server.domain.user.repository.UserRepository;
67
import com.ifu.ifu_server.domain.user.service.UserService;
@@ -15,24 +16,32 @@
1516
@RequiredArgsConstructor
1617
public class AuthService {
1718

18-
private final UserRepository userRepository;
19-
private final UserService userService;
20-
private final JwtTokenProvider jwtTokenProvider;
19+
private final UserRepository userRepository;
20+
private final UserService userService;
21+
private final JwtTokenProvider jwtTokenProvider;
22+
23+
@Transactional
24+
public User processOAuth2Login(String providerId, String email) {
25+
boolean isNewUser = !userRepository.existsByProviderIdAndDeletedAtIsNull(providerId);
2126

22-
@Transactional
23-
public User processOAuth2Login(String providerId, String email) {
24-
User user = userRepository.findByProviderIdAndDeletedAtIsNull(providerId)
25-
.orElseGet(() -> userService.createUser(providerId, email));
27+
User user = userRepository.findByProviderIdAndDeletedAtIsNull(providerId)
28+
.orElseGet(() -> userService.createUser(providerId, email));
2629

27-
if (user.getEmail() == null && email != null) {
28-
user.updateEmail(email);
29-
}
30+
if (user.getEmail() == null && email != null) {
31+
user.updateEmail(email);
32+
}
3033

31-
String refreshToken = jwtTokenProvider.createRefreshToken(user.getId());
34+
String refreshToken = jwtTokenProvider.createRefreshToken(user.getId());
3235

33-
user.updateRefreshToken(refreshToken);
34-
userRepository.save(user);
36+
user.updateRefreshToken(refreshToken);
37+
userRepository.save(user);
3538

36-
return user;
39+
if (isNewUser) {
40+
log.info("신규 회원가입", kv("event", "user.signup"), kv("userId", user.getId()));
41+
} else {
42+
log.info("로그인", kv("event", "user.login"), kv("userId", user.getId()));
3743
}
38-
}
44+
45+
return user;
46+
}
47+
}

src/main/java/com/ifu/ifu_server/domain/comment/service/CommentService.java

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.ifu.ifu_server.domain.comment.service;
22

3+
import static net.logstash.logback.argument.StructuredArguments.kv;
4+
35
import com.ifu.ifu_server.domain.comment.dto.AdminCommentDeleteResponse;
46
import com.ifu.ifu_server.domain.comment.dto.AdminCommentListResponse;
57
import com.ifu.ifu_server.domain.comment.dto.AdminCommentReplyResponse;
@@ -90,6 +92,11 @@ public CommentResponse createComment(Long userId, CommentCreateRequest request)
9092
Comment savedComment = commentRepository.save(comment);
9193
// streak 업데이트
9294
userService.updateUserStreak(user);
95+
96+
log.info("댓글 생성", kv("event", "comment.create"),
97+
kv("userId", userId), kv("commentId", savedComment.getId()),
98+
kv("questionId", request.questionId()), kv("isReply", parent != null));
99+
93100
return CommentMapper.toCommentResponse(savedComment, List.of(), List.of(), Set.of());
94101
}
95102

@@ -159,10 +166,12 @@ public CommentListResponse getComments(Long userId, Long questionId, int page, i
159166
List<CommentReplyResponse> replies = repliesByParent
160167
.getOrDefault(comment.getId(), List.of())
161168
.stream()
162-
.map(reply -> CommentMapper.toCommentReplyResponse(reply, blockedCommentIds, likedCommentIds))
169+
.map(reply -> CommentMapper.toCommentReplyResponse(reply, blockedCommentIds,
170+
likedCommentIds))
163171
.collect(Collectors.toList());
164172

165-
return CommentMapper.toCommentResponse(comment, replies, blockedCommentIds, likedCommentIds);
173+
return CommentMapper.toCommentResponse(comment, replies, blockedCommentIds,
174+
likedCommentIds);
166175
})
167176
.collect(Collectors.toList());
168177

@@ -264,8 +273,8 @@ public void likeComment(Long userId, Long commentId) {
264273
// 좋아요 수 증가
265274
comment.incrementLikeCount();
266275

267-
log.info("댓글 좋아요 - userId: {}, commentId: {}, likeCount: {}",
268-
userId, commentId, comment.getLikeCount());
276+
log.info("댓글 좋아요", kv("event", "comment.like"),
277+
kv("userId", userId), kv("commentId", commentId), kv("likeCount", comment.getLikeCount()));
269278
}
270279

271280
/**
@@ -289,8 +298,8 @@ public void unlikeComment(Long userId, Long commentId) {
289298
// 좋아요 수 감소
290299
comment.decrementLikeCount();
291300

292-
log.info("댓글 좋아요 취소 - userId: {}, commentId: {}, likeCount: {}",
293-
userId, commentId, comment.getLikeCount());
301+
log.info("댓글 좋아요 취소", kv("event", "comment.unlike"),
302+
kv("userId", userId), kv("commentId", commentId), kv("likeCount", comment.getLikeCount()));
294303
}
295304

296305
// === Admin ===
@@ -385,7 +394,8 @@ public AdminCommentDeleteResponse forceDeleteComment(Long adminUserId, Long comm
385394
comment.delete(DeletedBy.ADMIN);
386395

387396
// 로그 남기기
388-
log.info("Admin {} deleted comment {} with reason: {}", adminUserId, commentId, reason);
397+
log.info("어드민 댓글 삭제", kv("event", "admin.comment.delete"),
398+
kv("adminUserId", adminUserId), kv("commentId", commentId), kv("reason", reason));
389399

390400
return new AdminCommentDeleteResponse(
391401
comment.getId(),

src/main/java/com/ifu/ifu_server/domain/question/service/AdminQuestionService.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.ifu.ifu_server.domain.question.service;
22

3+
import static net.logstash.logback.argument.StructuredArguments.kv;
4+
35
import com.ifu.ifu_server.domain.question.dto.AdminQuestionResponse;
46
import com.ifu.ifu_server.domain.question.dto.CloseQuestionResponse;
57
import com.ifu.ifu_server.domain.question.dto.PublishQuestionResponse;
@@ -32,7 +34,8 @@ public class AdminQuestionService {
3234
public List<AdminQuestionResponse> getAllQuestions() {
3335
List<Question> questions = questionRepository.findAllByOrderByIdAsc();
3436

35-
log.info("어드민 질문 전체 목록 조회 - resultCount: {}", questions.size());
37+
log.info("어드민 질문 전체 목록 조회", kv("event", "admin.question.list"),
38+
kv("resultCount", questions.size()));
3639

3740
return questions.stream()
3841
.map(AdminQuestionResponse::from)
@@ -56,7 +59,8 @@ public CloseQuestionResponse closeQuestion(Long questionId) {
5659
throw new BusinessException(ErrorCode.QUESTION_CLOSE_NOT_ALLOWED);
5760
}
5861

59-
log.info("어드민 질문 종료 - questionId: {}, closedAt: {}", questionId, question.getCloseAt());
62+
log.info("질문 종료", kv("event", "question.close"),
63+
kv("questionId", questionId), kv("closedAt", question.getCloseAt()));
6064

6165
return CloseQuestionResponse.from(question);
6266
}
@@ -94,7 +98,8 @@ public PublishQuestionResponse publishQuestion(Long questionId) {
9498
throw new BusinessException(ErrorCode.QUESTION_PUBLISH_NOT_ALLOWED);
9599
}
96100

97-
log.info("어드민 질문 발행 - questionId: {}, publishAt: {}", questionId, question.getPublishAt());
101+
log.info("질문 발행", kv("event", "question.publish"),
102+
kv("questionId", questionId), kv("publishAt", question.getPublishAt()));
98103

99104
return PublishQuestionResponse.from(question);
100105
}
Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,28 @@
11
package com.ifu.ifu_server.domain.user.repository;
22

33
import com.ifu.ifu_server.domain.user.entity.User;
4+
import java.util.Optional;
45
import org.springframework.data.jpa.repository.JpaRepository;
56
import org.springframework.data.jpa.repository.Modifying;
67
import org.springframework.data.jpa.repository.Query;
78
import org.springframework.data.repository.query.Param;
89
import org.springframework.stereotype.Repository;
910

10-
import java.util.Optional;
11-
1211
@Repository
1312
public interface UserRepository extends JpaRepository<User, Long> {
1413

15-
Optional<User> findByProviderIdAndDeletedAtIsNull(String providerId);
14+
Optional<User> findByProviderIdAndDeletedAtIsNull(String providerId);
15+
16+
boolean existsByProviderIdAndDeletedAtIsNull(String providerId);
1617

17-
Optional<User> findByIdAndDeletedAtIsNull(Long id);
18+
Optional<User> findByIdAndDeletedAtIsNull(Long id);
1819

19-
boolean existsByNicknameAndDeletedAtIsNull(String nickname);
20+
boolean existsByNicknameAndDeletedAtIsNull(String nickname);
2021

21-
/**
22-
* 부하 테스트용 유저 삭제 (이메일에 특정 문자열 포함)
23-
*/
24-
@Modifying
25-
@Query("DELETE FROM User u WHERE u.email LIKE %:emailPattern%")
26-
int deleteByEmailContaining(@Param("emailPattern") String emailPattern);
22+
/**
23+
* 부하 테스트용 유저 삭제 (이메일에 특정 문자열 포함)
24+
*/
25+
@Modifying
26+
@Query("DELETE FROM User u WHERE u.email LIKE %:emailPattern%")
27+
int deleteByEmailContaining(@Param("emailPattern") String emailPattern);
2728
}

src/main/java/com/ifu/ifu_server/domain/vote/service/VoteService.java

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.ifu.ifu_server.domain.vote.service;
22

3+
import static net.logstash.logback.argument.StructuredArguments.kv;
4+
35
import com.ifu.ifu_server.domain.question.entity.Question;
46
import com.ifu.ifu_server.domain.question.repository.QuestionRepository;
57
import com.ifu.ifu_server.domain.user.entity.User;
@@ -29,9 +31,7 @@ public class VoteService {
2931
private final UserService userService;
3032

3133
/**
32-
* 투표하기 (upsert)
33-
* - 기존 투표가 없으면 생성
34-
* - 기존 투표가 있으면 변경
34+
* 투표하기 (upsert) - 기존 투표가 없으면 생성 - 기존 투표가 있으면 변경
3535
*/
3636
@Transactional
3737
public VoteResponse vote(Long userId, Long questionId, VoteRequest request) {
@@ -67,8 +67,9 @@ public VoteResponse vote(Long userId, Long questionId, VoteRequest request) {
6767
updateVoteStats(voteStats, previousChoice, choice);
6868
vote.updateChoice(choice);
6969
isChanged = true;
70-
log.info("투표 변경 - userId: {}, questionId: {}, {} -> {}",
71-
userId, questionId, previousChoice, choice);
70+
log.info("투표 변경", kv("event", "vote.change"),
71+
kv("userId", userId), kv("questionId", questionId),
72+
kv("previousChoice", previousChoice), kv("newChoice", choice));
7273
}
7374
} else {
7475
// 새 투표 생성
@@ -85,7 +86,8 @@ public VoteResponse vote(Long userId, Long questionId, VoteRequest request) {
8586
// streak 업데이트 (새 투표만)
8687
userService.updateUserStreak(user);
8788

88-
log.info("새 투표 - userId: {}, questionId: {}, choice: {}", userId, questionId, choice);
89+
log.info("새 투표 생성", kv("event", "vote.cast"),
90+
kv("userId", userId), kv("questionId", questionId), kv("choice", choice));
8991
}
9092

9193
return new VoteResponse(

0 commit comments

Comments
 (0)