Skip to content

Conversation

@sjk4618
Copy link
Member

@sjk4618 sjk4618 commented Oct 9, 2025

🔥Pull requests

⛳️ 작업한 브랜치

👷 작업한 내용

[ dev ]

  • http req, res의 정보들을 디스코드에서 확인할 수 있도록 웹훅 연결함
    • filter단에서 http 정보들을 가져와서 비동기로 디스코드로 정보들을 보냄
    • 간단한 비동기이기 때문에 @async 사용하여 구현
  • dev에서는 따로 로그 파일을 저장하지 않고, Loki로 로그를 가져와 grafana에서 볼 수 있도록 함

[ prod ]

  • info, error 로그 파일 두개로 나눠서 ec2에 저장

🚨 참고 사항

[ todo ] - 변경 가능성 있음

  • dev에서 error 로그들은 비동기로 디스코드로 보낼 수 있도록 확장 (이렇게 되면 Loki로 수집해서 그라파나에 안보내도됨, 그라파나가 하지만 검색 및 UI/UX가 더 좋아서 그라파나로 공유가능하면 그라파나 그대로 사용할 예정)
  • prod에서도 error 로그들은 비동기로 디스코드에 보낼 수 있도록 확장 + Loki로는 info 이상 로그들 수집해서 그라파나에서 조회 가능하도록(이런 작업들이 얼마나 메모리나 성능을 저하시키는지 확인해봐야함)

@sjk4618 sjk4618 self-assigned this Oct 9, 2025
@coderabbitai
Copy link

coderabbitai bot commented Oct 9, 2025

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • 개발 환경에서 Discord로 HTTP 로그 전송 지원
    • 개발용 Loki 연동 및 프로덕션 파일 로깅 구성 추가
  • Refactor

    • 날짜/시간 입력 포맷을 “yyyy-MM-dd HH:mm”로 통일
    • 로깅 필터 프로파일 조정 및 유틸 클래스 구조 정리
  • Chores

    • AWS SDK S3 의존성 업그레이드
    • Loki 로그백 앱렌더 추가
    • Feign 재시도 설정(백오프·최대 3회) 도입
    • .gitignore 규칙 업데이트

Walkthrough

dev/prod 로그 구성 재편, 개발환경의 HTTP 요청·응답을 Discord 웹훅으로 비동기 전송하도록 추가, Feign 재시도 및 비동기 Executor(MDC 전파) 도입, 날짜 포맷 하이픈으로 통일, Lombok 유틸 클래스 정리, 빌드·의존성 및 .gitignore 조정.

Changes

Cohort / File(s) Summary
Repo / Build config
.gitignore, build.gradle
.gitignore 환경별 application-*.yml 규칙 조정(application.yml 재포함). build.gradlecom.github.loki4j:loki-logback-appender:1.5.2 추가, AWS SDK BOM/S3 버전 2.21.0 → 2.34.0 및 의존성 문법 변경.
Date/time 포맷 변경
src/main/java/.../ticket/.../TicketRoundWithTypeCreateRequest.java, src/main/java/.../ticket/.../TicketRoundWithTypeUpdateRequest.java, src/main/java/.../global/util/DateFormatterUtil.java
Jackson @JsonFormat 및 DateFormatter 패턴을 "yyyy.MM.dd HH:mm"/"yyyy.MM.dd""yyyy-MM-dd HH:mm"/"yyyy-MM-dd"로 변경.
Lombok 유틸 전환
src/main/java/.../global/util/LogFormUtil.java, src/main/java/.../global/util/PriceFormatterUtil.java, src/main/java/.../global/util/TimeFormatterUtil.java
@NoArgsConstructor(access = PRIVATE)@UtilityClass 적용(유틸 클래스화).
Async 및 Feign 설정
src/main/java/.../global/config/AsyncConfig.java, src/main/java/.../global/config/FeignConfig.java
alertExecutor 빈 추가(ThreadPoolTaskExecutor, MDC 전파 TaskDecorator). Feign Retryer 빈 추가(초기 100ms, max 1s, max 3회).
Discord 연동 컴포넌트
src/main/java/.../global/external/discord/DiscordSender.java, .../DiscordDevHttpLogSender.java, .../DiscordFeignClient.java, .../discord/dto/DiscordMessage.java, .../discord/util/DiscordMessageFormatterUtil.java
Discord 전송 추상화 인터페이스, Feign 클라이언트, 메시지 DTO, Markdown 포매터, 비동기 전송 구현(@Async("alertExecutor")) 추가.
HTTP 로깅 변경
src/main/java/.../global/filter/DevRequestResponseLoggingFilter.java, src/main/java/.../global/util/HttpReqResLogJsonBuilder.java, src/main/java/.../global/filter/MDCLoggingFilter.java
개발용 필터가 요청/응답을 JSON으로 빌드해 DiscordSender로 전송하도록 흐름 변경. 본문 추출·절단 로직 추가(HttpReqResLogJsonBuilder). MDCLoggingFilter@Profile("!local") 추가.
Logback 구성 개편
src/main/resources/logback-spring.xml, src/main/resources/logback/logback-dev-info.xml, .../logback-dev-error.xml, .../logback-dev-loki.xml, .../logback-prod-file.xml
dev: 기존 파일 기반 dev appenders 제거 및 Loki 전송 구성 추가(logback-dev-loki.xml). prod: 파일 롤링 기반 JSON 로그 구성 추가(logback-prod-file.xml). logback-spring.xml에서 프로필별 include 재구성.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Client as 클라이언트
  participant App as PermitServer (dev)
  participant Filter as DevRequestResponseLoggingFilter
  participant Builder as HttpReqResLogJsonBuilder
  participant Sender as DiscordSender
  participant Executor as alertExecutor (Async)
  participant Webhook as DiscordFeignClient

  Client->>App: HTTP 요청
  App->>Filter: doFilter(request,response,chain)
  Filter->>App: chain.doFilter(...)
  App-->>Filter: 응답 완료
  Filter->>Builder: buildJsonLog(req,res,duration)
  Builder-->>Filter: JSON 로그 문자열
  Filter->>Sender: send(json)  -- 비동기 호출
  Sender-)Executor: enqueue task (MDC 복원)
  Executor->>Webhook: POST /webhook {content: formatted message}
  Webhook-->>Executor: 응답 (Feign Retryer 적용)
  Executor-->>Sender: 완료
  Filter-->>Client: HTTP 응답(본문 복원)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Out of Scope Changes Check ⚠️ Warning PR에는 티켓 DTO의 날짜 포맷 변경과 AWS SDK 버전 업그레이드 등 이슈 #153의 로깅 및 웹훅 구현 범위를 벗어난 코드 변경이 포함되어 있습니다. 로깅 관련 변경만 포함하도록 티켓 DTO 포맷 수정 및 AWS SDK 버전 업데이트를 별도 PR로 분리하는 것을 권장합니다.
