Skip to content

[feat] logging#4

Merged
toychip merged 4 commits intomainfrom
feat/#3-logging
Feb 26, 2026
Merged

[feat] logging#4
toychip merged 4 commits intomainfrom
feat/#3-logging

Conversation

@toychip
Copy link
Copy Markdown
Contributor

@toychip toychip commented Feb 26, 2026

관련 이슈


변경 내용


체크리스트

  • Ktlint
  • MDC 추가,
  • Cloudwatch 세팅 -> 추후 구현
  • Logging AOP 추가 -> Filter로 구현
  • 추후 ES 설치하여 로그 저장

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능
    • 액세스 로깅 시스템 추가: 모든 요청에 대해 HTTP 메서드, URI, 클라이언트 IP, 상태 코드, 처리 시간, 오류 메시지를 포함한 구조화된 로그 캡처
    • 비동기 이벤트 기반 로그 처리 구현
    • 요청 추적을 위한 상관관계 ID 지원
    • 개발 및 프로덕션 환경별 로깅 설정 추가

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Feb 26, 2026

📝 Walkthrough

Walkthrough

접근 로깅 시스템이 추가되었습니다. 도메인 모델(AccessLog, AccessLogEvent), 이벤트 처리 인프라(Events, EventsConfiguration, AsyncConfig), HTTP 필터(MdcLoggingFilter)는 요청 메타데이터를 캡처하고 이벤트를 발행하며, 출력 포트 및 어댑터 패턴은 영속성을 제공하고, MDC 구성은 요청 상관관계를 설정하고, 중앙화된 로깅 구성을 도입합니다.

Changes

Cohort / File(s) Summary
Domain Model Layer
linktrip-application/src/main/kotlin/com/linktrip/application/domain/log/AccessLog.kt, linktrip-application/src/main/kotlin/com/linktrip/application/domain/log/AccessLogEvent.kt
구조화된 접근 로그 데이터를 나타내는 데이터 클래스와 이벤트 래퍼 추가
Event & Async Infrastructure
linktrip-common/src/main/kotlin/com/linktrip/common/config/event/Events.kt, linktrip-common/src/main/kotlin/com/linktrip/common/config/event/EventsConfiguration.kt, linktrip-common/src/main/kotlin/com/linktrip/common/config/async/AsyncConfig.kt
중앙화된 이벤트 발행자, 이벤트 초기화 구성, 비동기 실행을 위한 ThreadPoolTaskExecutor 빈 추가
Event Listener & Port
linktrip-application/src/main/kotlin/com/linktrip/application/domain/log/AccessLogEventListener.kt, linktrip-application/src/main/kotlin/com/linktrip/application/port/output/log/AccessLogPort.kt
AccessLogEvent를 처리하고 AccessLogPort 구현에 위임하는 비동기 리스너 및 영속성 계약 인터페이스 추가
HTTP Request Logging
linktrip-input-http/src/main/kotlin/com/linktrip/input/http/filter/MdcLoggingFilter.kt, linktrip-common/src/main/kotlin/com/linktrip/common/logging/MdcKey.kt
요청 메타데이터를 MDC에 캡처하고 AccessLogEvent를 발행하는 서블릿 필터와 MDC 키 상수 추가
Persistence Adapter
linktrip-output-persistence/mysql/src/main/kotlin/com/linktrip/output/persistence/mysql/adapter/NoOpAccessLogAdapter.kt
로그만 출력하는 AccessLogPort의 임시 구현 추가
Logging Configuration
linktrip-bootstrap/src/main/resources/logback-spring.xml, linktrip-bootstrap/src/main/resources/application-dev.yml, linktrip-bootstrap/src/main/resources/application-prod.yml
Logback 프로필 기반 구성 추가 및 기존 application.yml 로깅 레벨 설정 제거

Sequence Diagram

