Skip to content

feat(observability): 클라이언트/서버 오류로그·스토리지 사용량 API 구현 #29 - #71

Open
tlgms wants to merge 8 commits into
feature/29-observability-02-core-apisfrom
feature/29-observability-03-logs-resources
Open

feat(observability): 클라이언트/서버 오류로그·스토리지 사용량 API 구현 #29#71
tlgms wants to merge 8 commits into
feature/29-observability-02-core-apisfrom
feature/29-observability-03-logs-resources

Conversation

@tlgms

@tlgms tlgms commented Aug 11, 2026

Copy link
Copy Markdown

Summary

Related Issue

Implementation

  • 클라이언트 로그 수집: fingerprint(SHA-256) 그룹핑 Redis 저장, 메시지 500자 절단, 배치 20건/64KB 제한
  • 클라이언트 오류 목록 조회: 기본 1시간, 최대 7일 범위, size 최대 100 clamp, 커서 기반 페이지네이션
  • 서버 API 오류 목록 조회: service/status 파라미터 검증(INVALID_SERVICE 등). 서버 오류를 이 서비스로 보고하는 수집 경로가 문서에 없어 EmptyServerLogStoreAdapter가 항상 빈 결과를 반환(코드 주석으로 근거 명시) — 연동 추가 시 client-log와 동일한 패턴으로 교체
  • 스토리지 사용량: 실시간 측정. DB는 접속 권한이 없어 usedBytes=0/totalBytes=null, bucket은 로컬 리포트 저장 디렉터리 사용량으로 대체

Testing

  • bazel build/test //systems/observability/... 통과

Checklist

  • API 단위로 커밋 분리
  • 코드 리뷰

tlgms and others added 4 commits August 9, 2026 22:15
POST /api/monitor/v11/collect/client-log 구현. 메시지는 500자로
절단, 배치 20건 초과·빈 배치는 INVALID_PAYLOAD, 본문 64KB 초과는
PAYLOAD_TOO_LARGE. User-Agent에서 브라우저/OS를 휴리스틱으로 추출해
로그 항목에 함께 저장한다.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
GET /api/monitor/v11/logs/client 구현. 기본 조회 범위 1시간, 최대
7일 초과 시 INVALID_TIME_RANGE. size는 최대 100으로 clamp, cursor는
손상 시 Cursor.decode에서 INVALID_CURSOR로 처리된다.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
GET /api/monitor/v11/logs/server 구현. service는 ServiceName 검증
실패 시 INVALID_SERVICE, status는 4xx/5xx 또는 구체 코드로 파싱하고
형식이 올바르지 않으면 INVALID_PAYLOAD.

서버 오류를 이 서비스로 보고하는 수집 경로가 문서에 없어
EmptyServerLogStoreAdapter가 항상 빈 결과를 반환한다. 각 서비스에
오류 리포팅 연동이 추가되면 (client-log와 동일한 fingerprint 그룹핑
패턴으로) Redis 기반 어댑터로 교체한다.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
GET /api/monitor/v11/resources 구현. LocalDiskStorageUsageAdapter가
매 요청 실시간으로 측정하므로 database.totalBytes/usageRatio는
DB 접속 권한이 없어 null, bucket은 로컬 리포트 저장 디렉터리 사용량으로
대체한다. 실시간 측정이라 METRIC_UNAVAILABLE 케이스는 발생하지 않는다.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are limited based on label configuration.

🏷️ Required labels (at least one) (1)
  • ready-for-review
🚫 Excluded labels (none allowed) (2)
  • wip
  • do-not-review

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: a250c15a-beff-41a9-ad6a-733b3e497e89

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

사용자는 클라이언트 로그와 서버 API 오류를 조회하고, 데이터베이스·버킷 스토리지 사용량을 확인할 수 있습니다. 클라이언트 로그는 안전한 크기와 배치 제한을 적용해 수집합니다.

아키텍처 변경

  • 클라이언트 로그 수집·조회, 서버 로그 조회, 스토리지 사용량 조회 유스케이스와 포트를 추가했습니다.
  • 클라이언트 로그를 SHA-256 fingerprint로 그룹화하고 Redis에 저장하는 흐름을 지원합니다.
  • 로그 조회에 시간 범위, 로그 레벨, 서비스, 상태 필터와 커서 기반 페이지네이션을 적용했습니다.
  • 클라이언트 로그 메시지는 최대 500자로 자릅니다.
  • 클라이언트 로그 요청은 최대 20건, 본문 최대 64KB로 제한합니다.
  • User-Agent에서 브라우저와 운영체제 정보를 추출합니다.
  • 서버 로그 저장 경로가 없을 때 빈 결과를 반환하는 EmptyServerLogStoreAdapter를 추가했습니다.
  • 스토리지 사용량 API와 응답 DTO를 추가했습니다.
  • 데이터베이스 사용량은 권한이 없으면 usedBytes=0, totalBytes=null을 반환합니다.
  • 버킷 사용량은 로컬 리포트 저장 디렉터리 사용량으로 대체합니다.
  • 클라이언트 캐시 TTL은 300초로 제공합니다.

위험 영역

  • 클라이언트 로그 수집은 IP 기반 요청 제한과 본문·배치 크기 제한에 의존합니다.
  • 잘못된 시간 범위, 로그 레벨, 서비스, 상태 값은 도메인 예외를 발생시킵니다.
  • 서버 로그 저장소가 연결되지 않으면 조회 결과가 빈 목록으로 반환됩니다.
  • 데이터베이스 권한이 없으면 사용률을 계산할 수 없습니다.
  • 버킷 사용량은 실제 외부 버킷이 아닌 로컬 디렉터리 기준입니다.

마이그레이션 및 호환성

  • 새 API 사용자는 요청 DTO와 필수 검증 조건을 준수해야 합니다.
  • 기존 서버 로그 수집 기능이 없으면 EmptyServerLogStoreAdapter가 기본 동작을 제공합니다.
  • 클라이언트는 nextCursorhasNext를 사용해 페이지를 계속 조회해야 합니다.
  • 스토리지 사용량 소비자는 totalBytesusageRationull 값을 처리해야 합니다.