✅ Passed checks (4 passed)
Check name Status Explanation
Title Check ✅ Passed 제목은 로그 파일 관리와 디스코드 웹훅 연동이라는 PR의 핵심 변경사항을 명확히 요약하여 이 변경이 무엇을 다루는지 쉽게 알 수 있도록 작성되었습니다.
Linked Issues Check ✅ Passed PR에서 dev 환경의 HTTP 요청·응답 디스코드 웹훅 연동과 prod 환경의 파일 로그 분리 및 Loki/Grafana 수집 구성이 모두 구현되어 있어 이슈 #153의 주요 요구사항을 충족합니다.
Description Check ✅ Passed 설명은 dev와 prod 환경에서의 로그 처리 방식과 디스코드 연동 구현을 구체적으로 다루고 있어 PR 변경사항과 직접 연관되어 있습니다.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/#153

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

♻️ Duplicate comments (1)
src/main/java/com/permitseoul/permitserver/domain/admin/ticket/api/dto/req/TicketRoundWithTypeUpdateRequest.java (1)

20-20: 생성 요청과 일관된 날짜 형식 변경입니다.

TicketRoundWithTypeCreateRequest와 동일한 형식 변경이 적용되어 create/update API 간 일관성이 유지됩니다.

단, 이는 API 호환성을 깨는 변경이므로 클라이언트 업데이트가 필요합니다 (이미 TicketRoundWithTypeCreateRequest.java에서 플래그됨).

Also applies to: 24-24

🧹 Nitpick comments (14)
src/main/java/com/permitseoul/permitserver/global/util/TimeFormatterUtil.java (1)

5-7: @UtilityClass 적용 승인, 사용하지 않는 import 제거 권장

Lombok의 @UtilityClass로 전환하여 유틸리티 클래스 의미를 명시적으로 표현한 것은 좋은 개선입니다.

다만 5-6번째 줄의 AccessLevelNoArgsConstructor import는 더 이상 사용되지 않으므로 제거하는 것이 좋습니다:

-import lombok.AccessLevel;
-import lombok.NoArgsConstructor;
 import lombok.experimental.UtilityClass;
src/main/java/com/permitseoul/permitserver/global/util/LogFormUtil.java (1)

4-6: @UtilityClass 적용 승인, 사용하지 않는 import 제거 권장

다른 유틸리티 클래스들과 일관되게 @UtilityClass로 전환한 것은 좋은 개선입니다.

4-5번째 줄의 사용되지 않는 import를 제거하세요:

-import lombok.AccessLevel;
-import lombok.NoArgsConstructor;
 import lombok.experimental.UtilityClass;
src/main/java/com/permitseoul/permitserver/global/util/PriceFormatterUtil.java (1)

4-6: @UtilityClass 적용 승인, 사용하지 않는 import 제거 권장

프로젝트 전반에 걸쳐 유틸리티 클래스를 @UtilityClass로 일관되게 전환하는 것은 좋은 패턴입니다.

4-5번째 줄의 사용되지 않는 import를 제거하세요:

-import lombok.AccessLevel;
-import lombok.NoArgsConstructor;
 import lombok.experimental.UtilityClass;
src/main/java/com/permitseoul/permitserver/global/util/DateFormatterUtil.java (1)

6-8: @UtilityClass 적용 승인, 사용하지 않는 import 제거 권장

다른 유틸리티 클래스들과 일관되게 @UtilityClass로 전환한 것은 좋습니다.

6-7번째 줄의 사용되지 않는 import를 제거하세요:

-import lombok.AccessLevel;
-import lombok.NoArgsConstructor;
 import lombok.experimental.UtilityClass;
src/main/resources/logback/logback-prod-file.xml (2)

4-4: 로그 파일 경로 설정 검토 필요

하드코딩된 절대 경로 /home/ubuntu/logs/가 사용되고 있습니다. 배포 환경이 변경될 경우 문제가 될 수 있으므로, 환경 변수나 Spring 속성을 통해 경로를 설정하는 것을 권장합니다.

다음과 같이 환경 변수를 사용하도록 수정할 수 있습니다:

-        <file>/home/ubuntu/logs/PERMIT-INFO-LOG.log</file>
+        <file>${LOG_PATH:-/home/ubuntu/logs}/PERMIT-INFO-LOG.log</file>