sequenceDiagram
    participant Client
    participant MdcLoggingFilter
    participant Events
    participant AccessLogEventListener
    participant AccessLogPort as AccessLogPort<br/>(Multiple Impls)
    
    Client->>MdcLoggingFilter: HTTP Request
    MdcLoggingFilter->>MdcLoggingFilter: Capture request metadata<br/>into MDC
    MdcLoggingFilter->>MdcLoggingFilter: Log request entry
    MdcLoggingFilter->>MdcLoggingFilter: Execute filter chain
    
    alt Request Success
        MdcLoggingFilter->>MdcLoggingFilter: Get status & duration
        MdcLoggingFilter->>MdcLoggingFilter: Create AccessLog<br/>(no error)
        MdcLoggingFilter->>Events: raise(AccessLogEvent)
        Events->>AccessLogEventListener: handle(AccessLogEvent)
        AccessLogEventListener->>AccessLogPort: save(accessLog)<br/>[async]
        AccessLogPort-->>AccessLogEventListener: persisted
    else Request Error
        MdcLoggingFilter->>MdcLoggingFilter: Catch exception
        MdcLoggingFilter->>MdcLoggingFilter: Create AccessLog<br/>(with error)
        MdcLoggingFilter->>Events: raise(AccessLogEvent)
        Events->>AccessLogEventListener: handle(AccessLogEvent)
        AccessLogEventListener->>AccessLogPort: save(accessLog)<br/>[async]
    end
    
    MdcLoggingFilter->>MdcLoggingFilter: Clear MDC
    MdcLoggingFilter->>Client: HTTP Response
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목이 PR의 주요 변경 사항을 포괄적으로 요약합니다. 이 PR은 접근 로깅 시스템 전체를 추가하는 것으로, MDC, 이벤트 발행, 비동기 처리, 필터 구현 등을 포함합니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/#3-logging

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
Copy Markdown

@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

🧹 Nitpick comments (5)
linktrip-bootstrap/src/main/resources/logback-spring.xml (1)

6-11: 로그 패턴에 날짜 정보가 누락되었습니다.

현재 시간 패턴이 HH:mm:ss.SSS로 설정되어 있어 날짜 정보가 포함되지 않습니다. 운영 환경에서 일자가 다른 로그를 분석하거나, 로그 파일을 외부 시스템(예: Elasticsearch, CloudWatch)으로 전송할 때 타임스탬프 파싱에 문제가 발생할 수 있습니다.

♻️ 날짜 정보를 포함한 패턴 제안
     <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
         <encoder>
-            <pattern>%d{HH:mm:ss.SSS}|%5p|%-8X{requestId:- }|%-40.40logger{39}|%m%n%wEx</pattern>
+            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS}|%5p|%-8X{requestId:- }|%-40.40logger{39}|%m%n%wEx</pattern>
             <charset>utf8</charset>
         </encoder>
     </appender>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@linktrip-bootstrap/src/main/resources/logback-spring.xml` around lines 6 -
11, The console appender "STDOUT" uses a time-only pattern (%d{HH:mm:ss.SSS})
which omits the date; update the encoder's pattern (the <pattern> element inside
the STDOUT appender) to include the full date and time (for example use
%d{yyyy-MM-dd HH:mm:ss.SSS} or equivalent) so logs contain a parsable timestamp
across days and external systems.
linktrip-common/src/main/kotlin/com/linktrip/common/config/async/AsyncConfig.kt (1)

12-20: 셧다운 시 로그 유실 및 큐 포화 시 예외 발생 가능성이 있습니다.

현재 설정에서 두 가지 잠재적 문제가 있습니다:

  1. 애플리케이션 종료 시 큐에 남아있는 로그 작업이 유실될 수 있습니다.
  2. 큐(500)가 가득 차면 기본 AbortPolicy에 의해 RejectedExecutionException이 발생합니다.
♻️ 제안된 개선안
     `@Bean`(name = ["AccessLogExecutor"])
     fun accessLogExecutor(): Executor =
         ThreadPoolTaskExecutor().apply {
             corePoolSize = 2
             maxPoolSize = 2
             queueCapacity = 500
             setThreadNamePrefix("AsyncAccessLog-")
+            setWaitForTasksToCompleteOnShutdown(true)
+            setAwaitTerminationSeconds(30)
+            setRejectedExecutionHandler(java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy())
             initialize()
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@linktrip-common/src/main/kotlin/com/linktrip/common/config/async/AsyncConfig.kt`
around lines 12 - 20, accessLogExecutor에서 종료 시 큐에 남은 로그가 유실되고 큐 포화 시
RejectedExecutionException이 발생할 수 있으므로 ThreadPoolTaskExecutor 설정을 변경해 주세요:
accessLogExecutor()의 반환 ThreadPoolTaskExecutor에
setWaitForTasksToCompleteOnShutdown(true)와 적절한 awaitTerminationSeconds(예: 30)를
설정해 종료 시 남은 작업을 대기하게 하고, 포화 처리 정책은 setRejectedExecutionHandler(new
java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy())로 변경해 큐가 찼을 때 호출 스레드가
작업을 실행하도록 하여 예외를 방지하세요.
linktrip-application/src/main/kotlin/com/linktrip/application/domain/log/AccessLog.kt (1)