검증 체크리스트 및 롤아웃

  • bazel build/test //systems/observability/... 통과
  • 클라이언트 로그 메시지 절단과 배치 검증 테스트
  • 클라이언트 로그 시간 범위와 페이지 크기 검증 테스트
  • 서버 로그 상태 필터와 시간 범위 검증 테스트
  • 스토리지 사용률과 캐시 TTL 검증 테스트
  • UserAgentParser 브라우저·운영체제 파싱 테스트
  • 배포 후 로그 수집 제한과 Redis 저장량을 모니터링
  • 서버 로그 저장소 연동 전 빈 결과 응답을 운영 환경에서 확인
  • 데이터베이스 및 버킷 사용량 대체 기준을 운영 환경에서 확인

Walkthrough

클라이언트 로그 수집·조회 API와 서버 로그 조회 API를 추가했다. 조회 시간과 상태 필터를 검증한다. 스토리지 사용량 조회 API를 추가했다. 응답 DTO와 User-Agent 파서를 정의하고 애플리케이션 테스트를 추가했다.

Changes

관측성 API 확장

Layer / File(s) Summary
클라이언트 로그 수집
systems/observability/observability-adapter-in/.../controller/ClientLogCollectController.kt, systems/observability/observability-adapter-in/.../dto/request/ClientLogCollectRequest.kt, systems/observability/observability-application/.../ClientLogCollectionService.kt, systems/observability/observability-domain/.../UserAgentParser.kt, systems/observability/.../*Test.kt
요청 DTO에 검증 제약을 추가했다. 컨트롤러는 64 KiB 초과 요청을 거부하고 IP와 User-Agent를 전달한다. 서비스는 rate limit, 배치 크기, 메시지 길이를 검증하고 로그를 저장한다. User-Agent에서 브라우저와 운영체제를 추출한다.
클라이언트 로그 조회
systems/observability/observability-adapter-in/.../ClientLogController.kt, systems/observability/observability-adapter-in/.../ClientLogPageResponse.kt, systems/observability/observability-application/.../GetClientLogsUseCase.kt, systems/observability/observability-application/.../ClientLogQueryService.kt, systems/observability/.../ClientLogQueryServiceTest.kt
로그 레벨과 커서를 변환한다. 조회 기본 기간을 최근 1시간으로 설정한다. 조회 범위를 최대 7일, 페이지 크기를 1~100으로 제한한다. 결과를 페이지 응답 DTO로 변환한다.
서버 로그 조회
systems/observability/observability-application/.../ServerLogStorePort.kt, systems/observability/observability-application/.../GetServerLogsUseCase.kt, systems/observability/observability-application/.../ServerLogQueryService.kt, systems/observability/observability-adapter-in/.../ServerLogController.kt, systems/observability/observability-adapter-in/.../ServerLogPageResponse.kt, systems/observability/observability-adapter-out/.../EmptyServerLogStoreAdapter.kt, systems/observability/.../ServerLogQueryServiceTest.kt
서버 로그 조회 포트와 상태 필터 타입을 추가했다. 서비스명, 상태, 시간 범위, 페이지 크기와 커서를 처리한다. 3xx 형식 상태 클래스와 정확한 상태 코드를 지원한다. 기본 저장소 어댑터는 빈 페이지를 반환한다.
스토리지 사용량 조회
systems/observability/observability-application/.../GetStorageUsageUseCase.kt, systems/observability/observability-application/.../StorageUsageResult.kt, systems/observability/observability-application/.../StorageUsageQueryService.kt, systems/observability/observability-adapter-in/.../ResourceController.kt, systems/observability/observability-adapter-in/.../StorageUsageResponse.kt, systems/observability/.../StorageUsageQueryServiceTest.kt
스토리지 사용량 조회 계약과 서비스를 추가했다. 데이터베이스 사용률은 전체 용량이 양수일 때 계산한다. 결과에 버킷 지표와 300초 캐시 TTL을 포함한다. API 응답 DTO로 변환한다.

Estimated code review effort: 4 (Complex) | ~45 minutes

Suggested labels: feature, kotlin, test, security

🚥 Pre-merge checks | ✅ 8 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 2.70% which is insufficient. The required threshold is 85.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Behavior Change Needs Tests ⚠️ Warning PR diff에서 observability-adapter-in의 컨트롤러·매퍼 10개와 adapter-out 어댑터가 추가됐지만 두 subsystem의 테스트 변경은 0개입니다. 불필요 사유도 없습니다. 두 adapter subsystem에 payload·IP·파라미터·응답 매핑과 EmptyServerLogStoreAdapter의 빈 페이지 동작을 검증하는 Kotlin 테스트를 추가하세요.
✅ Passed checks (8 passed)
Check name Status Explanation
Title check ✅ Passed 제목이 Conventional Commit 형식인 feat(observability): <subject>를 따르며 변경 내용을 정확히 설명하고 관련 이슈 #29를 포함합니다.
Description check ✅ Passed 설명이 클라이언트·서버 로그 및 스토리지 사용량 API 변경, 구현 세부 사항과 테스트 결과를 구체적으로 설명합니다.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Kotlin Layer Boundary ✅ Passed 변경된 domain 파일에는 adapter/bootstrap import가 없습니다. application의 Spring @Service는 기존 서비스 패턴과 의존성 등록 지침에 따라 UseCase 빈 주입에 사용되어 정당화됩니다.
Go Error Context ✅ Passed PR의 4개 기능 커밋은 Kotlin 파일만 변경했으며, HEAD 트리와 변경 경로에 Go 파일이 없습니다. 따라서 Go 오류 래핑 검사는 적용되지 않습니다.
Bazel Formatting ✅ Passed PR base 브랜치와 HEAD의 diff 30개 파일에 BUILD.bazel 또는 .bzl 파일이 없습니다. 따라서 Bazel 포맷팅 및 안정적 target naming 검사는 적용되지 않습니다.
Todo Must Reference Issue ✅ Passed PR 집계 diff의 추가 라인과 변경 파일에서 TODO/FIXME 주석을 찾지 못했습니다. 따라서 이슈 참조 누락 조건이 발생하지 않았습니다.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/29-observability-03-logs-resources
  • 🛠️ cleanup stale imports
  • 🛠️ harden error messages
  • 🛠️ test clarity pass

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.

@tlgms

tlgms commented Aug 12, 2026

Copy link
Copy Markdown
Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 12

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/controller/ClientLogCollectController.kt`:
- Around line 53-58: Update the clientIp function to use X-Forwarded-For only
when the request originates from a configured trusted proxy and the header is
normalized by that proxy; otherwise, always use request.remoteAddr. Reuse the
existing trusted-proxy configuration or validation mechanism rather than
trusting the header for every request.
- Around line 28-34: Move the 64KB payload enforcement out of
ClientLogCollectController’s post-deserialization check and into a request-level
mechanism that runs before `@RequestBody` processing, such as the proxy/servlet
container limit or a body-counting filter. Ensure both declared Content-Length
and chunked requests are rejected with ErrorCode.PAYLOAD_TOO_LARGE when the
request body exceeds MAX_PAYLOAD_BYTES.

In
`@systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/controller/ClientLogController.kt`:
- Around line 20-33: 컨트롤러의 입력 변환 동작을 검증하는 테스트를 추가하십시오. ClientLogController의 logs
메서드를 직접 실행해 쉼표로 구분된 다중 level이 올바르게 변환되고 cursor가 디코딩되어 use case에 전달되는지 의미 있는 검증을
작성하십시오. 또한 잘못된 LogLevel과 잘못된 cursor 입력이 각각 예상되는 오류 코드로 처리되는 테스트를 추가하십시오.

In
`@systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/dto/request/ClientLogCollectRequest.kt`:
- Line 12: Update the logs property in ClientLogCollectRequest with field-level
nested validation so each ClientLogItemRequest is validated, and add an MVC test
confirming entries with empty message or pageUrl are rejected with a 4xx
response before reaching the service.

In
`@systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/ClientLogCollectionService.kt`:
- Around line 12-14: Remove the Spring-specific import and `@Service` annotation
from ClientLogCollectionService so the observability-application module remains
framework-independent. Move its bean registration to the appropriate adapter or
bootstrap configuration while preserving the service’s existing use-case
behavior.

In
`@systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/ClientLogQueryService.kt`:
- Around line 13-19: Remove the Spring `Service` import and `@Service`
annotation from `ClientLogQueryService`; register this use-case implementation
in the bootstrap or adapter composition point instead, keeping the application
module free of direct Spring dependencies.
- Around line 22-25: ClientLogQueryService의 from 기본값 계산을 현재 시각 now가 아닌
resolvedTo 기준으로 변경하십시오. to가 지정되지 않은 경우에도 기존처럼 현재 시각을 기준으로 유지하고, to만 지정된 과거 조회에서는
resolvedFrom이 resolvedTo.minus(DEFAULT_LOOKBACK)이 되도록 하십시오. 해당 조회가 저장소에 [to -
1시간, to] 범위를 전달하는지 검증하는 테스트를 추가하십시오.

In
`@systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/ServerLogQueryService.kt`:
- Around line 40-47: Update ServerLogQueryService.parseStatus to accept status
classes only when the leading digit is 1 through 5, and exact status codes only
within 100..599; reject 0xx, 6xx, 99, 600, and -1 with
MonitorDomainException(ErrorCode.INVALID_PAYLOAD). Add corresponding
out-of-range rejection tests in ServerLogQueryServiceTest at lines 36-60.
- Around line 14-16: Remove the org.springframework.stereotype.Service import
and `@Service` annotation from ServerLogQueryService, keeping it as a plain Kotlin
class. Register ServerLogQueryService as a bean through the
observability-bootstrap configuration instead, so the observability-application
module no longer depends directly on Spring.

In
`@systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/StorageUsageQueryService.kt`:
- Around line 8-14: StorageUsageQueryService에서
org.springframework.stereotype.Service import와 `@Service` 애노테이션을 제거해 애플리케이션 계층의
Spring 의존성을 없애세요. Spring bean 등록은 adapter 또는 bootstrap 구성으로 이동하고,
StorageUsageQueryService는 현재처럼 GetStorageUsageUseCase와 StorageUsagePort만 의존하도록
유지하세요.

In
`@systems/observability/observability-application/src/test/kotlin/hs/kr/entrydsm/observability/application/ClientLogCollectionServiceTest.kt`:
- Around line 43-59: Update rejectsEmptyOrOversizedBatch and
rateLimitExceededThrowsTooManyRequests to capture the thrown
MonitorDomainException and assert its ErrorCode, using INVALID_PAYLOAD for empty
or oversized batches and TOO_MANY_REQUESTS for the rate-limit case. Keep the
existing exception-type assertions while adding these scenario-specific code
checks.

In
`@systems/observability/observability-application/src/test/kotlin/hs/kr/entrydsm/observability/application/ClientLogQueryServiceTest.kt`:
- Around line 22-26: Update defaultsToOneHourLookbackAndClampsSize and
FakeClientLogStorePort so list captures its from, to, and size arguments instead
of only returning totalCount. Using the fixed clock, assert from is exactly one
hour before the current time, to equals the current time, and size equals 100;
retain deterministic inputs and meaningful argument-based assertions.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: d52e50b5-2611-44d5-925b-0f2a5ad227a6

📥 Commits

Reviewing files that changed from the base of the PR and between 4e1b95b and 5ce0b76.

📒 Files selected for processing (30)
  • systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/controller/ClientLogCollectController.kt
  • systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/controller/ClientLogController.kt
  • systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/controller/ResourceController.kt
  • systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/controller/ServerLogController.kt
  • systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/dto/common/ResponseMapper.kt
  • systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/dto/request/ClientLogCollectRequest.kt
  • systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/dto/response/ClientLogAcceptResponse.kt
  • systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/dto/response/ClientLogPageResponse.kt
  • systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/dto/response/ServerLogPageResponse.kt
  • systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/dto/response/StorageUsageResponse.kt
  • systems/observability/observability-adapter-out/src/main/kotlin/hs/kr/entrydsm/observability/adapterout/log/EmptyServerLogStoreAdapter.kt
  • systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/ClientLogCollectionService.kt
  • systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/ClientLogQueryService.kt
  • systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/ServerLogQueryService.kt
  • systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/StorageUsageQueryService.kt
  • systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/in/GetClientLogsUseCase.kt
  • systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/in/GetServerLogsUseCase.kt
  • systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/in/GetStorageUsageUseCase.kt
  • systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/in/RecordClientLogUseCase.kt
  • systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/in/result/ClientLogAcceptResult.kt
  • systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/in/result/StorageUsageResult.kt
  • systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/out/ServerLogStorePort.kt
  • systems/observability/observability-application/src/test/kotlin/hs/kr/entrydsm/TestMain.kt
  • systems/observability/observability-application/src/test/kotlin/hs/kr/entrydsm/observability/application/ClientLogCollectionServiceTest.kt
  • systems/observability/observability-application/src/test/kotlin/hs/kr/entrydsm/observability/application/ClientLogQueryServiceTest.kt
  • systems/observability/observability-application/src/test/kotlin/hs/kr/entrydsm/observability/application/ServerLogQueryServiceTest.kt
  • systems/observability/observability-application/src/test/kotlin/hs/kr/entrydsm/observability/application/StorageUsageQueryServiceTest.kt
  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/service/UserAgentParser.kt
  • systems/observability/observability-domain/src/test/kotlin/hs/kr/entrydsm/TestMain.kt
  • systems/observability/observability-domain/src/test/kotlin/hs/kr/entrydsm/observability/domain/UserAgentParserTest.kt
📜 Review details
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{kt,go}

📄 CodeRabbit inference engine (Custom checks)

If production logic is changed in Kotlin or Go files, require corresponding test updates in the same subsystem unless the PR description explicitly justifies why tests are unnecessary

Files:

  • systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/in/GetStorageUsageUseCase.kt
  • systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/in/result/ClientLogAcceptResult.kt
  • systems/observability/observability-domain/src/test/kotlin/hs/kr/entrydsm/TestMain.kt
  • systems/observability/observability-application/src/test/kotlin/hs/kr/entrydsm/observability/application/StorageUsageQueryServiceTest.kt
  • systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/dto/response/ClientLogAcceptResponse.kt
  • systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/in/GetClientLogsUseCase.kt
  • systems/observability/observability-application/src/test/kotlin/hs/kr/entrydsm/observability/application/ServerLogQueryServiceTest.kt
  • systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/in/GetServerLogsUseCase.kt
  • systems/observability/observability-application/src/test/kotlin/hs/kr/entrydsm/observability/application/ClientLogQueryServiceTest.kt
  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/service/UserAgentParser.kt
  • systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/ServerLogQueryService.kt
  • systems/observability/observability-application/src/test/kotlin/hs/kr/entrydsm/TestMain.kt
  • systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/ClientLogQueryService.kt
  • systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/in/result/StorageUsageResult.kt
  • systems/observability/observability-domain/src/test/kotlin/hs/kr/entrydsm/observability/domain/UserAgentParserTest.kt
  • systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/controller/ClientLogController.kt
  • systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/controller/ClientLogCollectController.kt
  • systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/in/RecordClientLogUseCase.kt
  • systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/controller/ResourceController.kt
  • systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/out/ServerLogStorePort.kt
  • systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/dto/response/ClientLogPageResponse.kt
  • systems/observability/observability-adapter-out/src/main/kotlin/hs/kr/entrydsm/observability/adapterout/log/EmptyServerLogStoreAdapter.kt
  • systems/observability/observability-application/src/test/kotlin/hs/kr/entrydsm/observability/application/ClientLogCollectionServiceTest.kt
  • systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/ClientLogCollectionService.kt
  • systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/controller/ServerLogController.kt
  • systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/dto/response/ServerLogPageResponse.kt
  • systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/dto/response/StorageUsageResponse.kt
  • systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/StorageUsageQueryService.kt
  • systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/dto/common/ResponseMapper.kt
  • systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/dto/request/ClientLogCollectRequest.kt
**/*-application/**/*.{java,kt,scala,groovy}