동일하게 line 24, 32, 65에도 적용해주세요.


76-76: discardingThreshold 설정 검토

discardingThreshold=0으로 설정하면 큐가 가득 찰 때 로그가 버려지지 않고 대기하게 됩니다. 이는 로그 유실을 방지하지만, 높은 부하 시 애플리케이션 스레드가 블로킹될 수 있습니다.

성능과 로그 완전성 간의 트레이드오프를 고려하여 적절한 값(예: 20% = queueSize * 0.2)을 설정하는 것을 권장합니다.

src/main/resources/logback/logback-dev-loki.xml (1)

11-11: HOSTNAME 변수 확인

${HOSTNAME} 환경 변수가 설정되어 있지 않으면 로그에 빈 값이 포함될 수 있습니다. 기본값을 설정하거나 시스템 속성을 사용하는 것을 권장합니다.

다음과 같이 기본값을 추가할 수 있습니다:

-                <pattern>app="permitseoul-dev", level="%level", logger="%logger{20}", host="${HOSTNAME}"</pattern>
+                <pattern>app="permitseoul-dev", level="%level", logger="%logger{20}", host="${HOSTNAME:-unknown}"</pattern>
src/main/resources/logback-spring.xml (1)

8-13: 일관성을 위한 리팩토링 제안

Local 프로파일은 inline root logger 설정을 유지하고 있지만, dev와 prod는 완전히 외부 파일로 위임하고 있습니다. 일관성을 위해 local도 logback-local.xml에서 root logger를 설정하고 이 파일에서는 include만 하는 것을 고려해보세요.

예시:

     <springProfile name="local">
         <include resource="logback/logback-local.xml"/>
-        <root level="INFO">
-            <appender-ref ref="LOCALCONSOLE"/>
-        </root>
     </springProfile>

그리고 logback-local.xml에 root logger 설정을 추가합니다.

src/main/java/com/permitseoul/permitserver/global/external/discord/dto/DiscordMessage.java (1)

4-8: 입력 검증 추가 고려

content 필드에 대한 null 또는 빈 문자열 검증이 없습니다. Discord API가 빈 메시지를 허용하는지 확인하고, 필요하다면 factory method에서 검증을 추가하는 것을 고려해보세요.

예시:

     public static DiscordMessage of(final String message) {
+        if (message == null || message.isBlank()) {
+            throw new IllegalArgumentException("Discord message content cannot be null or empty");
+        }
         return new DiscordMessage(message);
     }
build.gradle (1)

85-86: Loki4j 버전 업그레이드 권장: 현재 Stable은 2.0.0이며(Java 8 유지 불필요 시) 기능 개선 및 추가 버그 픽스를 위해 2.0.0으로 업데이트하세요. 1.5.2는 JSON nullable 문자열·동시 쓰기 시 JSON 손상 버그를 해결한 버전입니다.

src/main/java/com/permitseoul/permitserver/global/config/AsyncConfig.java (1)

16-24: 거부 정책(RejectionPolicy) 명시 권장.

큐 용량(20)과 최대 스레드 수(4)를 초과하는 작업이 제출되면 기본 거부 정책인 AbortPolicy가 작동하여 RejectedExecutionException이 발생합니다. Discord 알림이 급증하는 상황(예: 여러 요청이 동시에 실패)을 고려하면, 명시적으로 CallerRunsPolicy 또는 커스텀 정책을 설정하여 예외 발생을 방지하고 로그 누락을 줄이는 것이 좋습니다.

다음 diff를 적용하여 거부 정책을 명시하세요:

         executor.setThreadNamePrefix("DiscordAlert-");
         executor.setAllowCoreThreadTimeOut(true);
+        executor.setRejectedExecutionHandler(new java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy());
src/main/java/com/permitseoul/permitserver/global/external/discord/DiscordDevHttpLogSender.java (1)

21-30: 예외 처리 개선 고려.

현재 모든 예외를 catch하여 로그만 남기고 있습니다. @async 메서드이므로 예외가 호출자에게 전파되지 않는 것은 의도된 동작입니다. 다만, 다음을 고려하세요:

  1. JSON 파싱 실패(JsonProcessingException)와 Feign 호출 실패(FeignException)를 구분하여 처리하면 디버깅이 용이합니다.
  2. 메트릭이나 모니터링을 추가하여 Discord 전송 실패율을 추적하면 운영에 도움이 됩니다.

예외를 구분하여 처리하는 예시:

     @Async("alertExecutor")
     public void send(final String jsonMessage) {
         try {
             final JsonNode root = OBJECT_MAPPER.readTree(jsonMessage);
             final String content = DiscordMessageFormatterUtil.formatDevHttpLogToMarkdown(root);
             discordFeignClient.sendMessage(DiscordMessage.of(content));
+        } catch (com.fasterxml.jackson.core.JsonProcessingException e) {
+            log.error("🚨 [DiscordDevHttpLogSender] JSON 파싱 실패: {}", jsonMessage, e);
+        } catch (feign.FeignException e) {
+            log.error("🚨 [DiscordDevHttpLogSender] Discord API 호출 실패 (status: {})", e.status(), e);
         } catch (Exception e) {
             log.error("🚨 [DiscordDevHttpLogSender] Discord 전송 실패", e);
         }
     }
src/main/java/com/permitseoul/permitserver/global/util/HttpReqResLogJsonBuilder.java (1)

40-40: LocalDateTime 대신 타임존 명시 권장.

LocalDateTime.now()는 시스템의 기본 타임존을 사용하므로, 서버 환경에 따라 타임존이 다를 수 있습니다. 로그 시간을 명확하게 하려면 ZonedDateTime.now() 또는 Instant.now()를 사용하여 UTC 기준으로 기록하거나, 명시적으로 타임존을 지정하세요.