13-13: timestamp 기본값이 테스트 가용성을 저하시킬 수 있습니다.

LocalDateTime.now()를 기본값으로 사용하면 단위 테스트에서 시간을 제어하기 어렵습니다. 또한 LocalDateTime은 타임존 정보가 없어 분산 시스템에서 로그 시간 비교 시 문제가 될 수 있습니다.

♻️ Instant 사용 고려
-import java.time.LocalDateTime
+import java.time.Instant

 data class AccessLog(
     val requestId: String,
     val method: String,
     val uri: String,
     val clientIp: String,
     val statusCode: Int,
     val durationMs: Long,
     val errorMessage: String? = null,
-    val timestamp: LocalDateTime = LocalDateTime.now(),
+    val timestamp: Instant = Instant.now(),
 )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@linktrip-application/src/main/kotlin/com/linktrip/application/domain/log/AccessLog.kt`
at line 13, AccessLog의 timestamp 프로퍼티가 LocalDateTime.now()로 기본값을 가지면 테스트에서 시간이
고정/제어하기 어렵고 타임존 정보가 없어 분산 환경에서 비교가 문제됩니다; 수정하려면 AccessLog 클래스의 timestamp 타입을
java.time.Instant(또는 OffsetDateTime/ZonedDateTime)로 변경하고 기본값을 직접 호출하지 말고 Clock를
주입해 사용하거나 기본값으로 Clock.systemUTC()를 사용하는 Instant.now(clock) 패턴을 적용하세요(예: 생성자나
팩토리에 Clock 파라미터를 추가하거나 timestamp를 필수 인자로 바꿔 테스트에서 Clock.fixed(...)로 시간 제어가 가능하도록
하세요).
linktrip-common/src/main/kotlin/com/linktrip/common/config/event/Events.kt (1)

5-14: publisher가 null일 때 이벤트가 조용히 무시됩니다.

raise 메서드에서 publisher가 null이면 이벤트가 아무런 경고 없이 버려집니다. 초기화 순서 문제나 설정 누락 시 디버깅이 어려울 수 있습니다. 또한 멀티스레드 환경에서 @Volatile 없이 가시성 문제가 발생할 수 있습니다.

♻️ 제안된 개선안
 object Events {
-    private var publisher: ApplicationEventPublisher? = null
+    `@Volatile`
+    private var publisher: ApplicationEventPublisher? = null
+
+    private val logger = org.slf4j.LoggerFactory.getLogger(Events::class.java)

     fun setPublisher(publisher: ApplicationEventPublisher) {
         this.publisher = publisher
     }

     fun raise(event: Any) {
-        publisher?.publishEvent(event)
+        val p = publisher
+        if (p != null) {
+            p.publishEvent(event)
+        } else {
+            logger.warn("Event publisher not initialized, dropping event: {}", event::class.simpleName)
+        }
     }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@linktrip-common/src/main/kotlin/com/linktrip/common/config/event/Events.kt`
around lines 5 - 14, The Events singleton currently silently drops events when
publisher is null and is vulnerable to visibility issues; mark the publisher
field as `@Volatile`, make setPublisher thread-safe (e.g. annotate with
`@Synchronized` or synchronize the assignment inside setPublisher), and change
raise to fail fast instead of silently returning by using a null-check that
throws an informative IllegalStateException (e.g. val p = publisher ?: throw
IllegalStateException("Events.publisher not initialized") then
p.publishEvent(event)); reference: Events, publisher, setPublisher, raise.
linktrip-application/src/main/kotlin/com/linktrip/application/domain/log/AccessLogEventListener.kt (1)

