diff --git a/build.gradle b/build.gradle index 76225337..4527d19f 100644 --- a/build.gradle +++ b/build.gradle @@ -50,7 +50,6 @@ dependencies { runtimeOnly 'com.mysql:mysql-connector-j' runtimeOnly 'com.h2database:h2' - // Security implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.springframework.boot:spring-boot-starter-validation' @@ -88,6 +87,9 @@ dependencies { // Jasypt implementation 'com.github.ulisesbocchio:jasypt-spring-boot-starter:3.0.5' + + // Slack-Alarm + implementation 'com.github.maricn:logback-slack-appender:1.6.1' } tasks.named('test') { diff --git a/src/main/java/doldol_server/doldol/auth/filter/CustomLogoutFilter.java b/src/main/java/doldol_server/doldol/auth/filter/CustomLogoutFilter.java index 8489bfaa..5bed2a6e 100644 --- a/src/main/java/doldol_server/doldol/auth/filter/CustomLogoutFilter.java +++ b/src/main/java/doldol_server/doldol/auth/filter/CustomLogoutFilter.java @@ -18,7 +18,9 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +@Slf4j @RequiredArgsConstructor public class CustomLogoutFilter extends GenericFilterBean { @@ -47,6 +49,8 @@ private void doFilter(HttpServletRequest request, HttpServletResponse response, tokenProvider.deleteRefreshToken(id); + log.info("로그아웃 완료: userId={}", id); + ResponseUtil.writeNoContent( response, objectMapper, diff --git a/src/main/java/doldol_server/doldol/auth/filter/CustomUsernamePasswordAuthenticationFilter.java b/src/main/java/doldol_server/doldol/auth/filter/CustomUsernamePasswordAuthenticationFilter.java index b30cd439..4086924e 100644 --- a/src/main/java/doldol_server/doldol/auth/filter/CustomUsernamePasswordAuthenticationFilter.java +++ b/src/main/java/doldol_server/doldol/auth/filter/CustomUsernamePasswordAuthenticationFilter.java @@ -25,7 +25,9 @@ import jakarta.servlet.FilterChain; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +@Slf4j public abstract class CustomUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter { private final AuthenticationManager authenticationManager; @@ -64,13 +66,17 @@ protected void successfulAuthentication(HttpServletRequest request, HttpServletR @Override protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException { - handleFailureAuthentication(response); + handleFailureAuthentication(request, response); } private void handleSuccessAuthentication(HttpServletResponse response, Authentication authentication) throws IOException { CustomUserDetails userDetails = (CustomUserDetails)authentication.getPrincipal(); + + log.info("일반 로그인 성공: userId={}, role={}", + userDetails.getUserId(), userDetails.getRole()); + String userid = String.valueOf(userDetails.getUserId()); Collection authorities = authentication.getAuthorities(); @@ -96,8 +102,20 @@ private void handleSuccessAuthentication(HttpServletResponse response, Authentic ); } - private void handleFailureAuthentication(HttpServletResponse response) throws IOException { + private void handleFailureAuthentication(HttpServletRequest request, HttpServletResponse response) throws IOException { + String attemptedId = extractAttemptedId(request); + log.warn("로그인 실패: 아이디='{}', 잘못된 아이디 또는 비밀번호", attemptedId); + ResponseUtil.writeErrorResponse(response, objectMapper, AuthErrorCode.WRONG_ID_PW); } + private String extractAttemptedId(HttpServletRequest request) { + try { + String messageBody = StreamUtils.copyToString(request.getInputStream(), StandardCharsets.UTF_8); + LoginRequest loginRequest = objectMapper.readValue(messageBody, LoginRequest.class); + return loginRequest.id(); + } catch (Exception e) { + return "unknown"; + } + } } diff --git a/src/main/java/doldol_server/doldol/auth/filter/JwtAuthenticationFilter.java b/src/main/java/doldol_server/doldol/auth/filter/JwtAuthenticationFilter.java index f915c33a..cc29896e 100644 --- a/src/main/java/doldol_server/doldol/auth/filter/JwtAuthenticationFilter.java +++ b/src/main/java/doldol_server/doldol/auth/filter/JwtAuthenticationFilter.java @@ -21,7 +21,9 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +@Slf4j @RequiredArgsConstructor public class JwtAuthenticationFilter extends OncePerRequestFilter { @@ -44,18 +46,22 @@ protected void doFilterInternal( SecurityContextHolder.getContext().setAuthentication(authentication); } } catch (ExpiredJwtException e) { + log.warn("만료된 토큰으로 접근 시도: 토큰 만료"); SecurityContextHolder.clearContext(); ResponseUtil.writeErrorResponse(response, objectMapper, AuthErrorCode.TOKEN_EXPIRED); return; } catch (IncorrectClaimException e) { + log.warn("잘못된 클레임 토큰으로 접근 시도: {}", e.getMessage()); SecurityContextHolder.clearContext(); ResponseUtil.writeErrorResponse(response, objectMapper, AuthErrorCode.INCORRECT_CLAIM_TOKEN); return; } catch (UsernameNotFoundException e) { + log.warn("존재하지 않는 사용자 토큰으로 접근 시도: {}", e.getMessage()); SecurityContextHolder.clearContext(); ResponseUtil.writeErrorResponse(response, objectMapper, AuthErrorCode.USER_NOT_FOUND); return; } catch (Exception e) { + log.warn("유효하지 않은 토큰으로 접근 시도: {}", e.getMessage()); SecurityContextHolder.clearContext(); ResponseUtil.writeErrorResponse(response, objectMapper, AuthErrorCode.INVALID_TOKEN); return; diff --git a/src/main/java/doldol_server/doldol/auth/handler/CustomOAuth2FailureHandler.java b/src/main/java/doldol_server/doldol/auth/handler/CustomOAuth2FailureHandler.java index c7d46518..5dac2fc5 100644 --- a/src/main/java/doldol_server/doldol/auth/handler/CustomOAuth2FailureHandler.java +++ b/src/main/java/doldol_server/doldol/auth/handler/CustomOAuth2FailureHandler.java @@ -10,7 +10,9 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +@Slf4j @Component @RequiredArgsConstructor public class CustomOAuth2FailureHandler extends SimpleUrlAuthenticationFailureHandler { @@ -22,6 +24,8 @@ public class CustomOAuth2FailureHandler extends SimpleUrlAuthenticationFailureHa public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException { + log.warn("소셜 로그인 실패: 오류={}", exception.getMessage()); + response.sendRedirect(frontendUrl); } } diff --git a/src/main/java/doldol_server/doldol/auth/handler/CustomOAuth2SuccessHandler.java b/src/main/java/doldol_server/doldol/auth/handler/CustomOAuth2SuccessHandler.java index 014c7fb0..03d56e15 100644 --- a/src/main/java/doldol_server/doldol/auth/handler/CustomOAuth2SuccessHandler.java +++ b/src/main/java/doldol_server/doldol/auth/handler/CustomOAuth2SuccessHandler.java @@ -17,7 +17,9 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +@Slf4j @Component @RequiredArgsConstructor public class CustomOAuth2SuccessHandler extends SimpleUrlAuthenticationSuccessHandler { @@ -60,6 +62,10 @@ private void handleNewSocialUser(HttpServletResponse response, String socialId, private void handleExistingUser(HttpServletResponse response, CustomUserDetails userDetails, String registrationId) throws IOException { + + log.info("소셜 로그인 성공: userId={}, socialType={}, role={}", + userDetails.getUserId(), registrationId.toUpperCase(), userDetails.getRole()); + String userid = String.valueOf(userDetails.getUserId()); UserTokenResponse loginToken = tokenProvider.createLoginToken(userid, userDetails.getRole()); diff --git a/src/main/java/doldol_server/doldol/auth/service/AuthService.java b/src/main/java/doldol_server/doldol/auth/service/AuthService.java index cf9adde2..79c9e1f1 100644 --- a/src/main/java/doldol_server/doldol/auth/service/AuthService.java +++ b/src/main/java/doldol_server/doldol/auth/service/AuthService.java @@ -24,7 +24,9 @@ import doldol_server.doldol.user.service.UserService; import io.jsonwebtoken.Claims; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +@Slf4j @Service @Transactional(readOnly = true) @RequiredArgsConstructor @@ -44,6 +46,7 @@ public void checkIdDuplicate(String id) { boolean isIdExists = userRepository.existsByLoginId(id); if (isIdExists) { + log.warn("아이디 중복 확인: 이미 존재하는 아이디={}", id); throw new CustomException(AuthErrorCode.ID_DUPLICATED); } } @@ -78,15 +81,18 @@ public void validateVerificationCode(String email, String code) { Object result = redisTemplate.opsForValue().get(email); if (result == null) { + log.warn("인증 코드 만료: email={}", email); throw new CustomException(AuthErrorCode.EMAIL_NOT_FOUND); } String verificationCode = result.toString(); if (!verificationCode.equals(code)) { + log.warn("인증 코드 불일치: email={}, 입력코드={}", email, code); throw new CustomException(AuthErrorCode.VERIFICATION_CODE_WRONG); } + log.info("이메일 인증 완료: email={}", email); redisTemplate.opsForValue().set(email, EMAIL_VERIFIED_KEY, 30, TimeUnit.MINUTES); } @@ -103,6 +109,9 @@ public void register(RegisterRequest registerRequest) { .build(); userRepository.save(user); + + log.info("자체 회원가입 완료: userId={}, email={}, name='{}'", + user.getId(), user.getEmail(), user.getName()); } @Transactional @@ -118,6 +127,9 @@ public void oauthRegister(OAuthRegisterRequest oAuthRegisterRequest) { .build(); userRepository.save(user); + + log.info("소셜 회원가입 완료: userId={}, email={}, socialType={}", + user.getId(), user.getEmail(), user.getSocialType()); } @Transactional @@ -139,6 +151,8 @@ public ReissueTokenResponse reissue(String refreshToken) { UserTokenResponse newTokens = tokenProvider.createLoginToken(userId, user.getRole().getRole()); + log.info("토큰 재발급 완료: userId={}", userId); + return ReissueTokenResponse .builder() .accessToken(newTokens.accessToken()) @@ -155,14 +169,22 @@ public void withdraw(Long userId) { } if (user.getSocialId() != null) { - OAuth2ResponseStrategy strategy = oAuthSeperator.getStrategy(user.getSocialType().name()); - strategy.unlink(user.getSocialId()); - user.deleteOAuthInfo(); + try { + OAuth2ResponseStrategy strategy = oAuthSeperator.getStrategy(user.getSocialType().name()); + strategy.unlink(user.getSocialId()); + user.deleteOAuthInfo(); + } catch (Exception e) { + log.error("소셜 계정 연동 해제 실패: userId={}, socialType={}, 오류={}", + userId, user.getSocialType(), e.getMessage(), e); + } } user.updateDeleteStatus(); tokenProvider.deleteRefreshToken(String.valueOf(userId)); + + log.info("소셜 계정 연동 해제: userId={}, socialType={}", + userId, user.getSocialType()); } public void validateUserInfo(String name, String email, String phone) { @@ -205,6 +227,8 @@ public void resetPassword(String email) { user.updateUserPassword(passwordEncoder.encode(tempPassword)); emailService.sendEmailTempPassword(email, tempPassword); + + log.info("비밀번호 재설정 완료: email={}, userId={}", email, user.getId()); } public void checkRegisterInfoDuplicate(String email, String phone) { @@ -212,10 +236,13 @@ public void checkRegisterInfoDuplicate(String email, String phone) { boolean existsByPhone = userRepository.existsByPhone(phone); if (existsByEmail && !existsByPhone) { + log.warn("이메일 중복: email={}", email); throw new CustomException(AuthErrorCode.EMAIl_DUPLICATED); } else if (!existsByEmail && existsByPhone) { + log.warn("전화번호 중복: phone={}", phone); throw new CustomException(AuthErrorCode.PHONE_DUPLICATED); } else if (existsByEmail && existsByPhone) { + log.warn("이메일/전화번호 모두 중복: email={}, phone={}", email, phone); throw new CustomException(AuthErrorCode.EMAIL_PHONE_DUPLICATED); } } diff --git a/src/main/java/doldol_server/doldol/auth/service/CustomOAuth2UserService.java b/src/main/java/doldol_server/doldol/auth/service/CustomOAuth2UserService.java index 1e997edd..2dd10064 100644 --- a/src/main/java/doldol_server/doldol/auth/service/CustomOAuth2UserService.java +++ b/src/main/java/doldol_server/doldol/auth/service/CustomOAuth2UserService.java @@ -20,7 +20,9 @@ import doldol_server.doldol.user.repository.UserRepository; import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +@Slf4j @Service @RequiredArgsConstructor public class CustomOAuth2UserService extends DefaultOAuth2UserService { @@ -51,6 +53,8 @@ public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2Authentic } else if (socialLinkedUser.isPresent() && socialLinkedUser.get().isDeleted()) { + log.warn("탈퇴한 계정으로 소셜 로그인 시도: socialId={}, socialType={}", + oAuth2Response.getSocialId(), registrationId); throw new CustomOAuth2Exception(AuthErrorCode.ALREADY_WITHDRAWN); } diff --git a/src/main/java/doldol_server/doldol/auth/service/EmailService.java b/src/main/java/doldol_server/doldol/auth/service/EmailService.java index 3cb8d8ca..429e75c7 100644 --- a/src/main/java/doldol_server/doldol/auth/service/EmailService.java +++ b/src/main/java/doldol_server/doldol/auth/service/EmailService.java @@ -14,7 +14,9 @@ import jakarta.mail.MessagingException; import jakarta.mail.internet.MimeMessage; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +@Slf4j @Service @RequiredArgsConstructor public class EmailService { @@ -29,7 +31,9 @@ public void sendEmailVerificationCode(String email, String verificationCode) { } try { sendToEmailCode(email, verificationCode); + log.info("인증 코드 이메일 발송 완료: email={}", email); } catch (MessagingException e) { + log.error("인증 코드 이메일 발송 실패: email={}, 오류={}", email, e.getMessage(), e); throw new CustomException(MailErrorCode.EMAIL_SENDING_ERROR); } } @@ -41,7 +45,9 @@ public void sendEmailTempPassword(String email, String password) { } try { sendToEmailTempPassword(email, password); + log.info("임시 비밀번호 이메일 발송 완료: email={}", email); } catch (MessagingException e) { + log.error("임시 비밀번호 이메일 발송 실패: email={}, 오류={}", email, e.getMessage(), e); throw new CustomException(MailErrorCode.EMAIL_SENDING_ERROR); } } diff --git a/src/main/java/doldol_server/doldol/common/exception/GlobalExceptionHandler.java b/src/main/java/doldol_server/doldol/common/exception/GlobalExceptionHandler.java index c1bb5cdc..2b20e01e 100644 --- a/src/main/java/doldol_server/doldol/common/exception/GlobalExceptionHandler.java +++ b/src/main/java/doldol_server/doldol/common/exception/GlobalExceptionHandler.java @@ -17,7 +17,9 @@ import org.springframework.web.bind.MissingServletRequestParameterException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; +import lombok.extern.slf4j.Slf4j; +@Slf4j @RestControllerAdvice public class GlobalExceptionHandler { @@ -27,6 +29,8 @@ public class GlobalExceptionHandler { @ExceptionHandler(value = CustomException.class) public ResponseEntity> handleCustomException(CustomException e) { ErrorCode errorCode = e.getErrorCode(); + log.warn("커스텀 예외 발생: 코드={}, 메시지={}", errorCode.getCode(), e.getMessage()); + return ResponseEntity.status(errorCode.getHttpStatus()) .body(ErrorResponse.error(errorCode.getCode(), e.getMessage())); } @@ -44,6 +48,8 @@ protected ResponseEntity>> handleMethodArgumen errors.put(fieldError.getField(), fieldError.getDefaultMessage()); } + log.warn("유효성 검사 실패: 필드 오류={}", errors); + CommonErrorCode errorCode = CommonErrorCode.INVALID_VALUE; return ResponseEntity.status(errorCode.getHttpStatus()) .body(ErrorResponse.error(errors, errorCode.getCode(), errorCode.getMessage())); @@ -54,6 +60,8 @@ protected ResponseEntity>> handleMethodArgumen */ @ExceptionHandler(AuthenticationException.class) public ResponseEntity> handleAuthenticationException(AuthenticationException e) { + log.warn("인증 예외 발생: {}", e.getMessage()); + AuthErrorCode errorCode = AuthErrorCode.INVALID_TOKEN; return ResponseEntity.status(errorCode.getHttpStatus()) .body(ErrorResponse.error(errorCode.getCode(), errorCode.getMessage())); @@ -64,6 +72,8 @@ public ResponseEntity> handleAuthenticationException(Authent */ @ExceptionHandler(AccessDeniedException.class) public ResponseEntity> handleAccessDeniedException(AccessDeniedException e) { + log.warn("접근 권한 없음: {}", e.getMessage()); + AuthErrorCode errorCode = AuthErrorCode.ACCESS_DENIED; return ResponseEntity.status(errorCode.getHttpStatus()) .body(ErrorResponse.error(errorCode.getCode(), errorCode.getMessage())); @@ -74,6 +84,8 @@ public ResponseEntity> handleAccessDeniedException(AccessDen */ @ExceptionHandler(IllegalArgumentException.class) public ResponseEntity> handleIllegalArgumentException(IllegalArgumentException e) { + log.warn("잘못된 인자: {}", e.getMessage()); + CommonErrorCode errorCode = CommonErrorCode.INVALID_ARGUMENT; return ResponseEntity.status(errorCode.getHttpStatus()) .body(ErrorResponse.error(errorCode.getCode(), errorCode.getMessage())); @@ -84,6 +96,8 @@ public ResponseEntity> handleIllegalArgumentException(Illega */ @ExceptionHandler(RuntimeException.class) public ResponseEntity> handleRuntimeException(RuntimeException e) { + log.error("런타임 예외 발생: {}", e.getMessage(), e); + CommonErrorCode errorCode = CommonErrorCode.RUNTIME_ERROR; return ResponseEntity.status(errorCode.getHttpStatus()) .body(ErrorResponse.error(errorCode.getCode(), errorCode.getMessage())); @@ -94,6 +108,8 @@ public ResponseEntity> handleRuntimeException(RuntimeExcepti */ @ExceptionHandler(Exception.class) public ResponseEntity> handleGeneralException(Exception e) { + log.error("예상치 못한 예외 발생: {}", e.getMessage(), e); + CommonErrorCode errorCode = CommonErrorCode.INTERNAL_SERVER_ERROR; return ResponseEntity.status(errorCode.getHttpStatus()) .body(ErrorResponse.error(errorCode.getCode(), errorCode.getMessage())); @@ -105,8 +121,21 @@ public ResponseEntity> handleGeneralException(Exception e) { @ExceptionHandler(MissingServletRequestParameterException.class) public ResponseEntity> handleMissingServletRequestParameterException( MissingServletRequestParameterException e) { + log.warn("필수 파라미터 누락: 파라미터={}, 타입={}", e.getParameterName(), e.getParameterType()); + CommonErrorCode errorCode = CommonErrorCode.MISSING_PARAMETER; return ResponseEntity.status(errorCode.getHttpStatus()) .body(ErrorResponse.error(errorCode.getCode(), errorCode.getMessage())); } -} + + /** + * OAuth2 연동 해제 예외 + */ + @ExceptionHandler(OAuth2UnlinkException.class) + public ResponseEntity> handleOAuth2UnlinkException(OAuth2UnlinkException e) { + log.error("OAuth2 연동 해제 실패: 코드={}, 메시지={}", e.getErrorCode().getCode(), e.getMessage(), e); + + return ResponseEntity.status(e.getErrorCode().getHttpStatus()) + .body(ErrorResponse.error(e.getErrorCode().getCode(), e.getMessage())); + } +} \ No newline at end of file diff --git a/src/main/java/doldol_server/doldol/rollingPaper/service/MessageService.java b/src/main/java/doldol_server/doldol/rollingPaper/service/MessageService.java index 31fe73c9..a817c8b1 100644 --- a/src/main/java/doldol_server/doldol/rollingPaper/service/MessageService.java +++ b/src/main/java/doldol_server/doldol/rollingPaper/service/MessageService.java @@ -25,7 +25,9 @@ import doldol_server.doldol.user.entity.User; import doldol_server.doldol.user.service.UserService; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +@Slf4j @Service @Transactional(readOnly = true) @RequiredArgsConstructor @@ -40,6 +42,7 @@ public MessageResponse getMessage(Long messageId, Long userId) { MessageResponse message = messageRepository.getMessage(messageId, userId); if (message == null) { + log.warn("존재하지 않는 메시지 조회 시도: messageId={}, userId={}", messageId, userId); throw new CustomException(MessageErrorCode.MESSAGE_NOT_FOUND); } @@ -102,6 +105,10 @@ public void createMessage(CreateMessageRequest request, Long userId) { .build(); messageRepository.save(message); + + log.info("메시지 작성 완료: messageId={}, paperId={}, 발신자={}, 수신자={}, 글자수={}", + message.getId(), paper.getId(), userId, request.receiverId(), + request.content().length()); } @Transactional @@ -113,6 +120,7 @@ public void updateMessage(UpdateMessageRequest request, Long userId) { } conditionalUpdate(request, message); + log.info("메시지 수정 완료: messageId={}, 수정자={}", request.messageId(), userId); } @Transactional @@ -125,6 +133,8 @@ public void deleteMessage(DeleteMessageRequest request, Long userId) { } message.updateDeleteStatus(); + + log.info("메시지 삭제 완료: messageId={}, 삭제자={}", request.messageId(), userId); } private Long getReceivedMessageCounts(Long paperId, Long userId) { @@ -151,14 +161,22 @@ private void conditionalUpdate(UpdateMessageRequest request, Message message) { } private MessageResponse decryptMessageContent(MessageResponse messageResponse) { - String decryptedContent = encryptor.decrypt(messageResponse.content()); - return messageResponse.withDecryptedContent(decryptedContent); + try { + String decryptedContent = encryptor.decrypt(messageResponse.content()); + return messageResponse.withDecryptedContent(decryptedContent); + } catch (Exception e) { + log.error("메시지 복호화 실패: messageId={}, 오류={}", + messageResponse.messageId(), e.getMessage(), e); + return messageResponse.withDecryptedContent("메시지 내용을 불러올 수 없습니다."); + } } private void validateMessageCount(Paper paper, User fromUser, User toUser) { long messageCount = messageRepository.countByPaperAndFromAndToAndIsDeletedFalse( paper, fromUser, toUser); if (messageCount >= 5) { + log.warn("메시지 작성 제한 도달: paperId={}, 발신자={}, 수신자={}, 현재개수={}", + paper.getId(), fromUser.getId(), toUser.getId(), messageCount); throw new CustomException(MessageErrorCode.MESSAGE_LIMIT_EXCEEDED); } } diff --git a/src/main/java/doldol_server/doldol/rollingPaper/service/PaperService.java b/src/main/java/doldol_server/doldol/rollingPaper/service/PaperService.java index f56602e1..cadd6427 100644 --- a/src/main/java/doldol_server/doldol/rollingPaper/service/PaperService.java +++ b/src/main/java/doldol_server/doldol/rollingPaper/service/PaperService.java @@ -22,7 +22,9 @@ import doldol_server.doldol.rollingPaper.entity.Participant; import doldol_server.doldol.rollingPaper.repository.PaperRepository; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +@Slf4j @Service @Transactional(readOnly = true) @RequiredArgsConstructor @@ -44,6 +46,9 @@ public CreatePaperResponse createPaper(PaperRequest request, Long userId) { participantService.addUser(userId, paper, true); + log.info("롤링페이퍼 생성 완료: paperId={}, 제목='{}', 생성자={}, 공개날짜={}", + paper.getId(), paper.getName(), userId, paper.getOpenDate()); + return CreatePaperResponse.of(paper); } @@ -59,6 +64,9 @@ public void joinPaper(JoinPaperRequest request, Long userId) { participantService.validateJoinable(userId, paper.getId()); participantService.addUser(userId, paper, false); + + log.info("롤링페이퍼 참여 완료: paperId={}, 참여자={}, 제목='{}'", + paper.getId(), userId, paper.getName()); } public PaperListResponse getMyRollingPapers(CursorPageRequest request, @@ -83,6 +91,9 @@ private String createInvitationCode() { private Paper getByInvitationCode(String invitationCode) { return paperRepository.findByInvitationCode(invitationCode) - .orElseThrow(() -> new CustomException(PAPER_NOT_FOUND)); + .orElseThrow(() -> { + log.warn("유효하지 않은 초대 코드로 접근: invitationCode={}", invitationCode); + return new CustomException(PAPER_NOT_FOUND); + }); } } diff --git a/src/main/java/doldol_server/doldol/rollingPaper/service/ParticipantService.java b/src/main/java/doldol_server/doldol/rollingPaper/service/ParticipantService.java index 8d5debe5..a72b4aa7 100644 --- a/src/main/java/doldol_server/doldol/rollingPaper/service/ParticipantService.java +++ b/src/main/java/doldol_server/doldol/rollingPaper/service/ParticipantService.java @@ -38,6 +38,9 @@ public void addUser(Long userId, Paper paper, boolean isMaster) { paper.addParticipant(); participantRepository.save(participant); + + log.info("참여자 추가 완료: paperId={}, userId={}, 마스터여부={}", + paper.getId(), userId, isMaster); } public CursorPage getParticipants(Long paperId, @@ -75,12 +78,14 @@ public void validateJoinable(Long userId, Long paperId) { private void alreadyExistUser(Long userId, List participants) { if (participants.stream().anyMatch(participant -> participant.getUser().getId().equals(userId))) { + log.warn("이미 참여 중인 사용자의 중복 참여 시도: userId={}", userId); throw new CustomException(PARTICIPANT_ALREADY_EXIST); } } private void existPaper(List participants) { if (participants.isEmpty()) { + log.warn("존재하지 않는 롤링페이퍼 접근 시도"); throw new CustomException(PAPER_NOT_FOUND); } } diff --git a/src/main/java/doldol_server/doldol/user/service/UserService.java b/src/main/java/doldol_server/doldol/user/service/UserService.java index 436deb05..4b919480 100644 --- a/src/main/java/doldol_server/doldol/user/service/UserService.java +++ b/src/main/java/doldol_server/doldol/user/service/UserService.java @@ -11,7 +11,9 @@ import doldol_server.doldol.user.entity.User; import doldol_server.doldol.user.repository.UserRepository; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +@Slf4j @Service @Transactional(readOnly = true) @RequiredArgsConstructor @@ -21,17 +23,37 @@ public class UserService { private final PasswordEncoder passwordEncoder; public User getById(Long userId) { - return userRepository.findById(userId).orElseThrow(() -> new CustomException(UserErrorCode.USER_NOT_FOUND)); + return userRepository.findById(userId) + .orElseThrow(() -> { + log.warn("존재하지 않는 사용자 조회 시도: userId={}", userId); + return new CustomException(UserErrorCode.USER_NOT_FOUND); + }); } @Transactional public void changeInfo(UpdateUserInfoRequest request, Long userId) { User user = getById(userId); + + boolean nameUpdated = false; + boolean passwordUpdated = false; + if (request.name() != null) { user.updateUserName(request.name()); + nameUpdated = true; } if (request.password() != null) { user.updateUserPassword(passwordEncoder.encode(request.password())); + passwordUpdated = true; + } + + if (nameUpdated && passwordUpdated) { + log.info("사용자 정보 수정 완료: userId={}, 수정항목=이름+비밀번호", userId); + } else if (nameUpdated) { + log.info("사용자 정보 수정 완료: userId={}, 수정항목=이름", userId); + } else if (passwordUpdated) { + log.info("사용자 정보 수정 완료: userId={}, 수정항목=비밀번호", userId); + } else { + log.warn("사용자 정보 수정 요청했으나 변경사항 없음: userId={}", userId); } } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 9b0ac782..2ee95942 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -8,4 +8,17 @@ spring: format_sql: true highlight_sql: true application: - name: doldol \ No newline at end of file + name: doldol + +logging: + level: + org.hibernate.SQL: info + doldol_server.doldol: info + slack: + dev: + webhook-uri: "" + prod: + warn: + webhook-uri: "" + error: + webhook-uri: "" \ No newline at end of file diff --git a/src/main/resources/config b/src/main/resources/config index ceceb3e4..e1c2eeff 160000 --- a/src/main/resources/config +++ b/src/main/resources/config @@ -1 +1 @@ -Subproject commit ceceb3e4713ac9eb9b0c8ca1693a22eb293dfaa9 +Subproject commit e1c2eeff1026c9be876775d29201c509a60ae8b0 diff --git a/src/main/resources/console-appender.xml b/src/main/resources/console-appender.xml new file mode 100644 index 00000000..18b32381 --- /dev/null +++ b/src/main/resources/console-appender.xml @@ -0,0 +1,7 @@ + + + + ${LOG_PATTERN} + + + \ No newline at end of file diff --git a/src/main/resources/file-error-appender.xml b/src/main/resources/file-error-appender.xml new file mode 100644 index 00000000..c638e6ff --- /dev/null +++ b/src/main/resources/file-error-appender.xml @@ -0,0 +1,14 @@ + + + /app/logs/error/error-${BY_DATE}.log + true + + ERROR + ACCEPT + DENY + + + ${LOG_PATTERN} + + + \ No newline at end of file diff --git a/src/main/resources/file-info-appender.xml b/src/main/resources/file-info-appender.xml new file mode 100644 index 00000000..1c6a90f9 --- /dev/null +++ b/src/main/resources/file-info-appender.xml @@ -0,0 +1,14 @@ + + + /app/logs/info/info-${BY_DATE}.log + true + + INFO + ACCEPT + DENY + + + ${LOG_PATTERN} + + + \ No newline at end of file diff --git a/src/main/resources/logback-spring.xml b/src/main/resources/logback-spring.xml new file mode 100644 index 00000000..530d03db --- /dev/null +++ b/src/main/resources/logback-spring.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/slack-dev-appender.xml b/src/main/resources/slack-dev-appender.xml new file mode 100644 index 00000000..de6dca20 --- /dev/null +++ b/src/main/resources/slack-dev-appender.xml @@ -0,0 +1,25 @@ + + + ${SLACK_DEV_WEBHOOK_URI} + #be-dev-log + doldol-dev + :hammer_and_wrench: + true + + + [%level] %d{HH:mm:ss} %logger{20} - %msg + + + false + + + + + + INFO + + 0 + 512 + false + + \ No newline at end of file diff --git a/src/main/resources/slack-prod-appender.xml b/src/main/resources/slack-prod-appender.xml new file mode 100644 index 00000000..ea8e5896 --- /dev/null +++ b/src/main/resources/slack-prod-appender.xml @@ -0,0 +1,60 @@ + + + + ${SLACK_PROD_WARN_WEBHOOK_URI} + #be-prod-warn-log + doldol-prod-warn + :warning: + true + + + WARNING %d{HH:mm:ss} %logger{20} - %msg + + + false + + + + + ${SLACK_PROD_ERROR_WEBHOOK_URI} + #be-prod-error-log + doldol-prod-error + :rotating_light: + true + + + CRITICAL ERROR - Server: doldol-prod - Time: %d{yyyy-MM-dd HH:mm:ss} - Thread: %thread - Logger: %logger{36} - Message: %msg + + + true + Production Error - Immediate Action Required + danger + PRODUCTION ERROR ALERT + + + + + + + WARN + ACCEPT + DENY + + 20 + 256 + false + + + + + + + ERROR + ACCEPT + DENY + + 0 + 128 + true + + \ No newline at end of file