다음 중 하나를 적용하세요:

옵션 1: UTC 기준 사용 (권장)

-            logMap.put(FIELD_TIME, LocalDateTime.now().toString());
+            logMap.put(FIELD_TIME, java.time.Instant.now().toString());

옵션 2: Asia/Seoul 타임존 명시

+import java.time.ZonedDateTime;
+import java.time.ZoneId;
+
-            logMap.put(FIELD_TIME, LocalDateTime.now().toString());
+            logMap.put(FIELD_TIME, ZonedDateTime.now(ZoneId.of("Asia/Seoul")).toString());
src/main/java/com/permitseoul/permitserver/domain/admin/ticket/api/dto/req/TicketRoundWithTypeCreateRequest.java (1)

44-44: 중첩된 TicketTypeRequest의 날짜 형식도 일관되게 업데이트되었습니다.

외부 레코드와 동일한 형식 변경이 적용되어 일관성이 유지됩니다.

티켓 시스템의 특성상 타임존 정보가 중요할 수 있습니다. 현재 형식은 타임존 정보가 없어 서버와 클라이언트의 타임존 해석이 다를 경우 문제가 발생할 수 있습니다. 향후 ISO-8601 완전 형식(예: "yyyy-MM-dd'T'HH:mm:ssXXX")을 고려하거나, 최소한 모든 날짜를 UTC로 처리하는 규칙을 문서화하는 것을 권장합니다.

Also applies to: 48-48

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ca75f2b and 1c9479a.

📒 Files selected for processing (23)
  • .gitignore (1 hunks)
  • build.gradle (1 hunks)
  • src/main/java/com/permitseoul/permitserver/domain/admin/ticket/api/dto/req/TicketRoundWithTypeCreateRequest.java (2 hunks)
  • src/main/java/com/permitseoul/permitserver/domain/admin/ticket/api/dto/req/TicketRoundWithTypeUpdateRequest.java (2 hunks)
  • src/main/java/com/permitseoul/permitserver/global/config/AsyncConfig.java (1 hunks)
  • src/main/java/com/permitseoul/permitserver/global/config/FeignConfig.java (1 hunks)
  • src/main/java/com/permitseoul/permitserver/global/external/discord/DiscordDevHttpLogSender.java (1 hunks)
  • src/main/java/com/permitseoul/permitserver/global/external/discord/DiscordFeignClient.java (1 hunks)
  • src/main/java/com/permitseoul/permitserver/global/external/discord/DiscordSender.java (1 hunks)
  • src/main/java/com/permitseoul/permitserver/global/external/discord/dto/DiscordMessage.java (1 hunks)
  • src/main/java/com/permitseoul/permitserver/global/external/discord/util/DiscordMessageFormatterUtil.java (1 hunks)
  • src/main/java/com/permitseoul/permitserver/global/filter/DevRequestResponseLoggingFilter.java (1 hunks)
  • src/main/java/com/permitseoul/permitserver/global/filter/MDCLoggingFilter.java (2 hunks)
  • src/main/java/com/permitseoul/permitserver/global/util/DateFormatterUtil.java (2 hunks)
  • src/main/java/com/permitseoul/permitserver/global/util/HttpReqResLogJsonBuilder.java (1 hunks)
  • src/main/java/com/permitseoul/permitserver/global/util/LogFormUtil.java (1 hunks)
  • src/main/java/com/permitseoul/permitserver/global/util/PriceFormatterUtil.java (1 hunks)
  • src/main/java/com/permitseoul/permitserver/global/util/TimeFormatterUtil.java (1 hunks)
  • src/main/resources/logback-spring.xml (1 hunks)
  • src/main/resources/logback/logback-dev-error.xml (0 hunks)
  • src/main/resources/logback/logback-dev-info.xml (0 hunks)
  • src/main/resources/logback/logback-dev-loki.xml (1 hunks)
  • src/main/resources/logback/logback-prod-file.xml (1 hunks)
💤 Files with no reviewable changes (2)
  • src/main/resources/logback/logback-dev-error.xml
  • src/main/resources/logback/logback-dev-info.xml
👮 Files not reviewed due to content moderation or server errors (2)
  • src/main/java/com/permitseoul/permitserver/domain/admin/ticket/api/dto/req/TicketRoundWithTypeCreateRequest.java
  • src/main/java/com/permitseoul/permitserver/domain/admin/ticket/api/dto/req/TicketRoundWithTypeUpdateRequest.java
🧰 Additional context used
🧬 Code graph analysis (4)
src/main/java/com/permitseoul/permitserver/global/util/LogFormUtil.java (3)
src/main/java/com/permitseoul/permitserver/global/util/DateFormatterUtil.java (1)
  • UtilityClass (17-107)
src/main/java/com/permitseoul/permitserver/global/util/PriceFormatterUtil.java (1)
  • UtilityClass (12-41)
src/main/java/com/permitseoul/permitserver/global/util/TimeFormatterUtil.java (1)
  • UtilityClass (12-23)
src/main/java/com/permitseoul/permitserver/global/util/TimeFormatterUtil.java (3)
src/main/java/com/permitseoul/permitserver/global/util/DateFormatterUtil.java (1)
  • UtilityClass (17-107)
src/main/java/com/permitseoul/permitserver/global/util/LogFormUtil.java (1)
  • UtilityClass (13-43)
src/main/java/com/permitseoul/permitserver/global/util/PriceFormatterUtil.java (1)
  • UtilityClass (12-41)