📄 CodeRabbit inference engine (Custom checks)

For files under *-application modules, flag direct dependency on infrastructure-specific framework classes unless justified

Files:

  • systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/in/GetStorageUsageUseCase.kt
  • systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/in/result/ClientLogAcceptResult.kt
  • systems/observability/observability-application/src/test/kotlin/hs/kr/entrydsm/observability/application/StorageUsageQueryServiceTest.kt
  • systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/in/GetClientLogsUseCase.kt
  • systems/observability/observability-application/src/test/kotlin/hs/kr/entrydsm/observability/application/ServerLogQueryServiceTest.kt
  • systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/in/GetServerLogsUseCase.kt
  • systems/observability/observability-application/src/test/kotlin/hs/kr/entrydsm/observability/application/ClientLogQueryServiceTest.kt
  • systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/ServerLogQueryService.kt
  • systems/observability/observability-application/src/test/kotlin/hs/kr/entrydsm/TestMain.kt
  • systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/ClientLogQueryService.kt
  • systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/in/result/StorageUsageResult.kt
  • systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/in/RecordClientLogUseCase.kt
  • systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/out/ServerLogStorePort.kt
  • systems/observability/observability-application/src/test/kotlin/hs/kr/entrydsm/observability/application/ClientLogCollectionServiceTest.kt
  • systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/ClientLogCollectionService.kt
  • systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/StorageUsageQueryService.kt