19-25: 저장 실패 유실량을 관측할 수 있게 보강해 주세요.

Line 19-25는 실패 시 경고 로그만 남기므로, 장애 시 유실 규모를 빠르게 파악하기 어렵습니다. 포트별 실패 카운터/알람(메트릭) 추가를 권장합니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@linktrip-application/src/main/kotlin/com/linktrip/application/domain/log/AccessLogEventListener.kt`
around lines 19 - 25, AccessLog 저장 실패 시 단순 경고만 남기지 말고 포트별 실패 카운터/메트릭을 증가시켜 유실량을
관측 가능하게 만드세요: AccessLogEventListener의 try/catch 블록(포인트:
port.save(event.accessLog) 및 logger.warn(...))에서 catch 내부에 메트릭 증가 호출을 추가하고, 태그로
port::class.simpleName 및 requestId 같은 식별자를 포함해 포트별 집계가 가능하도록 하세요; 기존 로그는 유지하되
새로운 카운터(예: Micrometer Counter 또는 회사 표준 메트릭 유틸)를 사용해 실패 발생 시 increment 하도록 구현하고,
메트릭 이름/태그는 다른 모듈과 일관되게 정하세요.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@linktrip-application/src/main/kotlin/com/linktrip/application/domain/log/AccessLog.kt`:
- Line 8: The AccessLog data class exposes the raw uri property which may
contain sensitive query parameters; update where AccessLog(uri: String, ...) is
created to pass a sanitized value instead and add a utility like
sanitizeUri(uri: String): String (or a companion/object helper) that either
strips the query string or masks known sensitive keys (e.g., token, password,
email) before assignment; reference the AccessLog class and its uri property and
ensure any persistence path (Elasticsearch/DTO mapping) uses the sanitizedUri
output rather than the raw input to meet privacy requirements.

In
`@linktrip-input-http/src/main/kotlin/com/linktrip/input/http/filter/MdcLoggingFilter.kt`:
- Line 35: The current low-entropy requestId is created in MdcLoggingFilter via
MDC.put(MdcKey.REQUEST_ID, UUID.randomUUID().toString().substring(0, 8)), which
risks collisions under high traffic; change this to a higher-entropy value (for
example use the full UUID string UUID.randomUUID().toString() or a longer
substring like first 16+ hex chars, or a base64/hex-encoded random 12+ byte
value) in the MDC.put call so MdcKey.REQUEST_ID has sufficient uniqueness for
correlation tracking.
- Around line 52-53: The access log call in raiseAccessLogEvent is using a
hardcoded 500 on exception paths, which can misrepresent the actual response
status; update the exception handling in MdcLoggingFilter (reference:
raiseAccessLogEvent and the catch/exception block) to determine and pass the
real response status (e.g., obtain status from the HttpServletResponse or the
request/response context) instead of the literal 500, falling back to 500 only
if the actual status cannot be retrieved.
- Around line 80-83: The getClientIp method in MdcLoggingFilter currently trusts
X-Forwarded-For and X-Real-IP without verification, so either enable and rely on
Spring’s forwarded headers handling or validate headers against a trusted proxy
list: add server.forward-headers-strategy in application.yml or register
ForwardedHeaderFilter (or implement explicit trusted proxy IP range checks) and
then update getClientIp to only read X-Forwarded-For/X-Real-IP when the
forwarded-header mechanism is active or when request.remoteAddr is in your
configured trusted proxy set (otherwise fall back to request.remoteAddr);
reference MdcLoggingFilter.getClientIp and the filter registration to implement
the chosen fix.

---