src/main/java/com/permitseoul/permitserver/global/util/PriceFormatterUtil.java (3)
src/main/java/com/permitseoul/permitserver/global/util/DateFormatterUtil.java (1)
  • UtilityClass (17-107)
src/main/java/com/permitseoul/permitserver/global/util/LogFormUtil.java (1)
  • UtilityClass (13-43)
src/main/java/com/permitseoul/permitserver/global/util/TimeFormatterUtil.java (1)
  • UtilityClass (12-23)
src/main/java/com/permitseoul/permitserver/global/util/DateFormatterUtil.java (2)
src/main/java/com/permitseoul/permitserver/global/util/LogFormUtil.java (1)
  • UtilityClass (13-43)
src/main/java/com/permitseoul/permitserver/global/util/TimeFormatterUtil.java (1)
  • UtilityClass (12-23)
🔇 Additional comments (25)
.gitignore (1)

42-45: 환경별 설정 파일 무시 여부 재확인 필요

application*.yml.gitignore에 추가하면 이후에는 이 설정 파일들을 새로 버전 관리할 수 없습니다. 이번 PR에서 환경별 로그 설정을 위해 해당 파일들을 커밋·배포할 계획이라면 이 무시 규칙은 변경 내용이 반영되지 못하게 만듭니다. 의도가 맞는지 확인해 주세요.

src/main/java/com/permitseoul/permitserver/global/util/DateFormatterUtil.java (1)

24-24: 주석 업데이트 및 소비처 포맷 검증
DateFormatterUtil의 주석을 아래와 같이 수정하고, formatyyyyMMdd()를 사용하는 모든 위치(예: AdminEventService, AdminTicketService)에서 하이픈 형식이 올바르게 처리되는지 확인하세요.

-    private static final DateTimeFormatter DATE = DateTimeFormatter.ofPattern("yyyy-MM-dd"); // "2025.08.15" 포맷팅
+    private static final DateTimeFormatter DATE = DateTimeFormatter.ofPattern("yyyy-MM-dd"); // "2025-08-15" 포맷팅
src/main/java/com/permitseoul/permitserver/domain/admin/ticket/api/dto/req/TicketRoundWithTypeUpdateRequest.java (3)

20-20: 날짜 형식 변경이 create request와 일관되게 적용되었습니다.

TicketRoundWithTypeCreateRequest와 동일하게 날짜 형식이 "yyyy-MM-dd HH:mm"로 통일되어 일관성 있는 API 인터페이스를 제공합니다.

Also applies to: 24-24, 47-47, 51-51


47-47: 중첩된 TicketTypeUpdateRequest도 일관되게 업데이트되었습니다.

생성 요청의 TicketTypeRequest와 동일한 형식이 적용되어 전체적인 일관성이 확보되었습니다.

Also applies to: 51-51


20-25: 생성/수정 DTO 간 일관성이 유지되었습니다.

TicketRoundWithTypeCreateRequest와 동일한 날짜 형식 변경이 적용되어 생성 및 수정 작업 간 일관성이 보장됩니다. 단, 첫 번째 파일에서 언급한 API 호환성 확인 사항이 이 파일에도 동일하게 적용됩니다.

Also applies to: 47-52

src/main/resources/logback/logback-prod-file.xml (1)

85-88: 중복 로그 기록 확인

Root logger가 INFO 레벨로 설정되어 있고 ASYNC_INFOASYNC_ERROR 모두를 참조하므로, ERROR 레벨 로그가 두 파일에 모두 기록됩니다.

이것이 의도된 동작인지 확인해주세요. ERROR 로그를 ERROR_FILE에만 기록하려면 별도의 logger 설정이나 필터 조정이 필요합니다.

src/main/java/com/permitseoul/permitserver/global/config/FeignConfig.java (1)

14-18: LGTM!

Feign 재시도 설정이 적절합니다. 초기 100ms 지연에서 최대 1초까지 지수 백오프를 사용하고 최대 3회 재시도하는 설정은 일반적인 API 호출에 적합합니다.

src/main/java/com/permitseoul/permitserver/global/filter/MDCLoggingFilter.java (1)

22-22: LGTM!

로컬 환경에서 MDC 로깅 필터를 제외하는 것은 적절한 프로파일 분리 전략입니다. 개발 및 운영 환경에서만 trace_id 추적이 활성화됩니다.

src/main/java/com/permitseoul/permitserver/global/external/discord/DiscordSender.java (1)

1-5: LGTM!

Discord 메시지 전송을 위한 깔끔한 인터페이스 추상화입니다. 단일 책임 원칙을 따르며 테스트 가능성이 좋습니다.

src/main/resources/logback/logback-dev-loki.xml (1)

6-6: Loki 엔드포인트 확인 필요

http://localhost:3100으로 설정되어 있습니다. Dev 환경에서 Loki가 로컬에서 실행되는지 확인해주세요. 컨테이너화된 환경이나 원격 Loki 인스턴스를 사용하는 경우 호스트명이나 서비스 이름으로 변경이 필요할 수 있습니다.

환경 변수를 사용하도록 개선할 수 있습니다:

-            <url>http://localhost:3100/loki/api/v1/push</url>
+            <url>${LOKI_URL:-http://localhost:3100/loki/api/v1/push}</url>
src/main/resources/logback-spring.xml (1)

16-23: LGTM!

프로파일별 로깅 설정을 별도 파일로 분리하여 관심사를 명확히 구분했습니다. Dev 환경은 Loki를, Prod 환경은 파일 기반 로깅을 사용하는 구조가 명확합니다.

src/main/java/com/permitseoul/permitserver/global/external/discord/dto/DiscordMessage.java (1)

4-4: LGTM!

Discord 메시지 페이로드를 위한 깔끔한 record 구현입니다. 불변성이 보장되며 factory method를 통한 생성 패턴을 따릅니다.

src/main/java/com/permitseoul/permitserver/global/external/discord/DiscordFeignClient.java (1)

17-18: void 반환 타입은 전송 실패를 감지하지 못함.

메서드가 void를 반환하므로 호출자는 Discord 전송 성공 여부를 알 수 없습니다. 현재 DiscordDevHttpLogSender에서 try-catch로 예외를 처리하고 있으나, Feign 레벨에서 발생하는 오류(4xx/5xx 응답, 타임아웃 등)가 예외로 전파되는지 확인하세요. 모니터링이나 메트릭 수집을 고려한다면 성공/실패 상태를 추적하는 방안도 검토하세요.

src/main/java/com/permitseoul/permitserver/global/config/AsyncConfig.java (1)

26-39: MDC 전파 로직 잘 구현됨.

비동기 스레드로 MDC 컨텍스트를 전파하는 로직이 올바르게 구현되어 있습니다. contextMap을 캡처하고, 작업 실행 전에 설정한 후 finally 블록에서 정리하는 패턴이 정확합니다.

src/main/java/com/permitseoul/permitserver/global/external/discord/DiscordDevHttpLogSender.java (1)

18-18: 정적 ObjectMapper 인스턴스는 안전함.

Jackson의 ObjectMapper는 스레드 안전하므로 정적 인스턴스로 재사용하는 것이 올바른 패턴입니다. 성능상 이점도 있습니다.

src/main/java/com/permitseoul/permitserver/global/util/HttpReqResLogJsonBuilder.java (2)

56-62: 본문 추출 및 자르기 로직 정확함.

null/빈 배열 처리와 최대 길이 제한이 올바르게 구현되어 있습니다. UTF-8 인코딩을 명시적으로 사용하는 것도 좋습니다.


14-15: @UtilityClass 사용 적절함.

Lombok의 @UtilityClass를 사용하여 유틸리티 클래스를 정의하는 것이 적절합니다. 이는 private 생성자를 자동으로 생성하고, 인스턴스화를 방지합니다.

Based on learnings

src/main/java/com/permitseoul/permitserver/global/external/discord/util/DiscordMessageFormatterUtil.java (3)

31-66: Markdown 포맷팅 로직 잘 구현됨.

Discord 메시지로 변환하는 로직이 명확하고 가독성이 좋습니다. 텍스트 블록(text block)을 사용하여 템플릿을 정의한 것이 효과적이며, 모든 필드에 대한 기본값을 제공하여 누락된 데이터를 안전하게 처리합니다.


68-78: prettyJson 메서드의 안전한 처리.

JSON 파싱 실패 시 원본 문자열을 반환하는 것이 적절합니다. null/빈 문자열/EMPTY_BODY 처리도 올바릅니다.


7-8: @UtilityClass 사용 적절함.

Lombok의 @UtilityClass를 사용하여 유틸리티 클래스를 정의하는 것이 적절합니다.

Based on learnings

src/main/java/com/permitseoul/permitserver/global/filter/DevRequestResponseLoggingFilter.java (3)

20-23: 생성자 주입 방식 적절함.

Lombok의 @RequiredArgsConstructor를 사용하여 final 필드에 대한 생성자 주입을 구현한 것이 Spring의 권장 패턴입니다.

Based on learnings


35-40: finally 블록에서 copyBodyToResponse 호출 보장됨.

finally 블록에서 res.copyBodyToResponse()를 호출하여, 예외 발생 여부와 관계없이 응답 본문이 클라이언트에게 전송되도록 보장합니다. 다만, discordSender.send()HttpReqResLogJsonBuilder.buildJsonLog()에서 예외가 발생하면 copyBodyToResponse()가 실행되지 않을 수 있습니다.

다음과 같이 개선하여 로그 전송 실패가 응답 전송을 방해하지 않도록 하세요:

         } finally {
             final long duration = System.currentTimeMillis() - start;
-            final String jsonLog = HttpReqResLogJsonBuilder.buildJsonLog(req, res, duration);
-            discordSender.send(jsonLog);
+            try {
+                final String jsonLog = HttpReqResLogJsonBuilder.buildJsonLog(req, res, duration);
+                discordSender.send(jsonLog);
+            } catch (Exception e) {
+                log.error("Discord 로그 전송 중 오류 발생", e);
+            }
             res.copyBodyToResponse();
         }

29-30: ContentCaching 래퍼 사용 적절함.

요청과 응답 본문을 여러 번 읽기 위해 ContentCachingRequestWrapperContentCachingResponseWrapper를 사용하는 것이 올바른 패턴입니다.

src/main/java/com/permitseoul/permitserver/domain/admin/ticket/api/dto/req/TicketRoundWithTypeCreateRequest.java (2)

18-18: 아래 스크립트를 실행해 프론트엔드 및 API 문서에서 이전 날짜 형식(yyyy.MM.dd) 사용 여부를 재검증하세요:

#!/bin/bash
# 프론트엔드 코드에서 이전 날짜 형식 사용 여부 확인
rg -n 'yyyy\.MM\.dd' -g '*.js' -g '*.jsx' -g '*.ts' -g '*.tsx' || echo "No old format found in frontend"

# API 문서/스펙에서 형식 업데이트 확인
fd -e md -e yaml -e json | xargs rg -l 'yyyy\.MM\.dd' || echo "No old format in docs"