**/*.{java,kt,scala,groovy,go,js,ts,tsx,jsx,py,rb,rs,cpp,c,h,hpp,cs}

📄 CodeRabbit inference engine (Custom checks)

Flag TODO/FIXME comments introduced by this PR that do not include an issue reference in the form #123 or a full tracker key like PROJ-123

Files:

  • systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/in/GetStorageUsageUseCase.kt
  • systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/in/result/ClientLogAcceptResult.kt
  • systems/observability/observability-domain/src/test/kotlin/hs/kr/entrydsm/TestMain.kt
  • systems/observability/observability-application/src/test/kotlin/hs/kr/entrydsm/observability/application/StorageUsageQueryServiceTest.kt
  • systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/dto/response/ClientLogAcceptResponse.kt
  • systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/in/GetClientLogsUseCase.kt
  • systems/observability/observability-application/src/test/kotlin/hs/kr/entrydsm/observability/application/ServerLogQueryServiceTest.kt
  • systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/in/GetServerLogsUseCase.kt
  • systems/observability/observability-application/src/test/kotlin/hs/kr/entrydsm/observability/application/ClientLogQueryServiceTest.kt
  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/service/UserAgentParser.kt
  • systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/ServerLogQueryService.kt
  • systems/observability/observability-application/src/test/kotlin/hs/kr/entrydsm/TestMain.kt
  • systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/ClientLogQueryService.kt
  • systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/in/result/StorageUsageResult.kt
  • systems/observability/observability-domain/src/test/kotlin/hs/kr/entrydsm/observability/domain/UserAgentParserTest.kt
  • systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/controller/ClientLogController.kt
  • systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/controller/ClientLogCollectController.kt
  • systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/in/RecordClientLogUseCase.kt
  • systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/controller/ResourceController.kt
  • systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/out/ServerLogStorePort.kt
  • systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/dto/response/ClientLogPageResponse.kt
  • systems/observability/observability-adapter-out/src/main/kotlin/hs/kr/entrydsm/observability/adapterout/log/EmptyServerLogStoreAdapter.kt
  • systems/observability/observability-application/src/test/kotlin/hs/kr/entrydsm/observability/application/ClientLogCollectionServiceTest.kt
  • systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/ClientLogCollectionService.kt
  • systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/controller/ServerLogController.kt
  • systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/dto/response/ServerLogPageResponse.kt
  • systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/dto/response/StorageUsageResponse.kt
  • systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/StorageUsageQueryService.kt
  • systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/dto/common/ResponseMapper.kt
  • systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/dto/request/ClientLogCollectRequest.kt
**/*.kt