Nitpick comments:
In
`@linktrip-application/src/main/kotlin/com/linktrip/application/domain/log/AccessLog.kt`:
- Line 13: AccessLog의 timestamp 프로퍼티가 LocalDateTime.now()로 기본값을 가지면 테스트에서 시간이
고정/제어하기 어렵고 타임존 정보가 없어 분산 환경에서 비교가 문제됩니다; 수정하려면 AccessLog 클래스의 timestamp 타입을
java.time.Instant(또는 OffsetDateTime/ZonedDateTime)로 변경하고 기본값을 직접 호출하지 말고 Clock를
주입해 사용하거나 기본값으로 Clock.systemUTC()를 사용하는 Instant.now(clock) 패턴을 적용하세요(예: 생성자나
팩토리에 Clock 파라미터를 추가하거나 timestamp를 필수 인자로 바꿔 테스트에서 Clock.fixed(...)로 시간 제어가 가능하도록
하세요).

In
`@linktrip-application/src/main/kotlin/com/linktrip/application/domain/log/AccessLogEventListener.kt`:
- Around line 19-25: AccessLog 저장 실패 시 단순 경고만 남기지 말고 포트별 실패 카운터/메트릭을 증가시켜 유실량을
관측 가능하게 만드세요: AccessLogEventListener의 try/catch 블록(포인트:
port.save(event.accessLog) 및 logger.warn(...))에서 catch 내부에 메트릭 증가 호출을 추가하고, 태그로
port::class.simpleName 및 requestId 같은 식별자를 포함해 포트별 집계가 가능하도록 하세요; 기존 로그는 유지하되
새로운 카운터(예: Micrometer Counter 또는 회사 표준 메트릭 유틸)를 사용해 실패 발생 시 increment 하도록 구현하고,
메트릭 이름/태그는 다른 모듈과 일관되게 정하세요.

In `@linktrip-bootstrap/src/main/resources/logback-spring.xml`:
- Around line 6-11: The console appender "STDOUT" uses a time-only pattern
(%d{HH:mm:ss.SSS}) which omits the date; update the encoder's pattern (the
<pattern> element inside the STDOUT appender) to include the full date and time
(for example use %d{yyyy-MM-dd HH:mm:ss.SSS} or equivalent) so logs contain a
parsable timestamp across days and external systems.

In
`@linktrip-common/src/main/kotlin/com/linktrip/common/config/async/AsyncConfig.kt`:
- Around line 12-20: accessLogExecutor에서 종료 시 큐에 남은 로그가 유실되고 큐 포화 시
RejectedExecutionException이 발생할 수 있으므로 ThreadPoolTaskExecutor 설정을 변경해 주세요:
accessLogExecutor()의 반환 ThreadPoolTaskExecutor에
setWaitForTasksToCompleteOnShutdown(true)와 적절한 awaitTerminationSeconds(예: 30)를
설정해 종료 시 남은 작업을 대기하게 하고, 포화 처리 정책은 setRejectedExecutionHandler(new
java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy())로 변경해 큐가 찼을 때 호출 스레드가
작업을 실행하도록 하여 예외를 방지하세요.

In `@linktrip-common/src/main/kotlin/com/linktrip/common/config/event/Events.kt`:
- Around line 5-14: The Events singleton currently silently drops events when
publisher is null and is vulnerable to visibility issues; mark the publisher
field as `@Volatile`, make setPublisher thread-safe (e.g. annotate with
`@Synchronized` or synchronize the assignment inside setPublisher), and change
raise to fail fast instead of silently returning by using a null-check that
throws an informative IllegalStateException (e.g. val p = publisher ?: throw
IllegalStateException("Events.publisher not initialized") then
p.publishEvent(event)); reference: Events, publisher, setPublisher, raise.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 534c1bd and bab4aec.

📒 Files selected for processing (13)
  • linktrip-application/src/main/kotlin/com/linktrip/application/domain/log/AccessLog.kt
  • linktrip-application/src/main/kotlin/com/linktrip/application/domain/log/AccessLogEvent.kt
  • linktrip-application/src/main/kotlin/com/linktrip/application/domain/log/AccessLogEventListener.kt
  • linktrip-application/src/main/kotlin/com/linktrip/application/port/output/log/AccessLogPort.kt
  • linktrip-bootstrap/src/main/resources/application-dev.yml
  • linktrip-bootstrap/src/main/resources/application-prod.yml
  • linktrip-bootstrap/src/main/resources/logback-spring.xml
  • linktrip-common/src/main/kotlin/com/linktrip/common/config/async/AsyncConfig.kt
  • linktrip-common/src/main/kotlin/com/linktrip/common/config/event/Events.kt
  • linktrip-common/src/main/kotlin/com/linktrip/common/config/event/EventsConfiguration.kt
  • linktrip-common/src/main/kotlin/com/linktrip/common/logging/MdcKey.kt
  • linktrip-input-http/src/main/kotlin/com/linktrip/input/http/filter/MdcLoggingFilter.kt
  • linktrip-output-persistence/mysql/src/main/kotlin/com/linktrip/output/persistence/mysql/adapter/NoOpAccessLogAdapter.kt