44-49: 중첩 레코드의 날짜 형식도 일관되게 업데이트되었습니다.

외부 레코드와 동일하게 내부 TicketTypeRequest의 날짜 형식도 업데이트되어 일관성이 유지되고 있습니다.


@NotNull(message = "티켓라운드 판매 시작일은 필수입니다.")
@JsonFormat(pattern = "yyyy.MM.dd HH:mm")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

날짜 형식 변경으로 인한 API 호환성 영향을 확인하세요.

날짜 형식을 "yyyy.MM.dd HH:mm"에서 "yyyy-MM-dd HH:mm"로 변경한 것은 ISO 8601 표준에 더 가깝게 정렬하는 좋은 개선입니다. 하지만 이는 기존 API 클라이언트에 영향을 줄 수 있는 breaking change입니다.

다음 사항들을 확인해주세요:

  1. 코드베이스 전체의 일관성: 다른 DTO 파일들에서도 동일한 날짜 형식 패턴이 사용되는지 확인이 필요합니다.

  2. 클라이언트 영향도: 기존 API 클라이언트들이 새로운 형식으로 요청을 보낼 수 있도록 업데이트되어야 합니다.

  3. API 문서: Swagger/OpenAPI 문서나 기타 API 문서에 날짜 형식 변경 사항이 반영되어야 합니다.

아래 스크립트를 실행하여 코드베이스 전체에서 다른 날짜 형식 패턴이 남아있는지 확인하세요:

Also applies to: 22-22, 44-44, 48-48


🏁 Script executed:

#!/bin/bash
# 설명: 이전 날짜 형식 패턴이 남아있는 파일 검색

# yyyy.MM.dd 패턴을 사용하는 @JsonFormat 어노테이션 검색
rg -n '@JsonFormat.*yyyy\.MM\.dd' --type java -g '!**/test/**'

Length of output: 148


다음 스크립트를 실행하여 리포지토리 내 모든 Java 파일에서 @JsonFormat 어노테이션에 남아있는 이전 날짜 형식(yyyy.MM.dd) 패턴을 검색해 주세요:


🏁 Script executed:

#!/bin/bash
# 이전 날짜 형식 'yyyy.MM.dd' 패턴 검색 (.java 파일 전체 대상)

rg -n '@JsonFormat.*yyyy\.MM\.dd' -g '*.java' -g '!**/test/**'

Length of output: 1159


일관성: 모든 @JsonFormat 어노테이션 패턴을 "yyyy-MM-dd HH:mm"으로 통일하세요

  • 아래 파일들에 아직 "yyyy.MM.dd HH:mm" 패턴이 남아있습니다. 모두 "yyyy-MM-dd HH:mm"으로 수정해야 합니다.
    • src/main/java/com/permitseoul/permitserver/domain/eventtimetable/timetable/api/dto/TimetableResponse.java (11,13,42,44)
    • src/main/java/com/permitseoul/permitserver/domain/eventtimetable/timetable/api/dto/TimetableDetailResponse.java (17,19)
    • src/main/java/com/permitseoul/permitserver/domain/admin/coupon/api/dto/response/CouponResponse.java (10)

  • Swagger/OpenAPI 문서 및 기타 API 문서에도 날짜 형식 변경 내용을 반영하세요.

  • 기존 클라이언트가 새 포맷을 사용하도록 공지・업데이트 계획을 검토하세요.

🤖 Prompt for AI Agents
In
src/main/java/com/permitseoul/permitserver/domain/admin/ticket/api/dto/req/TicketRoundWithTypeCreateRequest.java
around line 18, ensure the @JsonFormat pattern is the unified "yyyy-MM-dd
HH:mm"; if it already is, verify the other listed files use the same pattern and
update any occurrences of "yyyy.MM.dd HH:mm" to "yyyy-MM-dd HH:mm", then update
Swagger/OpenAPI date format annotations and API documentation accordingly and
coordinate a client-notification/update plan for the new format.

Comment on lines +18 to 23
@JsonFormat(pattern = "yyyy-MM-dd HH:mm")
LocalDateTime ticketRoundSalesStartDate,

@NotNull(message = "티켓라운드 판매 종료일은 필수입니다.")
@JsonFormat(pattern = "yyyy.MM.dd HH:mm")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm")
LocalDateTime ticketRoundSalesEndDate,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

API 호환성 깨짐 가능성을 확인하세요.

날짜 형식을 "yyyy.MM.dd HH:mm"에서 "yyyy-MM-dd HH:mm"로 변경했습니다. 이는 기존 API 클라이언트에 영향을 주는 breaking change입니다. 다음을 확인하세요:

  1. 모든 API 소비자(프론트엔드, 모바일 앱 등)가 새 형식을 사용하도록 업데이트되었는지
  2. 관련 테스트 케이스가 새 형식으로 업데이트되었는지
  3. API 문서가 새 형식을 반영하도록 업데이트되었는지

다음 스크립트로 코드베이스 전체에서 날짜 형식 일관성을 확인하세요:


🏁 Script executed:

#!/bin/bash
# 설명: 다른 DTO에서 여전히 구 형식을 사용하는지 확인

# 구 형식 패턴 검색
echo "=== 구 형식 (yyyy.MM.dd) 사용 중인 파일 ==="
rg -n '@JsonFormat\(pattern\s*=\s*"yyyy\.MM\.dd' --type=java

# 새 형식 패턴 검색
echo -e "\n=== 새 형식 (yyyy-MM-dd) 사용 중인 파일 ==="
rg -n '@JsonFormat\(pattern\s*=\s*"yyyy-MM-dd' --type=java

Length of output: 2388