⚙️ CodeRabbit configuration file

**/*.kt: Apply Kotlin Official Coding Conventions.

Formatting and structure:

  • Use 4 spaces for indentation; no tabs.
  • Keep files focused and readable; avoid horizontal alignment for spacing.
  • Place related declarations together and keep overloads adjacent.
  • Keep implementation member order stable and logical for readability.

Naming:

  • Package names are lowercase and do not use underscores.
  • Class/object names use UpperCamelCase.
  • Functions/properties/local variables use lowerCamelCase.
  • Constants use UPPER_SNAKE_CASE only for true constants.

API and null-safety:

  • Avoid platform type leakage in public APIs.
  • Use explicit types in public APIs when inference obscures meaning.
  • Prefer immutable values (val) over mutable values (var) unless mutation is required.
  • Flag nullable flows that can be replaced with safer modeling.

Imports and idioms:

  • Avoid wildcard imports unless justified by language/tooling conventions.
  • Prefer expression bodies for short, clear functions.
  • Prefer standard library idioms over custom utility wrappers when equivalent.

Architecture and tests:

  • Respect module boundaries (domain/application/adapter/bootstrap layering).
  • Highlight behavior-changing code that lacks corresponding unit/integration tests.
  • Ask for deterministic tests and meaningful assertions, not only happy-path checks.

Files:

  • systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/in/GetStorageUsageUseCase.kt
  • systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/in/result/ClientLogAcceptResult.kt
  • systems/observability/observability-domain/src/test/kotlin/hs/kr/entrydsm/TestMain.kt
  • systems/observability/observability-application/src/test/kotlin/hs/kr/entrydsm/observability/application/StorageUsageQueryServiceTest.kt
  • systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/dto/response/ClientLogAcceptResponse.kt
  • systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/in/GetClientLogsUseCase.kt
  • systems/observability/observability-application/src/test/kotlin/hs/kr/entrydsm/observability/application/ServerLogQueryServiceTest.kt
  • systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/in/GetServerLogsUseCase.kt
  • systems/observability/observability-application/src/test/kotlin/hs/kr/entrydsm/observability/application/ClientLogQueryServiceTest.kt
  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/service/UserAgentParser.kt
  • systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/ServerLogQueryService.kt
  • systems/observability/observability-application/src/test/kotlin/hs/kr/entrydsm/TestMain.kt
  • systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/ClientLogQueryService.kt
  • systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/in/result/StorageUsageResult.kt
  • systems/observability/observability-domain/src/test/kotlin/hs/kr/entrydsm/observability/domain/UserAgentParserTest.kt
  • systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/controller/ClientLogController.kt
  • systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/controller/ClientLogCollectController.kt
  • systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/in/RecordClientLogUseCase.kt
  • systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/controller/ResourceController.kt
  • systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/out/ServerLogStorePort.kt
  • systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/dto/response/ClientLogPageResponse.kt
  • systems/observability/observability-adapter-out/src/main/kotlin/hs/kr/entrydsm/observability/adapterout/log/EmptyServerLogStoreAdapter.kt
  • systems/observability/observability-application/src/test/kotlin/hs/kr/entrydsm/observability/application/ClientLogCollectionServiceTest.kt
  • systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/ClientLogCollectionService.kt
  • systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/controller/ServerLogController.kt
  • systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/dto/response/ServerLogPageResponse.kt
  • systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/dto/response/StorageUsageResponse.kt
  • systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/StorageUsageQueryService.kt
  • systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/dto/common/ResponseMapper.kt
  • systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/dto/request/ClientLogCollectRequest.kt
**/*-domain/**/*.{java,kt,scala,groovy}

📄 CodeRabbit inference engine (Custom checks)

For files under *-domain modules, fail if imports reference adapter or bootstrap packages

Files:

  • systems/observability/observability-domain/src/test/kotlin/hs/kr/entrydsm/TestMain.kt
  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/service/UserAgentParser.kt
  • systems/observability/observability-domain/src/test/kotlin/hs/kr/entrydsm/observability/domain/UserAgentParserTest.kt
🪛 detekt (1.23.8)
systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/ServerLogQueryService.kt

[warning] 43-43: This condition is too complex (4). Defined complexity threshold for conditions is set to '4'

(detekt.complexity.ComplexCondition)

🔇 Additional comments (20)
systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/in/RecordClientLogUseCase.kt (1)

1-19: LGTM!

systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/in/result/ClientLogAcceptResult.kt (1)

1-3: LGTM!

systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/dto/response/ClientLogAcceptResponse.kt (1)

1-3: LGTM!

systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/service/UserAgentParser.kt (1)

1-32: LGTM!

systems/observability/observability-domain/src/test/kotlin/hs/kr/entrydsm/observability/domain/UserAgentParserTest.kt (1)

1-30: LGTM!

systems/observability/observability-domain/src/test/kotlin/hs/kr/entrydsm/TestMain.kt (1)

13-13: LGTM!

systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/dto/common/ResponseMapper.kt (1)

5-8: LGTM!

Also applies to: 19-37, 54-56, 140-196

systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/in/GetClientLogsUseCase.kt (1)

8-11: LGTM!

systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/dto/response/ClientLogPageResponse.kt (1)

7-27: LGTM!

systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/in/GetStorageUsageUseCase.kt (1)

5-7: LGTM!

systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/in/result/StorageUsageResult.kt (1)

5-22: LGTM!

systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/controller/ResourceController.kt (1)

10-16: LGTM!

systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/dto/response/StorageUsageResponse.kt (1)

5-22: LGTM!