💤 Files with no reviewable changes (2)
  • linktrip-bootstrap/src/main/resources/application-prod.yml
  • linktrip-bootstrap/src/main/resources/application-dev.yml
📜 Review details
🔇 Additional comments (9)
linktrip-bootstrap/src/main/resources/logback-spring.xml (2)

14-21: 개발 환경 로깅 설정 확인.

org.hibernate.orm.jdbc.bind 레벨이 TRACE로 설정되어 SQL 바인딩 파라미터 값이 로그에 노출됩니다. 개발 환경에서는 디버깅에 유용하지만, 민감한 데이터(비밀번호, 개인정보 등)가 로그에 기록될 수 있으니 테스트 데이터 사용 시 주의가 필요합니다.


24-30: 운영 환경 설정이 적절합니다.

운영 환경에서 com.linktrip을 INFO, Hibernate SQL을 WARN으로 설정한 것은 적절한 로그 레벨입니다. PR 설명에 따르면 CloudWatch 설정이 추후 구현 예정이므로, 현재 콘솔 출력만 있는 것은 의도된 구성으로 보입니다.

linktrip-common/src/main/kotlin/com/linktrip/common/config/event/EventsConfiguration.kt (1)

8-17: LGTM!

InitializingBean을 사용하여 Spring 컨텍스트 초기화 후 이벤트 퍼블리셔를 설정하는 올바른 패턴입니다.

linktrip-common/src/main/kotlin/com/linktrip/common/logging/MdcKey.kt (1)

3-8: LGTM!

MDC 키를 중앙에서 관리하여 일관성을 보장합니다.

linktrip-application/src/main/kotlin/com/linktrip/application/domain/log/AccessLogEvent.kt (1)

7-9: LGTM!

이벤트 페이로드로 사용되는 깔끔한 래퍼 클래스입니다.

linktrip-application/src/main/kotlin/com/linktrip/application/port/output/log/AccessLogPort.kt (1)

9-11: LGTM!

헥사고날 아키텍처의 Output Port 패턴을 잘 따르고 있습니다.

linktrip-output-persistence/mysql/src/main/kotlin/com/linktrip/output/persistence/mysql/adapter/NoOpAccessLogAdapter.kt (1)

14-21: LGTM!

Elasticsearch 어댑터 구현 전까지 사용할 적절한 No-Op 구현체입니다. 디버그 레벨 로깅으로 개발 시 동작 확인이 가능합니다.

linktrip-application/src/main/kotlin/com/linktrip/application/domain/log/AccessLogEventListener.kt (1)

15-27: 포트별 장애 격리 처리 방식은 좋습니다.

@Async + 포트별 try/catch 조합으로, 한 저장소 실패가 전체 접근로그 처리 흐름을 멈추지 않게 구성되어 있습니다.

linktrip-input-http/src/main/kotlin/com/linktrip/input/http/filter/MdcLoggingFilter.kt (1)

54-56: finally 블록에서 MDC 정리는 적절합니다.

Line 54-56은 스레드 재사용 환경에서 MDC 누수를 방지하는 데 유효합니다.