날짜 형식 일관화 필요

TimetableResponse.java, TimetableDetailResponse.java 등에서 여전히 "yyyy.MM.dd HH:mm" 패턴을 사용 중입니다. 모든 @JsonFormat(pattern = ...)"yyyy-MM-dd HH:mm"으로 통일하고, 관련 API 문서·테스트·클라이언트도 함께 업데이트하세요.

🤖 Prompt for AI Agents
In
src/main/java/com/permitseoul/permitserver/domain/admin/ticket/api/dto/req/TicketRoundWithTypeCreateRequest.java
around lines 18 to 23, the @JsonFormat pattern is "yyyy-MM-dd HH:mm" but other
DTOs (e.g., TimetableResponse.java, TimetableDetailResponse.java) still use
"yyyy.MM.dd HH:mm"; update those files to use the unified "yyyy-MM-dd HH:mm"
pattern, then search the codebase for any remaining @JsonFormat usages and
replace the old pattern, and finally update API documentation, tests, and any
client code or test fixtures to reflect the new date format so
serialization/deserialization remains consistent across services.

Comment on lines +10 to +14
@FeignClient(
name = "discordClient",
url = "${discord.webhook-url}",
configuration = FeignConfig.class
)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

FeignConfig에서 재시도 및 타임아웃 설정 확인 필요.

Feign 클라이언트가 외부 Discord webhook을 호출하는데, 네트워크 장애나 일시적인 오류 발생 시 재시도 로직과 적절한 타임아웃 설정이 중요합니다. FeignConfig에 Retryer와 타임아웃이 적절히 구성되어 있는지 확인하세요.

다음 스크립트로 FeignConfig의 구성을 확인하세요:


🏁 Script executed:

#!/bin/bash
# FeignConfig에서 Retryer 및 타임아웃 설정 확인
rg -n -A 10 "class FeignConfig" --type=java

Length of output: 717


FeignConfig에 Timeout 설정 추가 필요

FeignConfig에 Retryer는 정의되어 있으나, connect 및 read timeout이 설정되어 있지 않습니다. Request.Options 빈을 추가해 적절한 타임아웃을 설정하세요. 예시:

@Bean
public Request.Options options() {
    return new Request.Options(
        5000, TimeUnit.MILLISECONDS,   // connect timeout
        10000, TimeUnit.MILLISECONDS,  // read timeout
        true
    );
}
🤖 Prompt for AI Agents
In
src/main/java/com/permitseoul/permitserver/global/external/discord/DiscordFeignClient.java
around lines 10 to 14, the FeignConfig currently defines a Retryer but lacks
connect/read timeouts; add a Request.Options bean in the FeignConfig to set
sensible connect and read timeouts (for example 5000ms connect and 10000ms read)
and register it as a @Bean so Feign uses those timeouts; ensure you import/use
java.util.concurrent.TimeUnit and return a new Request.Options with the connect
timeout, read timeout and followRedirects flag set appropriately.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1c9479a and 96026e1.

📒 Files selected for processing (1)
  • build.gradle (1 hunks)

build.gradle Outdated
Comment on lines 82 to 83
implementation "software.amazon.awssdk:bom:2.34.0"
implementation "software.amazon.awssdk:s3:2.34.0"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

AWS SDK BOM 선언 방식 오류 수정 필요

implementation "software.amazon.awssdk:bom:2.34.0"처럼 BOM을 일반 의존성으로 추가하면 Gradle이 POM 패키징을 직접 가져오려다 빌드가 실패합니다. platform(...) 또는 enforcedPlatform(...)으로 선언을 바꿔 주세요.

-	implementation "software.amazon.awssdk:bom:2.34.0"
+	implementation platform("software.amazon.awssdk:bom:2.34.0")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
implementation "software.amazon.awssdk:bom:2.34.0"
implementation "software.amazon.awssdk:s3:2.34.0"
// use platform() for BOM imports to avoid POM resolution errors
implementation platform("software.amazon.awssdk:bom:2.34.0")
implementation "software.amazon.awssdk:s3:2.34.0"
🤖 Prompt for AI Agents
In build.gradle around lines 82 to 83, the AWS SDK BOM is declared as a normal
dependency which causes Gradle to attempt to fetch the BOM POM as a jar and
fail; replace the BOM declaration by using platform(...) or
enforcedPlatform(...) (e.g., change the implementation line that references
software.amazon.awssdk:bom:2.34.0 to use platform/enforcedPlatform so the BOM is
imported correctly) and keep the s3 dependency as an implementation dependency.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
build.gradle (1)

82-85: AWS SDK 버전 관리를 BOM으로 일관되게 가져가세요.

지금은 S3 모듈만 직접 버전 지정하고 있어 다른 AWS 모듈을 추가할 때마다 일일이 버전을 맞춰야 합니다. platform으로 BOM을 가져오면 모든 AWS SDK 모듈이 같은 릴리스 라인으로 정렬되어 유지보수와 보안 패치 적용이 쉬워집니다.

+	implementation platform("software.amazon.awssdk:bom:2.34.0")
-	implementation "software.amazon.awssdk:s3:2.34.0"
+	implementation "software.amazon.awssdk:s3"
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 96026e1 and c1c370a.

📒 Files selected for processing (1)
  • build.gradle (1 hunks)

@sjk4618 sjk4618 merged commit f7a89f4 into dev Oct 9, 2025
2 checks passed
@sjk4618 sjk4618 deleted the feat/#153 branch October 9, 2025 17:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: dev, prod 환경별 xml 로그 파일 관리 및 dev 환경 http응답/요청 디스코드 웹훅 연동

2 participants