feat(observability): 세션수집·헬스체크·대시보드·시간대별지표 API 구현 #29 - #70
Conversation
POST /api/monitor/v11/collect/session (ENTER/HEARTBEAT/LEAVE) 컨트롤러 추가. rate limit 초과 시 429, 알 수 없는 sessionId는 404 SESSION_NOT_FOUND로 응답. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
GET /api/monitor/v11/health 구현. JDK HttpClient로 각 서비스의 actuator/health를 호출해 응답시간·의존성 상태를 UP/DEGRADED/DOWN으로 판정. monitor.services.<서비스>.base-url 설정으로 대상 서비스 지정, 로컬 기본값을 application.yaml에 추가. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
GET /api/monitor/v11/dashboard 구현. 트래픽/동시접속/기기분포는 세션 저장소, 서비스별 활동은 헬스체크 재사용. round 파라미터는 monitor.round.name 설정과 비교해 ROUND_NOT_FOUND를 판정한다. 클라이언트 오류 집계(clientLog)와 스토리지 사용량(resource)을 위해 ClientLogStorePort/RedisClientLogStoreAdapter(fingerprint 그룹핑 공용 헬퍼 포함)와 StorageUsagePort/LocalDiskStorageUsageAdapter를 함께 추가 — logs/client, resources API에서 재사용한다. API 요청 수(api)와 원서접수/PDF 통계(business)는 다른 서비스로부터 받는 수집 경로가 문서에 없어 0으로 고정하고 코드에 근거를 남겼다. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
GET /api/monitor/v11/metrics/series 구현. VISITOR는 세션 ENTER/HEARTBEAT 시각을 5분 단위 Redis Set에 기록해두고 조회 시 요청 interval에 맞춰 합집합으로 집계. 빈 버킷은 0으로 채운다. from>to·범위 90일 초과는 INVALID_TIME_RANGE, 지원하지 않는 interval이거나 버킷 1000개 초과는 INVALID_INTERVAL, 알 수 없는 지표명은 INVALID_METRIC. API_REQUEST는 수집 경로가 아직 없어 0으로 고정(대시보드와 동일한 gap). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Caution Review failedFailed to post review comments. We encountered an issue with GitHub. Use ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (46)
🧰 Additional context used📓 Path-based instructions (7)**/{BUILD.bazel,*.bzl}📄 CodeRabbit inference engine (Custom checks)
Files:
**/*.bzl⚙️ CodeRabbit configuration file
Files:
**/*.{kt,go}📄 CodeRabbit inference engine (Custom checks)
Files:
**/*.{java,kt,scala,groovy,go,js,ts,tsx,jsx,py,rb,rs,cpp,c,h,hpp,cs}📄 CodeRabbit inference engine (Custom checks)
Files:
**/*.kt⚙️ CodeRabbit configuration file
Files:
**/*-application/**/*.{java,kt,scala,groovy}📄 CodeRabbit inference engine (Custom checks)
Files:
**/*-domain/**/*.{java,kt,scala,groovy}📄 CodeRabbit inference engine (Custom checks)
Files:
🪛 detekt (1.23.8)systems/observability/observability-adapter-out/src/main/kotlin/hs/kr/entrydsm/observability/adapterout/health/ActuatorHealthCheckAdapter.kt[warning] 45-45: The caught exception is too generic. Prefer catching specific exceptions to the case that is currently handled. (detekt.exceptions.TooGenericExceptionCaught) [warning] 45-45: The caught exception is swallowed. The original exception could be lost. (detekt.exceptions.SwallowedException) 📝 Walkthrough세션 활동, 서비스 상태, 대시보드 지표, 방문자 시계열 데이터를 조회할 수 있는 관측성 API를 제공합니다. 주요 변경 사항
아키텍처 변경
위험 영역
마이그레이션 및 호환성
검증 체크리스트 및 롤아웃
Walkthrough관측성 모니터링 기능을 추가했습니다. 대시보드, 헬스, 메트릭 시계열, 세션 수집 API와 애플리케이션 서비스를 구현했습니다. Redis 저장소, Actuator 헬스 점검, 라운드 및 디스크 사용량 어댑터와 관련 설정을 추가했습니다. Changes관측성 모니터링 기능
Estimated code review effort: 4 (Complex) | ~60 minutes Suggested labels: Sequence Diagram(s)sequenceDiagram
participant DashboardController
participant MonitorDashboardService
participant RoundPort
participant HealthCheckPort
participant ClientLogStorePort
DashboardController->>MonitorDashboardService: getSnapshot(round)
MonitorDashboardService->>RoundPort: current()
MonitorDashboardService->>HealthCheckPort: check(service)
MonitorDashboardService->>ClientLogStorePort: countByLevel(from, to)
MonitorDashboardService-->>DashboardController: DashboardSnapshotResponse
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
|
There was a problem hiding this comment.
Actionable comments posted: 21
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
systems/observability/observability-application/src/test/kotlin/hs/kr/entrydsm/observability/application/SessionCollectionServiceTest.kt (1)
23-28: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winvisitor 기록을 검증하도록 테스트 대역을 변경하세요.
FakeMetricsStorePort.recordVisitor가 무동작이므로SessionCollectionService.kt의 Line 41과 Line 49 호출이 제거되어도 이 테스트는 통과합니다. 기록된(sessionId, at)목록을 저장하세요. 성공한 ENTER와 HEARTBEAT가 각각 올바른 세션 ID와 고정 Clock 시간을 기록하는지 검증하세요. 알 수 없는 HEARTBEAT는 기록하지 않는지도 검증하세요.As per coding guidelines, “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.”
Also applies to: 79-81
🤖 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/SessionCollectionServiceTest.kt` around lines 23 - 28, Update SessionCollectionServiceTest and FakeMetricsStorePort so recordVisitor stores each recorded (sessionId, at) pair instead of being a no-op. Add assertions that successful ENTER and HEARTBEAT calls record the expected session ID with the fixed Clock time, and that an unknown HEARTBEAT records nothing.Sources: Coding guidelines, Path instructions
🤖 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/MetricsSeriesController.kt`:
- Around line 19-31: MetricsSeriesController의 /api/monitor/v11/metrics/series
엔드포인트에 대한 adapter-in 컨트롤러 테스트를 추가하세요. metrics=VISITOR,API_REQUEST 요청이 정상 응답과 예상한
시리즈 데이터를 반환하는지 검증하고, 존재하지 않는 지표명이 포함된 요청은 INVALID_METRIC 오류 응답을 반환하는지 검증하세요.
In
`@systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/controller/SessionCollectController.kt`:
- Around line 37-42: clientIp가 신뢰되지 않은 요청의 X-Forwarded-For를 rate limit 키로 사용하지
않도록 수정하십시오. 요청의 remoteAddr가 기존 신뢰 프록시 설정에 포함된 경우에만 전달 헤더의 정규화된 첫 번째 주소를 사용하고, 그
외에는 항상 request.remoteAddr를 반환하도록 clientIp를 변경하십시오. 신뢰 프록시 및 비신뢰 요청, 헤더 변조 사례를
검증하는 SessionCollectController 테스트를 추가하십시오.
In
`@systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/dto/common/ResponseMapper.kt`:
- Around line 42-122: adapter-in 계약 테스트를 추가하십시오. ResponseMapper.kt 42-122의 변환
함수들은 중첩 응답 필드가 올바르게 매핑되는지 검증하고, DashboardController.kt 15-19는 선택적 round와 응답 본문을
검증하십시오. HealthController.kt 14-16은 상태 코드와 응답 본문을 검증하십시오.
SessionCollectController.kt 21-42는 202 Accepted, 요청 검증, User-Agent 및 클라이언트 IP 전달
처리를 검증하십시오.
In
`@systems/observability/observability-adapter-out/src/main/kotlin/hs/kr/entrydsm/observability/adapterout/health/ActuatorHealthCheckAdapter.kt`:
- Around line 29-35: Move the HttpRequest construction, including URI.create in
the health-check flow of ActuatorHealthCheckAdapter, inside the existing try
block so malformed monitor.services.*.base-url values are caught and handled as
DOWN responses. Keep the current request timing and response handling behavior
unchanged for valid URLs.
- Around line 45-46: Update the exception handling around HttpClient.send in the
health-check adapter to catch InterruptedException separately, restore the
thread’s interrupt status with Thread.currentThread().interrupt(), and then
return the existing failed ServiceHealthCheck result; retain the generic
Exception handling for other failures.
In
`@systems/observability/observability-adapter-out/src/main/kotlin/hs/kr/entrydsm/observability/adapterout/redis/FingerprintLogStore.kt`:
- Around line 29-31: Update the upsert flow in FingerprintLogStore so
fingerprint Hash entries and index ZSet members follow an explicit retention
period. Reuse the existing occurredAt score and retention configuration to
remove entries older than the retention cutoff from each relevant index via
removeRangeByScore, and apply expiration to entryKey records as needed so both
storage paths are bounded.
- Around line 16-32: Update FingerprintLogStore.upsert to remove the
redis.hasKey(entryKey) check and atomically increment FIELD_COUNT first, using
the returned count to detect whether the hash was newly created. Initialize
fields and FIELD_FIRST_OCCURRED_AT only when the count is 1, while preserving
the existing last-occurrence and sorted-set updates; use a Lua script if the
entire upsert must be atomic.
- Around line 40-49: Update the page method’s Redis fetch limit so that when
cursor is non-null it requests one additional record beyond size + 1,
compensating for the subsequent lastId removal; retain the current limit for an
initial page and continue deriving hasNext from the filtered results.
In
`@systems/observability/observability-adapter-out/src/main/kotlin/hs/kr/entrydsm/observability/adapterout/redis/RedisClientLogStoreAdapter.kt`:
- Line 44: Update the item-loading logic around fingerprints and
RedisClientLogStoreAdapter so store.entry is executed through
StringRedisTemplate.executePipelined, batching all HGETALL operations into a
single Redis round trip; preserve the existing toEntry mapping and null
filtering behavior.
- Around line 41-55: Update RedisClientLogStoreAdapter.list so multi-level
queries do not page through the ALL group and then discard entries. For
levels.size > 1, fetch and merge matching level entries by score (or continue
fetching until size filtered items are collected), ensuring items fills the
requested size where data exists. Recalculate hasNext and nextCursor from the
final filtered items and preserve correct continuation past excluded entries.
- Around line 61-63: Update the Redis entry conversion in toEntry so unknown
persisted values for LogLevel or LogSource are handled without propagating
IllegalArgumentException. Catch or otherwise safely handle enum resolution
failures and return null for that entry, allowing list retrieval to skip only
the invalid record while preserving normal conversion for valid values.
In
`@systems/observability/observability-adapter-out/src/main/kotlin/hs/kr/entrydsm/observability/adapterout/redis/RedisMetricsStoreAdapter.kt`:
- Around line 27-53: Update visitorCount and its bucket-range handling so
queries with partial 5-minute boundaries do not count events outside [from, to);
either enforce and validate aligned ranges for every caller or replace the Set
aggregation with a time-aware structure that filters exact timestamps. Preserve
correct empty, single-bucket, and multi-bucket behavior, and add tests covering
partial ranges and exact bucket boundaries.
In
`@systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/MetricsSeriesService.kt`:
- Around line 36-49: Update MetricsSeriesService to validate the expected bucket
count before materializing bucketStarts, rejecting ranges above MAX_BUCKETS
without creating the full list. Add a batch interval-query method to
MetricsStorePort and implement it in the storage adapter using a pipeline or
single script, then have the VISITOR branch reuse the returned values for all
buckets instead of calling visitorCount per bucket; preserve API_REQUEST’s zero
values and existing result ordering.
In
`@systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/MonitorDashboardService.kt`:
- Around line 57-66: Update MonitorDashboardService’s per-service aggregation to
run health checks concurrently and enforce one short overall timeout, relying on
ActuatorHealthCheckAdapter’s exception-to-unhealthy conversion without duplicate
handling. Add a brief health-result cache, extend SessionStorePort with a
batched per-service concurrent-user lookup (or Redis pipeline), and use it
instead of individual concurrentUsers calls. Add deterministic tests covering
parallel execution, timeout behavior, caching, and batched session retrieval.
In
`@systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/MonitorHealthService.kt`:
- Around line 22-36: Update MonitorHealthService.getHealth so
healthCheckPort.check(service) calls do not execute sequentially; use bounded
parallel execution or a short-TTL cache to keep total latency near a single
timeout. Preserve the existing ServiceHealthItemResult mapping and
status/dependency handling for each service.
- Around line 14-20: Remove the Spring `@Service` annotation and its import from
MonitorHealthService, then register a GetServiceHealthUseCase bean in the
observability-bootstrap configuration that constructs MonitorHealthService with
the configured HealthCheckPort and Clock dependencies.
In
`@systems/observability/observability-bootstrap/src/main/resources/application.yaml`:
- Around line 48-51: application.yaml의 round.from 및 round.to 기본값이 2026년에 고정되지
않도록 수정하세요. 배포 환경에서 MONITOR_ROUND_FROM과 MONITOR_ROUND_TO를 반드시 설정하도록 필수 설정으로 만들고,
해당 환경 변수 설정 방법을 배포 문서에 명시하세요.
- Line 28: Remove the hardcoded fallback from the JWT secret configuration value
so it references only the required JWT_SECRET environment variable and startup
fails when it is missing. Keep any development-only secret confined to
application-local.yaml rather than the shared application.yaml configuration.
In
`@systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/service/Fingerprint.kt`:
- Around line 7-10: Fingerprint.of에서 SHA-256 결과를 8자로 자르는 동작을 16자 이상을 사용하도록 변경해
충돌 가능성을 낮추세요. FingerprintLogStore.upsert와의 기존 fingerprint 생성 계약은 유지하고,
MessageDigest.getInstance("SHA-256") 호출마다 새 인스턴스를 생성하는 현재 동작은 재사용하지 마세요.
In
`@systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/service/ServiceLabels.kt`:
- Around line 6-16: ServiceLabels의 런타임 Map 프로퍼티 LABELS를 lowerCamelCase인 labels로
변경하고, 함수 of 내부의 모든 참조도 labels를 사용하도록 업데이트하세요.
In
`@systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/service/TimeBucketer.kt`:
- Around line 18-26: Update the public bucketStarts function to validate
interval at entry and reject zero or negative durations before entering the
while loop. Preserve the existing bucketing behavior for positive intervals and
use the project’s established argument-validation convention if available.
---
Outside diff comments:
In
`@systems/observability/observability-application/src/test/kotlin/hs/kr/entrydsm/observability/application/SessionCollectionServiceTest.kt`:
- Around line 23-28: Update SessionCollectionServiceTest and
FakeMetricsStorePort so recordVisitor stores each recorded (sessionId, at) pair
instead of being a no-op. Add assertions that successful ENTER and HEARTBEAT
calls record the expected session ID with the fixed Clock time, and that an
unknown HEARTBEAT records nothing.
🪄 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: 2ae8cb5f-551d-4ea4-abbe-882ca7188834
📒 Files selected for processing (46)
systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/controller/DashboardController.ktsystems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/controller/HealthController.ktsystems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/controller/MetricsSeriesController.ktsystems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/controller/SessionCollectController.ktsystems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/dto/common/ResponseMapper.ktsystems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/dto/request/SessionEventRequest.ktsystems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/dto/response/DashboardSnapshotResponse.ktsystems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/dto/response/MetricsSeriesResponse.ktsystems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/dto/response/ServiceHealthResponse.ktsystems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/dto/response/SessionEventResponse.ktsystems/observability/observability-adapter-out/deps.bzlsystems/observability/observability-adapter-out/src/main/kotlin/hs/kr/entrydsm/observability/adapterout/health/ActuatorHealthCheckAdapter.ktsystems/observability/observability-adapter-out/src/main/kotlin/hs/kr/entrydsm/observability/adapterout/health/MonitorServiceProperties.ktsystems/observability/observability-adapter-out/src/main/kotlin/hs/kr/entrydsm/observability/adapterout/redis/FingerprintLogStore.ktsystems/observability/observability-adapter-out/src/main/kotlin/hs/kr/entrydsm/observability/adapterout/redis/RedisClientLogStoreAdapter.ktsystems/observability/observability-adapter-out/src/main/kotlin/hs/kr/entrydsm/observability/adapterout/redis/RedisMetricsStoreAdapter.ktsystems/observability/observability-adapter-out/src/main/kotlin/hs/kr/entrydsm/observability/adapterout/round/MonitorRoundProperties.ktsystems/observability/observability-adapter-out/src/main/kotlin/hs/kr/entrydsm/observability/adapterout/round/StaticRoundAdapter.ktsystems/observability/observability-adapter-out/src/main/kotlin/hs/kr/entrydsm/observability/adapterout/storage/LocalDiskStorageUsageAdapter.ktsystems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/MetricsSeriesService.ktsystems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/MonitorDashboardService.ktsystems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/MonitorHealthService.ktsystems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/SessionCollectionService.ktsystems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/in/GetDashboardSnapshotUseCase.ktsystems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/in/GetMetricsSeriesUseCase.ktsystems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/in/GetServiceHealthUseCase.ktsystems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/in/result/DashboardSnapshotResult.ktsystems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/in/result/MetricsSeriesResult.ktsystems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/in/result/ServiceHealthResult.ktsystems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/out/ClientLogStorePort.ktsystems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/out/HealthCheckPort.ktsystems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/out/MetricsStorePort.ktsystems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/out/RoundPort.ktsystems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/out/StorageUsagePort.ktsystems/observability/observability-application/src/test/kotlin/hs/kr/entrydsm/TestMain.ktsystems/observability/observability-application/src/test/kotlin/hs/kr/entrydsm/observability/application/MetricsSeriesServiceTest.ktsystems/observability/observability-application/src/test/kotlin/hs/kr/entrydsm/observability/application/MonitorDashboardServiceTest.ktsystems/observability/observability-application/src/test/kotlin/hs/kr/entrydsm/observability/application/MonitorHealthServiceTest.ktsystems/observability/observability-application/src/test/kotlin/hs/kr/entrydsm/observability/application/SessionCollectionServiceTest.ktsystems/observability/observability-bootstrap/src/main/resources/application.yamlsystems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/service/Fingerprint.ktsystems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/service/ServiceLabels.ktsystems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/service/TimeBucketer.ktsystems/observability/observability-domain/src/test/kotlin/hs/kr/entrydsm/TestMain.ktsystems/observability/observability-domain/src/test/kotlin/hs/kr/entrydsm/observability/domain/FingerprintTest.ktsystems/observability/observability-domain/src/test/kotlin/hs/kr/entrydsm/observability/domain/TimeBucketerTest.kt
📜 Review details
🧰 Additional context used
📓 Path-based instructions (7)
**/{BUILD.bazel,*.bzl}
📄 CodeRabbit inference engine (Custom checks)
In BUILD.bazel and .bzl files, require buildifier-compatible formatting and stable target naming
Files:
systems/observability/observability-adapter-out/deps.bzl
**/*.bzl
⚙️ CodeRabbit configuration file
**/*.bzl: Apply Bazel Starlark (.bzl) style guidance.Readability and docs:
- Keep file/module docstrings and docstrings for public functions/macros.
- Use descriptive parameter names and document attribute intent.
API design:
- Macros should take a
nameargument and derive generated target names from it.- Prefer keyword arguments when calling macros for clarity and stability.
- Keep macro side effects predictable and visible.
Encapsulation:
- Use private visibility for helper targets created by macros unless explicitly public.
- Avoid exposing internal implementation targets unintentionally.
Tooling:
- Enforce buildifier formatting and lint compliance.
Files:
systems/observability/observability-adapter-out/deps.bzl
**/*.{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-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/dto/request/SessionEventRequest.ktsystems/observability/observability-adapter-out/src/main/kotlin/hs/kr/entrydsm/observability/adapterout/health/MonitorServiceProperties.ktsystems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/in/GetDashboardSnapshotUseCase.ktsystems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/out/StorageUsagePort.ktsystems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/in/GetMetricsSeriesUseCase.ktsystems/observability/observability-adapter-out/src/main/kotlin/hs/kr/entrydsm/observability/adapterout/round/MonitorRoundProperties.ktsystems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/in/result/MetricsSeriesResult.ktsystems/observability/observability-domain/src/test/kotlin/hs/kr/entrydsm/observability/domain/TimeBucketerTest.ktsystems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/controller/DashboardController.ktsystems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/out/RoundPort.ktsystems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/in/result/ServiceHealthResult.ktsystems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/out/HealthCheckPort.ktsystems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/out/MetricsStorePort.ktsystems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/service/ServiceLabels.ktsystems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/service/Fingerprint.ktsystems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/MonitorHealthService.ktsystems/observability/observability-domain/src/test/kotlin/hs/kr/entrydsm/observability/domain/FingerprintTest.ktsystems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/controller/SessionCollectController.ktsystems/observability/observability-adapter-out/src/main/kotlin/hs/kr/entrydsm/observability/adapterout/storage/LocalDiskStorageUsageAdapter.ktsystems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/controller/MetricsSeriesController.ktsystems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/dto/response/ServiceHealthResponse.ktsystems/observability/observability-adapter-out/src/main/kotlin/hs/kr/entrydsm/observability/adapterout/round/StaticRoundAdapter.ktsystems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/in/GetServiceHealthUseCase.ktsystems/observability/observability-adapter-out/src/main/kotlin/hs/kr/entrydsm/observability/adapterout/redis/RedisClientLogStoreAdapter.ktsystems/observability/observability-domain/src/test/kotlin/hs/kr/entrydsm/TestMain.ktsystems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/dto/response/SessionEventResponse.ktsystems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/SessionCollectionService.ktsystems/observability/observability-application/src/test/kotlin/hs/kr/entrydsm/observability/application/MonitorDashboardServiceTest.ktsystems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/controller/HealthController.ktsystems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/dto/response/MetricsSeriesResponse.ktsystems/observability/observability-application/src/test/kotlin/hs/kr/entrydsm/TestMain.ktsystems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/dto/response/DashboardSnapshotResponse.ktsystems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/service/TimeBucketer.ktsystems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/MetricsSeriesService.ktsystems/observability/observability-adapter-out/src/main/kotlin/hs/kr/entrydsm/observability/adapterout/health/ActuatorHealthCheckAdapter.ktsystems/observability/observability-application/src/test/kotlin/hs/kr/entrydsm/observability/application/SessionCollectionServiceTest.ktsystems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/out/ClientLogStorePort.ktsystems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/in/result/DashboardSnapshotResult.ktsystems/observability/observability-adapter-out/src/main/kotlin/hs/kr/entrydsm/observability/adapterout/redis/FingerprintLogStore.ktsystems/observability/observability-application/src/test/kotlin/hs/kr/entrydsm/observability/application/MetricsSeriesServiceTest.ktsystems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/MonitorDashboardService.ktsystems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/dto/common/ResponseMapper.ktsystems/observability/observability-application/src/test/kotlin/hs/kr/entrydsm/observability/application/MonitorHealthServiceTest.ktsystems/observability/observability-adapter-out/src/main/kotlin/hs/kr/entrydsm/observability/adapterout/redis/RedisMetricsStoreAdapter.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
#123or a full tracker key like PROJ-123
Files:
systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/dto/request/SessionEventRequest.ktsystems/observability/observability-adapter-out/src/main/kotlin/hs/kr/entrydsm/observability/adapterout/health/MonitorServiceProperties.ktsystems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/in/GetDashboardSnapshotUseCase.ktsystems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/out/StorageUsagePort.ktsystems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/in/GetMetricsSeriesUseCase.ktsystems/observability/observability-adapter-out/src/main/kotlin/hs/kr/entrydsm/observability/adapterout/round/MonitorRoundProperties.ktsystems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/in/result/MetricsSeriesResult.ktsystems/observability/observability-domain/src/test/kotlin/hs/kr/entrydsm/observability/domain/TimeBucketerTest.ktsystems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/controller/DashboardController.ktsystems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/out/RoundPort.ktsystems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/in/result/ServiceHealthResult.ktsystems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/out/HealthCheckPort.ktsystems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/out/MetricsStorePort.ktsystems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/service/ServiceLabels.ktsystems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/service/Fingerprint.ktsystems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/MonitorHealthService.ktsystems/observability/observability-domain/src/test/kotlin/hs/kr/entrydsm/observability/domain/FingerprintTest.ktsystems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/controller/SessionCollectController.ktsystems/observability/observability-adapter-out/src/main/kotlin/hs/kr/entrydsm/observability/adapterout/storage/LocalDiskStorageUsageAdapter.ktsystems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/controller/MetricsSeriesController.ktsystems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/dto/response/ServiceHealthResponse.ktsystems/observability/observability-adapter-out/src/main/kotlin/hs/kr/entrydsm/observability/adapterout/round/StaticRoundAdapter.ktsystems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/in/GetServiceHealthUseCase.ktsystems/observability/observability-adapter-out/src/main/kotlin/hs/kr/entrydsm/observability/adapterout/redis/RedisClientLogStoreAdapter.ktsystems/observability/observability-domain/src/test/kotlin/hs/kr/entrydsm/TestMain.ktsystems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/dto/response/SessionEventResponse.ktsystems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/SessionCollectionService.ktsystems/observability/observability-application/src/test/kotlin/hs/kr/entrydsm/observability/application/MonitorDashboardServiceTest.ktsystems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/controller/HealthController.ktsystems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/dto/response/MetricsSeriesResponse.ktsystems/observability/observability-application/src/test/kotlin/hs/kr/entrydsm/TestMain.ktsystems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/dto/response/DashboardSnapshotResponse.ktsystems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/service/TimeBucketer.ktsystems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/MetricsSeriesService.ktsystems/observability/observability-adapter-out/src/main/kotlin/hs/kr/entrydsm/observability/adapterout/health/ActuatorHealthCheckAdapter.ktsystems/observability/observability-application/src/test/kotlin/hs/kr/entrydsm/observability/application/SessionCollectionServiceTest.ktsystems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/out/ClientLogStorePort.ktsystems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/in/result/DashboardSnapshotResult.ktsystems/observability/observability-adapter-out/src/main/kotlin/hs/kr/entrydsm/observability/adapterout/redis/FingerprintLogStore.ktsystems/observability/observability-application/src/test/kotlin/hs/kr/entrydsm/observability/application/MetricsSeriesServiceTest.ktsystems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/MonitorDashboardService.ktsystems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/dto/common/ResponseMapper.ktsystems/observability/observability-application/src/test/kotlin/hs/kr/entrydsm/observability/application/MonitorHealthServiceTest.ktsystems/observability/observability-adapter-out/src/main/kotlin/hs/kr/entrydsm/observability/adapterout/redis/RedisMetricsStoreAdapter.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-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/dto/request/SessionEventRequest.ktsystems/observability/observability-adapter-out/src/main/kotlin/hs/kr/entrydsm/observability/adapterout/health/MonitorServiceProperties.ktsystems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/in/GetDashboardSnapshotUseCase.ktsystems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/out/StorageUsagePort.ktsystems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/in/GetMetricsSeriesUseCase.ktsystems/observability/observability-adapter-out/src/main/kotlin/hs/kr/entrydsm/observability/adapterout/round/MonitorRoundProperties.ktsystems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/in/result/MetricsSeriesResult.ktsystems/observability/observability-domain/src/test/kotlin/hs/kr/entrydsm/observability/domain/TimeBucketerTest.ktsystems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/controller/DashboardController.ktsystems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/out/RoundPort.ktsystems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/in/result/ServiceHealthResult.ktsystems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/out/HealthCheckPort.ktsystems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/out/MetricsStorePort.ktsystems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/service/ServiceLabels.ktsystems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/service/Fingerprint.ktsystems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/MonitorHealthService.ktsystems/observability/observability-domain/src/test/kotlin/hs/kr/entrydsm/observability/domain/FingerprintTest.ktsystems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/controller/SessionCollectController.ktsystems/observability/observability-adapter-out/src/main/kotlin/hs/kr/entrydsm/observability/adapterout/storage/LocalDiskStorageUsageAdapter.ktsystems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/controller/MetricsSeriesController.ktsystems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/dto/response/ServiceHealthResponse.ktsystems/observability/observability-adapter-out/src/main/kotlin/hs/kr/entrydsm/observability/adapterout/round/StaticRoundAdapter.ktsystems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/in/GetServiceHealthUseCase.ktsystems/observability/observability-adapter-out/src/main/kotlin/hs/kr/entrydsm/observability/adapterout/redis/RedisClientLogStoreAdapter.ktsystems/observability/observability-domain/src/test/kotlin/hs/kr/entrydsm/TestMain.ktsystems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/dto/response/SessionEventResponse.ktsystems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/SessionCollectionService.ktsystems/observability/observability-application/src/test/kotlin/hs/kr/entrydsm/observability/application/MonitorDashboardServiceTest.ktsystems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/controller/HealthController.ktsystems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/dto/response/MetricsSeriesResponse.ktsystems/observability/observability-application/src/test/kotlin/hs/kr/entrydsm/TestMain.ktsystems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/dto/response/DashboardSnapshotResponse.ktsystems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/service/TimeBucketer.ktsystems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/MetricsSeriesService.ktsystems/observability/observability-adapter-out/src/main/kotlin/hs/kr/entrydsm/observability/adapterout/health/ActuatorHealthCheckAdapter.ktsystems/observability/observability-application/src/test/kotlin/hs/kr/entrydsm/observability/application/SessionCollectionServiceTest.ktsystems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/out/ClientLogStorePort.ktsystems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/in/result/DashboardSnapshotResult.ktsystems/observability/observability-adapter-out/src/main/kotlin/hs/kr/entrydsm/observability/adapterout/redis/FingerprintLogStore.ktsystems/observability/observability-application/src/test/kotlin/hs/kr/entrydsm/observability/application/MetricsSeriesServiceTest.ktsystems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/MonitorDashboardService.ktsystems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/dto/common/ResponseMapper.ktsystems/observability/observability-application/src/test/kotlin/hs/kr/entrydsm/observability/application/MonitorHealthServiceTest.ktsystems/observability/observability-adapter-out/src/main/kotlin/hs/kr/entrydsm/observability/adapterout/redis/RedisMetricsStoreAdapter.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/GetDashboardSnapshotUseCase.ktsystems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/out/StorageUsagePort.ktsystems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/in/GetMetricsSeriesUseCase.ktsystems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/in/result/MetricsSeriesResult.ktsystems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/out/RoundPort.ktsystems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/in/result/ServiceHealthResult.ktsystems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/out/HealthCheckPort.ktsystems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/out/MetricsStorePort.ktsystems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/MonitorHealthService.ktsystems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/in/GetServiceHealthUseCase.ktsystems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/SessionCollectionService.ktsystems/observability/observability-application/src/test/kotlin/hs/kr/entrydsm/observability/application/MonitorDashboardServiceTest.ktsystems/observability/observability-application/src/test/kotlin/hs/kr/entrydsm/TestMain.ktsystems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/MetricsSeriesService.ktsystems/observability/observability-application/src/test/kotlin/hs/kr/entrydsm/observability/application/SessionCollectionServiceTest.ktsystems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/out/ClientLogStorePort.ktsystems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/in/result/DashboardSnapshotResult.ktsystems/observability/observability-application/src/test/kotlin/hs/kr/entrydsm/observability/application/MetricsSeriesServiceTest.ktsystems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/MonitorDashboardService.ktsystems/observability/observability-application/src/test/kotlin/hs/kr/entrydsm/observability/application/MonitorHealthServiceTest.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/observability/domain/TimeBucketerTest.ktsystems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/service/ServiceLabels.ktsystems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/service/Fingerprint.ktsystems/observability/observability-domain/src/test/kotlin/hs/kr/entrydsm/observability/domain/FingerprintTest.ktsystems/observability/observability-domain/src/test/kotlin/hs/kr/entrydsm/TestMain.ktsystems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/service/TimeBucketer.kt
🪛 detekt (1.23.8)
systems/observability/observability-adapter-out/src/main/kotlin/hs/kr/entrydsm/observability/adapterout/health/ActuatorHealthCheckAdapter.kt
[warning] 45-45: The caught exception is too generic. Prefer catching specific exceptions to the case that is currently handled.
(detekt.exceptions.TooGenericExceptionCaught)
[warning] 45-45: The caught exception is swallowed. The original exception could be lost.
(detekt.exceptions.SwallowedException)
🔇 Additional comments (30)
systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/in/GetDashboardSnapshotUseCase.kt (1)
5-8: LGTM!systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/in/result/DashboardSnapshotResult.kt (1)
7-67: LGTM!systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/out/RoundPort.kt (1)
5-9: LGTM!systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/out/StorageUsagePort.kt (1)
5-15: LGTM!systems/observability/observability-bootstrap/src/main/resources/application.yaml (1)
32-35: 🩺 Stability & Availability
auth의 프로세스 구성을 확인하세요.현재 저장소에는 별도
authbootstrap 모듈이 없지만, observability는AUTH를 독립 서비스로 헬스 체크합니다. 각 서비스의server.port선언도 없습니다.auth가identity와 별도 프로세스라면 기본 포트를 분리하세요.systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/in/GetMetricsSeriesUseCase.kt (1)
7-9: LGTM!systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/in/result/MetricsSeriesResult.kt (1)
6-15: LGTM!systems/observability/observability-adapter-out/src/main/kotlin/hs/kr/entrydsm/observability/adapterout/round/StaticRoundAdapter.kt (1)
7-12: LGTM!systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/dto/response/MetricsSeriesResponse.kt (1)
6-15: LGTM!systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/dto/response/ServiceHealthResponse.kt (1)
7-25: LGTM!systems/observability/observability-application/src/test/kotlin/hs/kr/entrydsm/TestMain.kt (1)
6-13: LGTM!systems/observability/observability-application/src/test/kotlin/hs/kr/entrydsm/observability/application/MetricsSeriesServiceTest.kt (1)
14-66: LGTM!systems/observability/observability-domain/src/test/kotlin/hs/kr/entrydsm/observability/domain/TimeBucketerTest.kt (1)
10-27: LGTM!systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/in/GetServiceHealthUseCase.kt (1)
5-7: LGTM!systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/in/result/ServiceHealthResult.kt (1)
7-25: LGTM!systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/out/HealthCheckPort.kt (1)
5-13: LGTM!systems/observability/observability-adapter-out/src/main/kotlin/hs/kr/entrydsm/observability/adapterout/health/MonitorServiceProperties.kt (1)
7-13: LGTM!systems/observability/observability-adapter-out/src/main/kotlin/hs/kr/entrydsm/observability/adapterout/round/MonitorRoundProperties.kt (1)
7-14: LGTM!systems/observability/observability-adapter-out/src/main/kotlin/hs/kr/entrydsm/observability/adapterout/storage/LocalDiskStorageUsageAdapter.kt (1)
11-30: LGTM!systems/observability/observability-adapter-out/deps.bzl (1)
1-12: LGTM!systems/observability/observability-application/src/test/kotlin/hs/kr/entrydsm/observability/application/MonitorHealthServiceTest.kt (1)
13-34: LGTM!systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/out/ClientLogStorePort.kt (1)
8-46: LGTM!systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/out/MetricsStorePort.kt (1)
5-10: LGTM!systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/SessionCollectionService.kt (1)
5-5: LGTM!Also applies to: 22-22, 41-49
systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/dto/request/SessionEventRequest.kt (1)
7-12: LGTM!systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/dto/response/SessionEventResponse.kt (1)
3-6: LGTM!systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/dto/response/DashboardSnapshotResponse.kt (1)
7-60: LGTM!systems/observability/observability-application/src/test/kotlin/hs/kr/entrydsm/observability/application/MonitorDashboardServiceTest.kt (1)
19-87: LGTM!systems/observability/observability-domain/src/test/kotlin/hs/kr/entrydsm/TestMain.kt (1)
11-12: LGTM!systems/observability/observability-domain/src/test/kotlin/hs/kr/entrydsm/observability/domain/FingerprintTest.kt (1)
8-24: LGTM!
| @GetMapping("/api/monitor/v11/metrics/series") | ||
| fun series( | ||
| @RequestParam metrics: String, | ||
| @RequestParam(required = false) from: Instant?, | ||
| @RequestParam(required = false) to: Instant?, | ||
| @RequestParam(required = false) interval: String?, | ||
| ): ApiResponse<MetricsSeriesResponse> { | ||
| val parsedMetrics = metrics.split(",").map { raw -> | ||
| runCatching { MetricType.valueOf(raw.trim()) } | ||
| .getOrElse { throw MonitorDomainException(ErrorCode.INVALID_METRIC) } | ||
| } | ||
| val result = getMetricsSeriesUseCase.getSeries(parsedMetrics, from, to, interval) | ||
| return ApiResponse(data = result.toResponse()) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
ast-grep outline \
systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/controller/MetricsSeriesController.kt \
--items all
fd -t f -i '.*MetricsSeries.*Test.*' systems/observability
rg -n -C 3 \
'MetricsSeriesController|/api/monitor/v11/metrics/series|INVALID_METRIC' \
systems/observabilityRepository: EntryDSM/entrydsm-platform
Length of output: 6811
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- adapter-in test files ---'
git ls-files 'systems/observability/observability-adapter-in/src/test/**' | sort
printf '%s\n' '--- related controller tests and endpoint assertions ---'
rg -n -i -C 4 \
'Controller|MockMvc|WebTestClient|api/monitor|metrics/series|MetricType|INVALID_METRIC' \
systems/observability/observability-adapter-in/src/test || true
printf '%s\n' '--- relevant diff summary ---'
git diff --stat -- \
systems/observability/observability-adapter-in \
systems/observability/observability-application \
systems/observability/observability-domainRepository: EntryDSM/entrydsm-platform
Length of output: 517
컨트롤러 테스트를 추가하세요.
MetricsSeriesController의 /api/monitor/v11/metrics/series 엔드포인트를 검증하는 adapter-in 테스트가 없습니다. metrics=VISITOR,API_REQUEST의 정상 응답과 잘못된 지표명에 대한 INVALID_METRIC 오류 응답을 검증하세요.
🤖 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/MetricsSeriesController.kt`
around lines 19 - 31, MetricsSeriesController의 /api/monitor/v11/metrics/series
엔드포인트에 대한 adapter-in 컨트롤러 테스트를 추가하세요. metrics=VISITOR,API_REQUEST 요청이 정상 응답과 예상한
시리즈 데이터를 반환하는지 검증하고, 존재하지 않는 지표명이 포함된 요청은 INVALID_METRIC 오류 응답을 반환하는지 검증하세요.
Sources: Coding guidelines, Path instructions
| private fun clientIp(request: HttpServletRequest): String = | ||
| request.getHeader("X-Forwarded-For") | ||
| ?.substringBefore(",") | ||
| ?.trim() | ||
| ?.takeIf { it.isNotBlank() } | ||
| ?: request.remoteAddr |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect forwarded-header and trusted-proxy configuration.
fd -a -t f 'application*.yml' 'application*.yaml' \
systems/observability/observability-bootstrap/src/main/resources
rg -n -C 3 \
'forward-headers|X-Forwarded-For|trusted-proxies|RemoteIp|proxy' \
systems/observability/observability-bootstrap/src/main/resources \
systems/observability/observability-adapter-inRepository: EntryDSM/entrydsm-platform
Length of output: 1477
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- controller and nearby call sites ---'
cat -n systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/controller/SessionCollectController.kt
rg -n -C 4 'RecordSessionEventUseCase|clientIp|remoteAddr|X-Forwarded-For|rate.?limit|fingerprint' systems/observability
printf '%s\n' '--- configuration files ---'
fd -a -t f . | rg '(^|/)(application[^/]*\.(yml|yaml|properties)|.*(ingress|gateway|nginx|traefik|deployment|helm|values).*\.ya?ml)$' |
while IFS= read -r file; do
printf '\n--- %s ---\n' "$file"
rg -n -C 3 'forward-headers|X-Forwarded-For|trusted-proxies|RemoteIp|proxy|ingress|rate.?limit|fingerprint' "$file" || true
doneRepository: EntryDSM/entrydsm-platform
Length of output: 39580
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
def client_ip(headers, remote_addr):
value = headers.get("X-Forwarded-For")
if value is not None:
first = value.split(",", 1)[0].strip()
if first.strip():
return first
return remote_addr
cases = [
({"X-Forwarded-For": "198.51.100.10"}, "10.0.0.4"),
({"X-Forwarded-For": "198.51.100.10, 10.0.0.4"}, "10.0.0.5"),
({"X-Forwarded-For": " "}, "10.0.0.6"),
({}, "10.0.0.7"),
]
for headers, remote_addr in cases:
print(f"{headers!r} -> {client_ip(headers, remote_addr)!r}")
print("rate-limit keys:")
for headers, remote_addr in cases[:2]:
ip = client_ip(headers, remote_addr)
print(f"session:{ip}")
PY
printf '%s\n' '--- observability tests ---'
fd -a -t f . systems/observability | rg 'src/test|Test\.kt$|IT\.kt$' | sort
printf '%s\n' '--- all forwarded-header and proxy configuration references ---'
rg -n -C 3 -i \
'forward-headers|X-Forwarded-For|trusted-prox|RemoteIpValve|ForwardedHeaderFilter|server\.forward|proxy_set_header|use-forwarded-headers|externalTrafficPolicy' \
. --glob '!**/build/**' --glob '!**/node_modules/**' || trueRepository: EntryDSM/entrydsm-platform
Length of output: 3759
신뢰되지 않은 X-Forwarded-For를 rate limit 키로 사용하지 마십시오.
현재 SessionCollectionService는 clientIp를 session:$clientIp rate limit 키로 사용합니다. 클라이언트가 X-Forwarded-For 값을 변경하면 IP별 제한을 우회할 수 있습니다.
신뢰된 프록시 요청에서만 정규화된 전달 헤더를 사용하고, 그 외 요청에서는 request.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/SessionCollectController.kt`
around lines 37 - 42, clientIp가 신뢰되지 않은 요청의 X-Forwarded-For를 rate limit 키로 사용하지
않도록 수정하십시오. 요청의 remoteAddr가 기존 신뢰 프록시 설정에 포함된 경우에만 전달 헤더의 정규화된 첫 번째 주소를 사용하고, 그
외에는 항상 request.remoteAddr를 반환하도록 clientIp를 변경하십시오. 신뢰 프록시 및 비신뢰 요청, 헤더 변조 사례를
검증하는 SessionCollectController 테스트를 추가하십시오.
| fun SessionEventResult.toResponse(): SessionEventResponse = | ||
| SessionEventResponse(sessionId = sessionId, heartbeatIntervalSeconds = heartbeatIntervalSeconds) | ||
|
|
||
| fun ServiceHealthResult.toResponse(): ServiceHealthResponse = | ||
| ServiceHealthResponse( | ||
| overall = overall, | ||
| checkedAt = checkedAt, | ||
| services = services.map { it.toResponse() }, | ||
| ) | ||
|
|
||
| fun ServiceHealthItemResult.toResponse(): ServiceHealthItemResponse = | ||
| ServiceHealthItemResponse( | ||
| service = service, | ||
| label = label, | ||
| status = status, | ||
| responseTimeMs = responseTimeMs, | ||
| version = version, | ||
| dependencies = dependencies.map { it.toResponse() }, | ||
| ) | ||
|
|
||
| fun DependencyStatusResult.toResponse(): DependencyStatusResponse = | ||
| DependencyStatusResponse(name = name, status = status) | ||
|
|
||
| fun DashboardSnapshotResult.toResponse(): DashboardSnapshotResponse = | ||
| DashboardSnapshotResponse( | ||
| generatedAt = generatedAt, | ||
| period = period.toResponse(), | ||
| traffic = traffic.toResponse(), | ||
| api = api.toResponse(), | ||
| business = business.toResponse(), | ||
| services = services.toResponse(), | ||
| clientLog = clientLog.toResponse(), | ||
| resource = resource.toResponse(), | ||
| ) | ||
|
|
||
| fun PeriodResult.toResponse(): PeriodResponse = PeriodResponse(type = type, round = round, from = from, to = to) | ||
|
|
||
| fun TrafficResult.toResponse(): TrafficResponse = | ||
| TrafficResponse( | ||
| totalVisitors = totalVisitors, | ||
| concurrent = concurrent.toResponse(), | ||
| avgSessionDurationSeconds = avgSessionDurationSeconds, | ||
| devices = devices.map { it.toResponse() }, | ||
| ) | ||
|
|
||
| fun ConcurrentResult.toResponse(): ConcurrentResponse = ConcurrentResponse(current = current, max = max, avg = avg) | ||
|
|
||
| fun DeviceStatResult.toResponse(): DeviceStatResponse = DeviceStatResponse(type = type, count = count, ratio = ratio) | ||
|
|
||
| fun ApiStatsResult.toResponse(): ApiStatsResponse = | ||
| ApiStatsResponse( | ||
| totalRequests = totalRequests, | ||
| successCount = successCount, | ||
| failureCount = failureCount, | ||
| failureRate = failureRate, | ||
| ) | ||
|
|
||
| fun BusinessStatsResult.toResponse(): BusinessStatsResponse = | ||
| BusinessStatsResponse(applicationSubmit = applicationSubmit.toResponse(), pdfDownload = pdfDownload.toResponse()) | ||
|
|
||
| fun OutcomeCountResult.toResponse(): OutcomeCountResponse = OutcomeCountResponse(success = success, failure = failure) | ||
|
|
||
| fun ServiceActivityResult.toResponse(): ServiceActivityResponse = | ||
| ServiceActivityResponse(windowSeconds = windowSeconds, items = items.map { it.toResponse() }) | ||
|
|
||
| fun ServiceActivityItemResult.toResponse(): ServiceActivityItemResponse = | ||
| ServiceActivityItemResponse(service = service, label = label, activeUsers = activeUsers, status = status) | ||
|
|
||
| fun ClientLogCountResult.toResponse(): ClientLogCountResponse = | ||
| ClientLogCountResponse(errorCount = errorCount, warnCount = warnCount) | ||
|
|
||
| fun ResourceUsageBriefResult.toResponse(): ResourceUsageBriefResponse = | ||
| ResourceUsageBriefResponse(dbUsedBytes = dbUsedBytes, bucketUsedBytes = bucketUsedBytes, measuredAt = measuredAt) | ||
|
|
||
| fun MetricsSeriesResult.toResponse(): MetricsSeriesResponse = | ||
| MetricsSeriesResponse(from = from, to = to, interval = interval, series = series.map { it.toResponse() }) | ||
|
|
||
| fun MetricSeriesResult.toResponse(): MetricSeriesResponse = | ||
| MetricSeriesResponse(metric = metric, points = points.map { it.toResponse() }) | ||
|
|
||
| fun MetricPointResult.toResponse(): MetricPointResponse = MetricPointResponse(t = t, v = v) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -e
printf '%s\n' '--- changed files/stat ---'
git diff --stat
git status --short
printf '%s\n' '--- relevant files ---'
git ls-files 'systems/observability/observability-adapter-in/*' | sed -n '1,200p'
printf '%s\n' '--- candidate tests ---'
fd -i -t f 'test|tests|spec' systems/observability/observability-adapter-in 2>/dev/null | sed -n '1,200p'
printf '%s\n' '--- source outlines ---'
for f in \
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/controller/DashboardController.kt \
systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/controller/HealthController.kt \
systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/controller/SessionCollectController.kt
do
if [ -f "$f" ]; then
echo "--- $f ---"
wc -l "$f"
ast-grep outline "$f" --lang kotlin 2>/dev/null || true
else
echo "MISSING $f"
fi
doneRepository: EntryDSM/entrydsm-platform
Length of output: 6308
🏁 Script executed:
#!/bin/bash
set -e
printf '%s\n' '--- changed files/stat ---'
git diff --stat
git status --short
printf '%s\n' '--- relevant files ---'
git ls-files 'systems/observability/observability-adapter-in/*' | sed -n '1,200p'
printf '%s\n' '--- candidate tests ---'
fd -i -t f 'test|tests|spec' systems/observability/observability-adapter-in 2>/dev/null | sed -n '1,200p'Repository: EntryDSM/entrydsm-platform
Length of output: 2886
🏁 Script executed:
#!/bin/bash
set -e
for f in \
systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/controller/DashboardController.kt \
systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/controller/HealthController.kt \
systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/controller/SessionCollectController.kt \
systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/controller/MetricsSeriesController.kt \
systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/dto/common/ResponseMapper.kt \
systems/observability/observability-adapter-in/BUILD.bazel \
systems/observability/observability-adapter-in/src/test/kotlin/hs/kr/entrydsm/observability/adapterin/web/exception/GlobalExceptionHandlerTest.kt
do
echo "--- $f ---"
cat -n "$f"
done
printf '%s\n' '--- all adapter-in test-like files ---'
git ls-files | rg 'observability-adapter-in.*/(src/test|test)/|observability-adapter-in.*(Test|Spec)\.(kt|java)$' || true
printf '%s\n' '--- controller/mapper test references across repository ---'
rg -n --glob '*.{kt,java}' '(DashboardController|HealthController|SessionCollectController|ResponseMapper|/api/monitor/v11)' . | sed -n '1,240p'Repository: EntryDSM/entrydsm-platform
Length of output: 20178
adapter-in 계약 테스트를 추가하십시오.
현재 모듈에는 GlobalExceptionHandlerTest만 있습니다. 다음 동작을 검증하는 테스트가 필요합니다.
ResponseMapper.kt: 중첩 응답 필드 매핑DashboardController.kt: 선택적round와 응답 본문HealthController.kt: 상태 코드와 응답 본문SessionCollectController.kt:202 Accepted, 요청 검증,User-Agent및 클라이언트 IP 처리
📍 Affects 4 files
systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/dto/common/ResponseMapper.kt#L42-L122(this comment)systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/controller/DashboardController.kt#L15-L19systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/controller/HealthController.kt#L14-L16systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/controller/SessionCollectController.kt#L21-L42
🤖 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/common/ResponseMapper.kt`
around lines 42 - 122, adapter-in 계약 테스트를 추가하십시오. ResponseMapper.kt 42-122의 변환
함수들은 중첩 응답 필드가 올바르게 매핑되는지 검증하고, DashboardController.kt 15-19는 선택적 round와 응답 본문을
검증하십시오. HealthController.kt 14-16은 상태 코드와 응답 본문을 검증하십시오.
SessionCollectController.kt 21-42는 202 Accepted, 요청 검증, User-Agent 및 클라이언트 IP 전달
처리를 검증하십시오.
Source: Coding guidelines
| val request = HttpRequest.newBuilder(URI.create("$baseUrl/actuator/health")) | ||
| .timeout(TIMEOUT) | ||
| .GET() | ||
| .build() | ||
| val start = System.nanoTime() | ||
| return try { | ||
| val response = client.send(request, HttpResponse.BodyHandlers.ofString()) |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
요청 생성도 try 블록 안에서 처리하세요.
Line 29의 URI.create()는 잘못된 monitor.services.*.base-url 값에서 예외를 발생시킵니다. 현재 예외는 try 블록 밖에서 발생하므로, 하나의 잘못된 설정이 해당 서비스 상태를 DOWN으로 반환하지 않고 헬스 API 전체를 실패시킵니다.
변경 예시
- val request = HttpRequest.newBuilder(URI.create("$baseUrl/actuator/health"))
- .timeout(TIMEOUT)
- .GET()
- .build()
- val start = System.nanoTime()
return try {
+ val request = HttpRequest.newBuilder(URI.create("$baseUrl/actuator/health"))
+ .timeout(TIMEOUT)
+ .GET()
+ .build()
+ val start = System.nanoTime()
val response = client.send(request, HttpResponse.BodyHandlers.ofString())📝 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.
| val request = HttpRequest.newBuilder(URI.create("$baseUrl/actuator/health")) | |
| .timeout(TIMEOUT) | |
| .GET() | |
| .build() | |
| val start = System.nanoTime() | |
| return try { | |
| val response = client.send(request, HttpResponse.BodyHandlers.ofString()) | |
| return try { | |
| val request = HttpRequest.newBuilder(URI.create("$baseUrl/actuator/health")) | |
| .timeout(TIMEOUT) | |
| .GET() | |
| .build() | |
| val start = System.nanoTime() | |
| val response = client.send(request, HttpResponse.BodyHandlers.ofString()) |
🤖 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-out/src/main/kotlin/hs/kr/entrydsm/observability/adapterout/health/ActuatorHealthCheckAdapter.kt`
around lines 29 - 35, Move the HttpRequest construction, including URI.create in
the health-check flow of ActuatorHealthCheckAdapter, inside the existing try
block so malformed monitor.services.*.base-url values are caught and handled as
DOWN responses. Keep the current request timing and response handling behavior
unchanged for valid URLs.
| } catch (e: Exception) { | ||
| ServiceHealthCheck(responseTimeMs = null, version = null, dependencies = emptyMap()) |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
sed -n '1,120p' systems/observability/observability-adapter-out/src/main/kotlin/hs/kr/entrydsm/observability/adapterout/health/ActuatorHealthCheckAdapter.kt
printf '\n--- related tests/usages ---\n'
rg -n "ActuatorHealthCheckAdapter|ServiceHealthCheck|HttpClient\.send|InterruptedException" systems/observabilityRepository: EntryDSM/entrydsm-platform
Length of output: 5637
🌐 Web query:
Java HttpClient send InterruptedException thread interrupt status official documentation
💡 Result:
According to official Java documentation and implementation details for the HttpClient class, if a thread is interrupted while performing a blocking synchronous request via HttpClient.send, the method throws an InterruptedException [1][2][3]. When an InterruptedException is thrown in Java, the interrupt status of the current thread is cleared [4]. Therefore, by the time the exception propagates to the caller, the thread's interrupt status is no longer set [4]. Regarding the behavior of the HTTP request itself: 1. Cancellation Attempt: The default HttpClient implementation attempts to cancel the underlying HTTP exchange when the operation is interrupted [1][5]. 2. No Immediate Guarantee: There is no guarantee as to exactly when this cancellation will take effect [1][3]. The request might still proceed or finish on the server side because processing may have already started asynchronously in another thread, and resource cleanup may occur asynchronously [1][3]. 3. Background Execution: Historically, older versions of the Java HttpClient (prior to fixes addressing JDK-8245462 and JDK-8251312) would throw an InterruptedException but allow the HTTP request to continue in the background [6]. Modern versions aim to release resources associated with the inflight request upon interruption, though the asynchronous nature of the cleanup remains [1][7]. For developers handling this, since the interrupt status is cleared upon catching InterruptedException, you should re-interrupt the thread if you intend for downstream components or callers to be aware of the interruption: try { client.send(request, bodyHandler); } catch (InterruptedException e) { Thread.currentThread.interrupt; // Restore interrupted status // Handle cancellation... }
Citations:
- 1: https://docs.oracle.com/en/java/javase/26/docs/api/java.net.http/java/net/http/HttpClient.html
- 2: https://docs.oracle.com/en/java/javase/22/docs/api/java.net.http/java/net/http/HttpClient.html
- 3: https://docs.oracle.com/en/java/javase/23/docs/api/java.net.http/java/net/http/HttpClient.html
- 4: openjdk/jdk17@80d8891
- 5: https://github.com/AdoptOpenJDK/openjdk-jdk/blob/master/src/java.net.http/share/classes/java/net/http/HttpClient.java
- 6: https://bugs.openjdk.org/browse/JDK-8245462
- 7: https://bugs.openjdk.org/browse/JDK-8251312
인터럽트 상태를 복원하세요.
HttpClient.send()가 InterruptedException을 발생시키면 catch (e: Exception)이 인터럽트 상태를 제거합니다. InterruptedException을 별도로 처리하고 Thread.currentThread().interrupt()를 호출한 뒤 실패 결과를 반환하세요.
🧰 Tools
🪛 detekt (1.23.8)
[warning] 45-45: The caught exception is too generic. Prefer catching specific exceptions to the case that is currently handled.
(detekt.exceptions.TooGenericExceptionCaught)
[warning] 45-45: The caught exception is swallowed. The original exception could be lost.
(detekt.exceptions.SwallowedException)
🤖 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-out/src/main/kotlin/hs/kr/entrydsm/observability/adapterout/health/ActuatorHealthCheckAdapter.kt`
around lines 45 - 46, Update the exception handling around HttpClient.send in
the health-check adapter to catch InterruptedException separately, restore the
thread’s interrupt status with Thread.currentThread().interrupt(), and then
return the existing failed ServiceHealthCheck result; retain the generic
Exception handling for other failures.
Source: Linters/SAST tools
| root: INFO | ||
| auth: | ||
| jwt: | ||
| secret: ${JWT_SECRET:local-development-secret-key-change-me-0123456789} |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
JWT secret의 기본값을 제거하세요. 저장소에 커밋된 비밀키입니다.
${JWT_SECRET:local-development-secret-key-change-me-0123456789}는 환경 변수가 없으면 하드코딩된 값을 사용합니다. 배포 환경에서 JWT_SECRET 설정이 누락되면 애플리케이션은 오류 없이 기동하고, 공개된 이 키로 토큰을 검증합니다. 공격자는 같은 키로 임의의 토큰을 위조할 수 있습니다.
기본값을 제거해 미설정 시 기동이 실패하게 하세요. 로컬 편의가 필요하면 application-local.yaml에만 개발용 값을 두세요.
🔒 제안 변경
auth:
jwt:
- secret: ${JWT_SECRET:local-development-secret-key-change-me-0123456789}
+ secret: ${JWT_SECRET}
issuer: ${JWT_ISSUER:entrydsm}🤖 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-bootstrap/src/main/resources/application.yaml`
at line 28, Remove the hardcoded fallback from the JWT secret configuration
value so it references only the required JWT_SECRET environment variable and
startup fails when it is missing. Keep any development-only secret confined to
application-local.yaml rather than the shared application.yaml configuration.
| round: | ||
| name: ${MONITOR_ROUND_NAME:current} | ||
| from: ${MONITOR_ROUND_FROM:2026-01-01T00:00:00+09:00} | ||
| to: ${MONITOR_ROUND_TO:2026-12-31T23:59:59+09:00} |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
round 기본값이 2026년으로 고정되어 있습니다.
MONITOR_ROUND_FROM과 MONITOR_ROUND_TO가 설정되지 않으면 대시보드 기간이 항상 2026년이 됩니다. MonitorDashboardService.getSnapshot은 이 값을 PeriodResult.from과 PeriodResult.to로 그대로 반환합니다. 해가 바뀌면 기간 표시가 틀립니다. 배포 환경에서 세 환경 변수를 반드시 설정하도록 문서화하거나, 기본값 없이 필수로 만드세요.
🤖 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-bootstrap/src/main/resources/application.yaml`
around lines 48 - 51, application.yaml의 round.from 및 round.to 기본값이 2026년에 고정되지
않도록 수정하세요. 배포 환경에서 MONITOR_ROUND_FROM과 MONITOR_ROUND_TO를 반드시 설정하도록 필수 설정으로 만들고,
해당 환경 변수 설정 방법을 배포 문서에 명시하세요.
| fun of(vararg parts: String): String { | ||
| val digest = MessageDigest.getInstance("SHA-256").digest(parts.joinToString("|").toByteArray()) | ||
| return digest.joinToString("") { "%02x".format(it) }.take(8) | ||
| } |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win
fingerprint 길이를 늘려 그룹 병합 오류를 줄이세요.
take(8)은 32비트만 사용합니다. FingerprintLogStore.upsert는 동일 fingerprint일 때 기존 Hash 필드를 유지하고 count만 증가시킵니다. 따라서 충돌이 발생하면 서로 다른 오류가 하나의 엔트리로 합쳐지고, 조회 시 다른 오류의 message와 pageUrl이 표시됩니다. 고유 오류 종류가 수만 개로 늘어나면 충돌 확률이 실질적으로 커집니다. 16자(64비트) 이상으로 늘리면 저비용으로 위험을 낮출 수 있습니다.
또한 MessageDigest.getInstance("SHA-256")을 호출할 때마다 새 인스턴스를 만드는 점은 의도된 동작입니다. MessageDigest는 스레드 안전하지 않으므로 인스턴스를 재사용하지 마세요.
♻️ 제안 변경
- fun of(vararg parts: String): String {
- val digest = MessageDigest.getInstance("SHA-256").digest(parts.joinToString("|").toByteArray())
- return digest.joinToString("") { "%02x".format(it) }.take(8)
- }
+ private const val LENGTH = 16
+
+ fun of(vararg parts: String): String {
+ val digest = MessageDigest.getInstance("SHA-256").digest(parts.joinToString("|").toByteArray())
+ return digest.joinToString("") { "%02x".format(it) }.take(LENGTH)
+ }📝 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.
| fun of(vararg parts: String): String { | |
| val digest = MessageDigest.getInstance("SHA-256").digest(parts.joinToString("|").toByteArray()) | |
| return digest.joinToString("") { "%02x".format(it) }.take(8) | |
| } | |
| private const val LENGTH = 16 | |
| fun of(vararg parts: String): String { | |
| val digest = MessageDigest.getInstance("SHA-256").digest(parts.joinToString("|").toByteArray()) | |
| return digest.joinToString("") { "%02x".format(it) }.take(LENGTH) | |
| } |
🤖 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-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/service/Fingerprint.kt`
around lines 7 - 10, Fingerprint.of에서 SHA-256 결과를 8자로 자르는 동작을 16자 이상을 사용하도록 변경해
충돌 가능성을 낮추세요. FingerprintLogStore.upsert와의 기존 fingerprint 생성 계약은 유지하고,
MessageDigest.getInstance("SHA-256") 호출마다 새 인스턴스를 생성하는 현재 동작은 재사용하지 마세요.
| private val LABELS = mapOf( | ||
| ServiceName.IDENTITY to "유저", | ||
| ServiceName.AUTH to "인증", | ||
| ServiceName.APPLICATION to "접수", | ||
| ServiceName.EVALUATION to "심사", | ||
| ServiceName.DOCUMENT to "서류", | ||
| ServiceName.NOTIFICATION to "알림", | ||
| ServiceName.SCHEDULE to "일정", | ||
| ) | ||
|
|
||
| fun of(service: ServiceName): String = LABELS.getValue(service) |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
런타임 맵 이름을 lowerCamelCase로 변경하세요.
LABELS는 const val로 선언할 수 없는 런타임 Map입니다. labels로 변경하세요.
변경 예시
-private val LABELS = mapOf(
+private val labels = mapOf(
...
-fun of(service: ServiceName): String = LABELS.getValue(service)
+fun of(service: ServiceName): String = labels.getValue(service)As per path instructions, “Functions/properties/local variables use lowerCamelCase. Constants use UPPER_SNAKE_CASE only for true constants.”
📝 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.
| private val LABELS = mapOf( | |
| ServiceName.IDENTITY to "유저", | |
| ServiceName.AUTH to "인증", | |
| ServiceName.APPLICATION to "접수", | |
| ServiceName.EVALUATION to "심사", | |
| ServiceName.DOCUMENT to "서류", | |
| ServiceName.NOTIFICATION to "알림", | |
| ServiceName.SCHEDULE to "일정", | |
| ) | |
| fun of(service: ServiceName): String = LABELS.getValue(service) | |
| private val labels = mapOf( | |
| ServiceName.IDENTITY to "유저", | |
| ServiceName.AUTH to "인증", | |
| ServiceName.APPLICATION to "접수", | |
| ServiceName.EVALUATION to "심사", | |
| ServiceName.DOCUMENT to "서류", | |
| ServiceName.NOTIFICATION to "알림", | |
| ServiceName.SCHEDULE to "일정", | |
| ) | |
| fun of(service: ServiceName): String = labels.getValue(service) |
🤖 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-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/service/ServiceLabels.kt`
around lines 6 - 16, ServiceLabels의 런타임 Map 프로퍼티 LABELS를 lowerCamelCase인 labels로
변경하고, 함수 of 내부의 모든 참조도 labels를 사용하도록 업데이트하세요.
Source: Path instructions
| fun bucketStarts(from: Instant, to: Instant, interval: Duration): List<Instant> { | ||
| val starts = mutableListOf<Instant>() | ||
| var cursor = from | ||
| while (cursor.isBefore(to)) { | ||
| starts.add(cursor) | ||
| cursor = cursor.plus(interval) | ||
| } | ||
| return starts | ||
| } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win
interval이 0 또는 음수면 무한 루프와 OOM이 발생합니다.
bucketStarts는 public 함수이며 Duration을 직접 받습니다. interval이 0이거나 음수이면 cursor가 전진하지 않거나 뒤로 이동합니다. 그러면 while 루프가 끝나지 않고 starts 리스트가 무한히 커집니다. 현재 호출부는 durationOf 결과만 전달하므로 재현되지 않습니다. 향후 호출부 추가에 대비해 진입 시점에 검증을 추가하세요.
🛡️ 제안 변경
fun bucketStarts(from: Instant, to: Instant, interval: Duration): List<Instant> {
+ require(!interval.isZero && !interval.isNegative) { "interval must be positive" }
val starts = mutableListOf<Instant>()📝 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.
| fun bucketStarts(from: Instant, to: Instant, interval: Duration): List<Instant> { | |
| val starts = mutableListOf<Instant>() | |
| var cursor = from | |
| while (cursor.isBefore(to)) { | |
| starts.add(cursor) | |
| cursor = cursor.plus(interval) | |
| } | |
| return starts | |
| } | |
| fun bucketStarts(from: Instant, to: Instant, interval: Duration): List<Instant> { | |
| require(!interval.isZero && !interval.isNegative) { "interval must be positive" } | |
| val starts = mutableListOf<Instant>() | |
| var cursor = from | |
| while (cursor.isBefore(to)) { | |
| starts.add(cursor) | |
| cursor = cursor.plus(interval) | |
| } | |
| return starts | |
| } |
🤖 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-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/service/TimeBucketer.kt`
around lines 18 - 26, Update the public bucketStarts function to validate
interval at entry and reject zero or negative durations before entering the
while loop. Preserve the existing bucketing behavior for positive intervals and
use the project’s established argument-validation convention if available.
Summary
Related Issue
Implementation
Testing
bazel build/test //systems/observability/...통과Checklist