systems/observability/observability-application/src/test/kotlin/hs/kr/entrydsm/observability/application/StorageUsageQueryServiceTest.kt (1)

9-42: LGTM!

systems/observability/observability-application/src/test/kotlin/hs/kr/entrydsm/TestMain.kt (1)

12-15: LGTM!

systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/out/ServerLogStorePort.kt (1)

7-42: LGTM!

systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/in/GetServerLogsUseCase.kt (1)

8-16: LGTM!

systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/controller/ServerLogController.kt (1)

16-36: LGTM!

systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/dto/response/ServerLogPageResponse.kt (1)

6-25: LGTM!

systems/observability/observability-adapter-out/src/main/kotlin/hs/kr/entrydsm/observability/adapterout/log/EmptyServerLogStoreAdapter.kt (1)

11-24: LGTM!

Comment on lines +28 to +34
@Valid @RequestBody request: ClientLogCollectRequest,
@RequestHeader(value = "User-Agent", required = false) userAgent: String?,
httpRequest: HttpServletRequest,
): ResponseEntity<ApiResponse<ClientLogAcceptResponse>> {
if (httpRequest.contentLengthLong > MAX_PAYLOAD_BYTES) {
throw MonitorDomainException(ErrorCode.PAYLOAD_TOO_LARGE)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

64KB 제한을 본문 역직렬화 전에 적용해야 합니다.

컨트롤러 메서드가 실행될 때는 @RequestBody가 이미 역직렬화되었습니다. 따라서 큰 본문은 제한 확인 전에 서버 메모리와 CPU를 사용합니다. 또한 청크 전송 요청은 contentLengthLong == -1이므로 현재 검사를 우회합니다. 프록시 또는 서블릿 컨테이너의 요청 크기 제한, 또는 본문 읽기 전 크기를 세는 필터를 사용하십시오.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/controller/ClientLogCollectController.kt`
around lines 28 - 34, Move the 64KB payload enforcement out of
ClientLogCollectController’s post-deserialization check and into a request-level
mechanism that runs before `@RequestBody` processing, such as the proxy/servlet
container limit or a body-counting filter. Ensure both declared Content-Length
and chunked requests are rejected with ErrorCode.PAYLOAD_TOO_LARGE when the
request body exceeds MAX_PAYLOAD_BYTES.

Comment on lines +53 to +58
private fun clientIp(request: HttpServletRequest): String =
request.getHeader("X-Forwarded-For")
?.substringBefore(",")
?.trim()
?.takeIf { it.isNotBlank() }
?: 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.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

X-Forwarded-For는 신뢰된 프록시에서만 사용해야 합니다.

외부 클라이언트가 이 헤더를 임의 값으로 설정할 수 있습니다. 그러면 요청마다 다른 clientIp 레이트 리밋 키를 만들어 IP 기반 제한을 우회할 수 있습니다. 신뢰된 프록시에서 정규화한 전달 헤더만 사용하십시오. 그 외 연결에서는 remoteAddr를 사용하십시오.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/controller/ClientLogCollectController.kt`
around lines 53 - 58, Update the clientIp function to use X-Forwarded-For only
when the request originates from a configured trusted proxy and the header is
normalized by that proxy; otherwise, always use request.remoteAddr. Reuse the
existing trusted-proxy configuration or validation mechanism rather than
trusting the header for every request.

Comment on lines +20 to +33
@GetMapping("/api/monitor/v11/logs/client")
fun logs(
@RequestParam(required = false) level: String?,
@RequestParam(required = false) from: Instant?,
@RequestParam(required = false) to: Instant?,
@RequestParam(defaultValue = "20") size: Int,
@RequestParam(required = false) cursor: String?,
): ApiResponse<ClientLogPageResponse> {
val levels = level?.split(",")?.map {
runCatching { LogLevel.valueOf(it.trim()) }.getOrElse { throw MonitorDomainException(ErrorCode.INVALID_PAYLOAD) }
}?.toSet()
val decodedCursor = cursor?.let { Cursor.decode(it) }
val page = getClientLogsUseCase.getLogs(levels, from, to, size, decodedCursor)
return ApiResponse(data = page.toResponse())

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

컨트롤러 입력 변환을 검증하는 테스트를 추가하십시오.

제공된 ClientLogQueryServiceTest는 이 컨트롤러를 실행하지 않습니다. 따라서 level 분할, 잘못된 LogLevel 처리, cursor 디코딩을 검증하지 않습니다.

유효한 다중 level과 cursor 전달을 검증하십시오. 잘못된 level과 cursor가 각각 예상 오류 코드로 처리되는 테스트를 추가하십시오.

As per coding guidelines, Kotlin production logic changes require corresponding test updates in the same subsystem. As per path instructions, behavior-changing code must have corresponding unit or integration tests with meaningful assertions.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/controller/ClientLogController.kt`
around lines 20 - 33, 컨트롤러의 입력 변환 동작을 검증하는 테스트를 추가하십시오. ClientLogController의
logs 메서드를 직접 실행해 쉼표로 구분된 다중 level이 올바르게 변환되고 cursor가 디코딩되어 use case에 전달되는지 의미 있는
검증을 작성하십시오. 또한 잘못된 LogLevel과 잘못된 cursor 입력이 각각 예상되는 오류 코드로 처리되는 테스트를 추가하십시오.

Sources: Coding guidelines, Path instructions


data class ClientLogCollectRequest(
@field:NotBlank val sessionId: String,
@field:NotEmpty val logs: List<ClientLogItemRequest>,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

logs 항목 검증을 전파해야 합니다.

현재 @Valid는 루트 ClientLogCollectRequest만 검증합니다. logs@field:Valid가 없으므로 빈 message 또는 빈 pageUrl을 포함한 항목이 서비스까지 전달됩니다. 중첩 항목 검증을 추가하고, 빈 항목이 4xx로 거부되는 MVC 테스트를 추가하십시오.

수정 예시
+import jakarta.validation.Valid
 import jakarta.validation.constraints.NotBlank
 ...
-    `@field`:NotEmpty val logs: List<ClientLogItemRequest>,
+    `@field`:NotEmpty
+    `@field`:Valid
+    val logs: List<ClientLogItemRequest>,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/dto/request/ClientLogCollectRequest.kt`
at line 12, Update the logs property in ClientLogCollectRequest with field-level
nested validation so each ClientLogItemRequest is validated, and add an MVC test
confirming entries with empty message or pageUrl are rejected with a 4xx
response before reaching the service.

Comment on lines +12 to +14
import org.springframework.stereotype.Service

@Service

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

애플리케이션 계층에서 Spring 의존성을 제거해야 합니다.

observability-application 모듈이 org.springframework.stereotype.Service@Service에 직접 의존합니다. 이 클래스는 순수한 유스케이스 구현으로 유지하십시오. Spring bean 등록은 adapter 또는 bootstrap 구성으로 이동하십시오.

As per coding guidelines: "For files under *-application modules, flag direct dependency on infrastructure-specific framework classes unless justified." As per path instructions: "Respect module boundaries (domain/application/adapter/bootstrap layering)."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/ClientLogCollectionService.kt`
around lines 12 - 14, Remove the Spring-specific import and `@Service` annotation
from ClientLogCollectionService so the observability-application module remains
framework-independent. Move its bean registration to the appropriate adapter or
bootstrap configuration while preserving the service’s existing use-case
behavior.

Sources: Coding guidelines, Path instructions

Comment on lines +14 to +16
import org.springframework.stereotype.Service

@Service

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 3 'ServerLogQueryService|`@Service`|`@Bean`|ComponentScan' systems/observability

Repository: EntryDSM/entrydsm-platform

Length of output: 13049


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- application module build files ---'
find systems/observability -maxdepth 3 -type f \( -name 'build.gradle*' -o -name 'pom.xml' -o -name 'settings.gradle*' \) -print

printf '%s\n' '--- ServerLogQueryService and related registrations ---'
rg -n -C 5 'ServerLogQueryService|GetServerLogsUseCase|`@Service`|`@Bean`|ComponentScan' systems/observability

printf '%s\n' '--- Spring dependencies in observability modules ---'
rg -n -C 3 'spring|framework' systems/observability --glob 'build.gradle*' --glob 'pom.xml'

Repository: EntryDSM/entrydsm-platform

Length of output: 27894


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- Gradle and Maven files ---'
git ls-files | grep -E '(^|/)(build\.gradle(\.kts)?|settings\.gradle(\.kts)?|pom\.xml)$' || true

printf '%s\n' '--- observability module files ---'
git ls-files systems/observability | grep -E '(build\.gradle|settings\.gradle|pom\.xml|Application|Config|Bootstrap|Main)' || true

printf '%s\n' '--- component scanning and bootstrap registrations ---'
rg -n -C 5 'ComponentScan|SpringBootApplication|`@Import`|ServerLogQueryService|GetServerLogsUseCase|observability.application' systems/observability --glob '*.kt' --glob '*.java'

printf '%s\n' '--- module dependency declarations ---'
rg -n -C 5 'observability-application|spring-boot-starter|spring-context|spring-framework|implementation|api' . --glob 'build.gradle*' --glob 'pom.xml' --glob '!build/**' --glob '!**/node_modules/**'

Repository: EntryDSM/entrydsm-platform

Length of output: 50383


observability-application 모듈에서 Spring 의존성을 제거하십시오.

ServerLogQueryServiceorg.springframework.stereotype.Service@Service를 직접 사용합니다. 서비스를 일반 Kotlin 클래스로 유지하고, 빈 등록을 observability-bootstrap의 구성으로 이동하십시오.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/ServerLogQueryService.kt`
around lines 14 - 16, Remove the org.springframework.stereotype.Service import
and `@Service` annotation from ServerLogQueryService, keeping it as a plain Kotlin
class. Register ServerLogQueryService as a bean through the
observability-bootstrap configuration instead, so the observability-application
module no longer depends directly on Spring.

Source: Coding guidelines

Comment on lines +40 to +47
private fun parseStatus(status: String?): StatusFilter? {
if (status == null) return null
val trimmed = status.trim()
if (trimmed.length == 3 && trimmed[1] == 'x' && trimmed[2] == 'x' && trimmed[0].isDigit()) {
return StatusFilter.StatusClass(trimmed[0])
}
return trimmed.toIntOrNull()?.let { StatusFilter.Exact(it) }
?: throw MonitorDomainException(ErrorCode.INVALID_PAYLOAD)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

HTTP 상태 값의 유효 범위를 검증하십시오.

현재 0xx, 6xx, 99, 600, -1 같은 값을 허용합니다. 이 값은 유효한 HTTP 상태 필터가 아닙니다. 상태 클래스는 1xx부터 5xx까지만 허용하고, 정확한 상태 코드는 100..599로 제한하십시오. 0xx, 6xx, 범위 밖 정수의 거부 테스트도 추가하십시오.

  • systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/ServerLogQueryService.kt#L40-L47: 상태 클래스와 정확한 상태 코드의 허용 범위를 검사하십시오.
  • systems/observability/observability-application/src/test/kotlin/hs/kr/entrydsm/observability/application/ServerLogQueryServiceTest.kt#L36-L60: 범위를 벗어난 상태 입력이 MonitorDomainException을 발생시키는 테스트를 추가하십시오.
수정 예시
 private fun parseStatus(status: String?): StatusFilter? {
-    if (status == null) return null
-    val trimmed = status.trim()
-    if (trimmed.length == 3 && trimmed[1] == 'x' && trimmed[2] == 'x' && trimmed[0].isDigit()) {
-        return StatusFilter.StatusClass(trimmed[0])
-    }
-    return trimmed.toIntOrNull()?.let { StatusFilter.Exact(it) }
-        ?: throw MonitorDomainException(ErrorCode.INVALID_PAYLOAD)
+    val trimmed = status?.trim() ?: return null
+    val code = trimmed.toIntOrNull()
+    return when {
+        trimmed.length == 3 && trimmed.substring(1) == "xx" && trimmed[0] in '1'..'5' ->
+            StatusFilter.StatusClass(trimmed[0])
+        code != null && code in 100..599 ->
+            StatusFilter.Exact(code)
+        else -> throw MonitorDomainException(ErrorCode.INVALID_PAYLOAD)
+    }
 }
📝 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
private fun parseStatus(status: String?): StatusFilter? {
if (status == null) return null
val trimmed = status.trim()
if (trimmed.length == 3 && trimmed[1] == 'x' && trimmed[2] == 'x' && trimmed[0].isDigit()) {
return StatusFilter.StatusClass(trimmed[0])
}
return trimmed.toIntOrNull()?.let { StatusFilter.Exact(it) }
?: throw MonitorDomainException(ErrorCode.INVALID_PAYLOAD)
private fun parseStatus(status: String?): StatusFilter? {
val trimmed = status?.trim() ?: return null
val code = trimmed.toIntOrNull()
return when {
trimmed.length == 3 && trimmed.substring(1) == "xx" && trimmed[0] in '1'..'5' ->
StatusFilter.StatusClass(trimmed[0])
code != null && code in 100..599 ->
StatusFilter.Exact(code)
else -> throw MonitorDomainException(ErrorCode.INVALID_PAYLOAD)
}
}
🧰 Tools
🪛 detekt (1.23.8)

[warning] 43-43: This condition is too complex (4). Defined complexity threshold for conditions is set to '4'

(detekt.complexity.ComplexCondition)

📍 Affects 2 files
  • systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/ServerLogQueryService.kt#L40-L47 (this comment)
  • systems/observability/observability-application/src/test/kotlin/hs/kr/entrydsm/observability/application/ServerLogQueryServiceTest.kt#L36-L60
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/ServerLogQueryService.kt`
around lines 40 - 47, Update ServerLogQueryService.parseStatus to accept status
classes only when the leading digit is 1 through 5, and exact status codes only
within 100..599; reject 0xx, 6xx, 99, 600, and -1 with
MonitorDomainException(ErrorCode.INVALID_PAYLOAD). Add corresponding
out-of-range rejection tests in ServerLogQueryServiceTest at lines 36-60.

Source: Linters/SAST tools

Comment on lines +8 to +14
import org.springframework.stereotype.Service

/** ponytail: 실시간으로 측정하므로 서버 자체 캐시는 두지 않는다. cacheTtlSeconds는 클라이언트에 권장하는 캐시 기간이다. */
@Service
class StorageUsageQueryService(
private val storageUsagePort: StorageUsagePort,
) : GetStorageUsageUseCase {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

애플리케이션 계층에서 Spring 의존성을 제거하세요.

StorageUsageQueryServiceorg.springframework.stereotype.Service@Service에 직접 의존합니다. 이 의존성은 애플리케이션 계층을 Spring 구성에 결합합니다. Spring bean 등록을 adapter 또는 bootstrap 구성으로 이동하고, 이 클래스는 입력 포트와 출력 포트에만 의존하게 하세요.

As per coding guidelines, "For files under *-application modules, flag direct dependency on infrastructure-specific framework classes unless justified".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/StorageUsageQueryService.kt`
around lines 8 - 14, StorageUsageQueryService에서
org.springframework.stereotype.Service import와 `@Service` 애노테이션을 제거해 애플리케이션 계층의
Spring 의존성을 없애세요. Spring bean 등록은 adapter 또는 bootstrap 구성으로 이동하고,
StorageUsageQueryService는 현재처럼 GetStorageUsageUseCase와 StorageUsagePort만 의존하도록
유지하세요.

Source: Coding guidelines

Comment on lines +43 to +59
fun rejectsEmptyOrOversizedBatch() {
val service = ClientLogCollectionService(store, FakeRateLimitPort(true))

assertThrows(MonitorDomainException::class.java) { service.record("sess_1", emptyList(), null, "127.0.0.1") }
assertThrows(MonitorDomainException::class.java) {
service.record("sess_1", List(21) { item() }, null, "127.0.0.1")
}
}

@Test
fun rateLimitExceededThrowsTooManyRequests() {
val service = ClientLogCollectionService(store, FakeRateLimitPort(false))

assertThrows(MonitorDomainException::class.java) {
service.record("sess_1", listOf(item()), null, "127.0.0.1")
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

예외 코드도 검증해야 합니다.

현재 테스트는 MonitorDomainException 타입만 검증합니다. INVALID_PAYLOADTOO_MANY_REQUESTS가 아닌 오류 코드가 발생해도 테스트가 통과합니다. 예외를 변수로 받은 뒤 각 시나리오의 ErrorCode를 단언하십시오.

As per path instructions: "Ask for deterministic tests and meaningful assertions, not only happy-path checks."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@systems/observability/observability-application/src/test/kotlin/hs/kr/entrydsm/observability/application/ClientLogCollectionServiceTest.kt`
around lines 43 - 59, Update rejectsEmptyOrOversizedBatch and
rateLimitExceededThrowsTooManyRequests to capture the thrown
MonitorDomainException and assert its ErrorCode, using INVALID_PAYLOAD for empty
or oversized batches and TOO_MANY_REQUESTS for the rate-limit case. Keep the
existing exception-type assertions while adding these scenario-specific code
checks.

Source: Path instructions

Comment on lines +22 to +26
fun defaultsToOneHourLookbackAndClampsSize() {
val result = service.getLogs(null, null, null, 500, null)

assertEquals(1, result.totalCount)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

저장소 호출 인자를 검증하십시오.

FakeClientLogStorePort.list는 모든 인자를 무시하고 항상 totalCount = 1을 반환합니다. 따라서 Line 25의 검증은 기본 1시간 범위와 size = 100 제한을 확인하지 않습니다.

Fake에서 from, to, size를 저장하십시오. 고정 clock 기준으로 from이 1시간 전이고 to가 현재 시각이며 size가 100인지 단언하십시오.

As per path instructions, tests must use deterministic inputs and meaningful assertions.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@systems/observability/observability-application/src/test/kotlin/hs/kr/entrydsm/observability/application/ClientLogQueryServiceTest.kt`
around lines 22 - 26, Update defaultsToOneHourLookbackAndClampsSize and
FakeClientLogStorePort so list captures its from, to, and size arguments instead
of only returning totalCount. Using the fixed clock, assert from is exactly one
hour before the current time, to equals the current time, and size equals 100;
retain deterministic inputs and meaningful argument-based assertions.

Source: Path instructions

tlgms and others added 4 commits August 21, 2026 01:50
- 수집 엔드포인트 본문 크기를 역직렬화 전에 필터에서 제한하고 길이를 알 수 없는 요청도 거부한다
- 클라이언트 로그 수집도 ClientIpResolver로 IP를 판정한다
- logs 항목까지 검증이 전파되도록 @Valid를 중첩 타입에 붙인다
- to만 지정한 과거 조회가 거부되지 않도록 기본 from을 to 기준으로 계산한다
- 상태 필터를 1xx~5xx와 100..599로 제한한다
- 남은 유스케이스의 @service를 제거하고 빈 등록을 UseCaseConfig로 옮긴다
- 예외 코드·저장소 전달 인자·거부 시 미기록을 단언하도록 테스트를 보강한다

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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