data class AccessLog(
val requestId: String,
val method: String,
val uri: String,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

URI에 민감한 정보가 포함될 수 있습니다.

쿼리 파라미터에 토큰, 이메일 등 민감한 정보가 포함될 수 있으므로, 로그 저장 전 URI 필터링이 필요할 수 있습니다. 추후 Elasticsearch 등에 저장할 때 개인정보 보호 규정(GDPR/CCPA) 준수 여부를 확인하세요.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@linktrip-application/src/main/kotlin/com/linktrip/application/domain/log/AccessLog.kt`
at line 8, The AccessLog data class exposes the raw uri property which may
contain sensitive query parameters; update where AccessLog(uri: String, ...) is
created to pass a sanitized value instead and add a utility like
sanitizeUri(uri: String): String (or a companion/object helper) that either
strips the query string or masks known sensitive keys (e.g., token, password,
email) before assignment; reference the AccessLog class and its uri property and
ensure any persistence path (Elasticsearch/DTO mapping) uses the sanitizedUri
output rather than the raw input to meet privacy requirements.

val startTime = System.currentTimeMillis()

try {
MDC.put(MdcKey.REQUEST_ID, UUID.randomUUID().toString().substring(0, 8))
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

requestId 엔트로피가 너무 낮아 충돌 위험이 큽니다.

Line 35에서 UUID를 8자리로 잘라 쓰면 고트래픽에서 requestId 충돌이 현실적으로 발생할 수 있어, MDC 상관관계 추적 정확도가 떨어집니다.

🔧 수정 제안
-            MDC.put(MdcKey.REQUEST_ID, UUID.randomUUID().toString().substring(0, 8))
+            MDC.put(MdcKey.REQUEST_ID, UUID.randomUUID().toString())
📝 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
MDC.put(MdcKey.REQUEST_ID, UUID.randomUUID().toString().substring(0, 8))
MDC.put(MdcKey.REQUEST_ID, UUID.randomUUID().toString())
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@linktrip-input-http/src/main/kotlin/com/linktrip/input/http/filter/MdcLoggingFilter.kt`
at line 35, The current low-entropy requestId is created in MdcLoggingFilter via
MDC.put(MdcKey.REQUEST_ID, UUID.randomUUID().toString().substring(0, 8)), which
risks collisions under high traffic; change this to a higher-entropy value (for
example use the full UUID string UUID.randomUUID().toString() or a longer
substring like first 16+ hex chars, or a base64/hex-encoded random 12+ byte
value) in the MDC.put call so MdcKey.REQUEST_ID has sufficient uniqueness for
correlation tracking.

Comment on lines +52 to +53
raiseAccessLogEvent(500, duration, e.message)
throw e
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

예외 경로 상태코드 500 고정은 기록 정확도를 떨어뜨릴 수 있습니다.

Line 52는 항상 500으로 저장해서, 이미 다른 에러 상태가 설정된 케이스의 접근로그가 왜곡될 수 있습니다.

🔧 수정 제안
-            raiseAccessLogEvent(500, duration, e.message)
+            val statusCode =
+                httpResponse.status.takeIf { it in 400..599 }
+                    ?: HttpServletResponse.SC_INTERNAL_SERVER_ERROR
+            raiseAccessLogEvent(statusCode, duration, e.message)
             throw e
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@linktrip-input-http/src/main/kotlin/com/linktrip/input/http/filter/MdcLoggingFilter.kt`
around lines 52 - 53, The access log call in raiseAccessLogEvent is using a
hardcoded 500 on exception paths, which can misrepresent the actual response
status; update the exception handling in MdcLoggingFilter (reference:
raiseAccessLogEvent and the catch/exception block) to determine and pass the
real response status (e.g., obtain status from the HttpServletResponse or the
request/response context) instead of the literal 500, falling back to 500 only
if the actual status cannot be retrieved.

Comment on lines +80 to +83
private fun getClientIp(request: HttpServletRequest): String =
request.getHeader("X-Forwarded-For")?.split(",")?.firstOrNull()?.trim()
?: request.getHeader("X-Real-IP")
?: request.remoteAddr
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for trusted proxy and forwarded header configurations
rg -n -C2 'ForwardedHeaderFilter|RemoteIpValve|server\.forward-headers-strategy|forward-headers|X-Forwarded-For|X-Real-IP' --type kotlin --type xml --type yaml --type properties

Repository: Link-Trip/BackEnd

Length of output: 95


🏁 Script executed:

#!/bin/bash
# Search for trusted proxy and forwarded header configurations
rg -n -C2 'ForwardedHeaderFilter|RemoteIpValve|server\.forward-headers-strategy|forward-headers|X-Forwarded-For|X-Real-IP' --type kotlin --type xml --type yaml

Repository: Link-Trip/BackEnd

Length of output: 830


🏁 Script executed:

#!/bin/bash
# Find application configuration files
echo "=== Application Configuration Files ==="
find . -type f \( -name "application*.yml" -o -name "application*.yaml" -o -name "application*.properties" \) 2>/dev/null | head -20

echo -e "\n=== Searching for server configuration ==="
rg -n 'server\.|forward|proxy' --type kotlin --type xml --type yaml | grep -i 'forward\|proxy\|server\.' | head -30

Repository: Link-Trip/BackEnd

Length of output: 386


🏁 Script executed:

#!/bin/bash
# Read application configuration files
echo "=== application.yml ==="
cat ./linktrip-bootstrap/src/main/resources/application.yml

echo -e "\n=== application-dev.yml ==="
cat ./linktrip-bootstrap/src/main/resources/application-dev.yml

echo -e "\n=== application-prod.yml ==="
cat ./linktrip-bootstrap/src/main/resources/application-prod.yml

echo -e "\n=== application-actuator.yml ==="
cat ./linktrip-bootstrap/src/main/resources/application-actuator.yml

Repository: Link-Trip/BackEnd

Length of output: 1216


🏁 Script executed:

#!/bin/bash
# Search for filter, interceptor, and security configurations
echo "=== Search for `@Bean` filter configurations ==="
rg -n '@Bean|@Component|@Configuration' --type kotlin | grep -i -A3 'filter\|interceptor' | head -40

echo -e "\n=== Search for ServletFilter or HandlerInterceptor implementations ==="
rg -n 'implements.*Filter|implements.*Interceptor|class.*Filter|class.*Interceptor' --type kotlin

echo -e "\n=== Search for security configuration ==="
rg -n '@EnableWebSecurity|@Configuration.*Sec|SecurityFilterChain' --type kotlin

Repository: Link-Trip/BackEnd

Length of output: 818


🏁 Script executed:

#!/bin/bash
cat -n linktrip-input-http/src/main/kotlin/com/linktrip/input/http/filter/MdcLoggingFilter.kt

Repository: Link-Trip/BackEnd

Length of output: 3765


프록시 신뢰 체인 구성이 필요합니다.

getClientIp 함수는 X-Forwarded-ForX-Real-IP 헤더를 검증 없이 직접 사용하고 있습니다. 코드베이스에서 server.forward-headers-strategy, ForwardedHeaderFilter, 또는 유사한 신뢰 프록시 설정을 찾을 수 없으므로, 현재 상태에서는 클라이언트가 이 헤더들을 위조하여 감사 로그의 IP 정보를 오염시킬 수 있습니다. 다음 중 하나를 구성해야 합니다:

  • application.yml에서 server.forward-headers-strategy 설정 추가
  • Spring의 ForwardedHeaderFilter 등록
  • 신뢰할 수 있는 프록시 IP 범위 명시적 검증
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@linktrip-input-http/src/main/kotlin/com/linktrip/input/http/filter/MdcLoggingFilter.kt`
around lines 80 - 83, The getClientIp method in MdcLoggingFilter currently
trusts X-Forwarded-For and X-Real-IP without verification, so either enable and
rely on Spring’s forwarded headers handling or validate headers against a
trusted proxy list: add server.forward-headers-strategy in application.yml or
register ForwardedHeaderFilter (or implement explicit trusted proxy IP range
checks) and then update getClientIp to only read X-Forwarded-For/X-Real-IP when
the forwarded-header mechanism is active or when request.remoteAddr is in your
configured trusted proxy set (otherwise fall back to request.remoteAddr);
reference MdcLoggingFilter.getClientIp and the filter registration to implement
the chosen fix.

@toychip toychip merged commit 09e309f into main Feb 26, 2026
1 check passed
@toychip toychip changed the title Feat/#3 logging [feat] logging Feb 26, 2026
@coderabbitai coderabbitai bot mentioned this pull request Mar 17, 2026
2 tasks
@coderabbitai coderabbitai bot mentioned this pull request Mar 25, 2026
2 tasks
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.

1 participant