Skip to content

/admin/logs API와 logback 파일 어펜더 추가 - #157

Merged
eclipse021 merged 7 commits into
mainfrom
junhyeon/feature/136/admin-logs-api
Jun 24, 2026
Merged

/admin/logs API와 logback 파일 어펜더 추가#157
eclipse021 merged 7 commits into
mainfrom
junhyeon/feature/136/admin-logs-api

Conversation

@eclipse021

@eclipse021 eclipse021 commented Jun 19, 2026

Copy link
Copy Markdown
Collaborator

Closes #136

Summary

EC2 콘솔/SSH 없이도 브라우저나 curl로 운영 로그를 확인할 수 있도록
admin 로그 조회 API 2개를 추가했습니다. 같이 동작할 logback 파일 어펜더도 함께 도입했습니다!

  • GET /admin/logs/fileslogs/ 폴더의 로그 파일 목록
  • GET /admin/logs/tail?file=&lines= — 현재 서버 로그의 마지막 N줄

Changes

1. logback 파일 어펜더

  • 콘솔 + 파일(logs/obrit.log)에 동시에 기록
  • 일자/크기 기준 롤링, 자동 gzip 압축
  • 보관 정책: 7일 또는 누적 3GB 초과 시 오래된 것부터 삭제

2. 로그 조회 API 구현

  • tail / gunzip -c | tail로 마지막 N줄만 추출 (큰 파일도 가볍게)
  • lines 범위: 1~1000 (기본 200)

3. 에러 핸들러 보강

이번에 새로 필요해진 404/504/400 케이스를 잡기 위해 핸들러 추가.

Etc

적용 화면

image

논의할 부분

Q1. 더 추가하면 좋을 게 있을지?? ex) error 로그의 경우 알림 등
Q2. 지금 설계 괜찮은지!

Summary by CodeRabbit

릴리스 노트

  • New Features

    • Admin 사용자가 로그 파일을 조회할 수 있는 API 추가
    • 로그 파일 목록 조회 및 파일의 마지막 N줄 조회 기능 지원
    • HTTP Basic 인증으로 보호되는 보안된 로그 접근
  • Chores

    • 로그 저장소 구성 추가
    • 로그 디렉토리를 버전 관리에서 제외하도록 업데이트

@eclipse021 eclipse021 self-assigned this Jun 19, 2026
@eclipse021
eclipse021 requested a review from a team as a code owner June 19, 2026 21:18
@eclipse021
eclipse021 requested review from jminkkk and ziweek June 19, 2026 21:18
@coderabbitai

coderabbitai Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@eclipse021, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 48 minutes and 34 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6479e99c-0ea9-4603-99b3-ef765f3cbfbc

📥 Commits

Reviewing files that changed from the base of the PR and between 7a1e98a and 7a105c8.

📒 Files selected for processing (2)
  • src/main/kotlin/depromeet/hotsix/obrit/admin/controller/AdminLogController.kt
  • src/main/kotlin/depromeet/hotsix/obrit/admin/service/LogTailService.kt

Walkthrough

Logback RollingFileAppenderlogs/obrit.log에 로그를 기록하는 설정을 추가하고, OS 프로세스 기반 tail 실행과 경로 검증을 담당하는 LogTailServiceAdminLogController를 신규 구현했습니다. GlobalExceptionHandler에 파라미터 검증 및 타임아웃 예외 핸들러를 추가하고, OpenAPI에 basicAuth 스킴을 등록했습니다.

Changes

Admin Log Tail API

Layer / File(s) Summary
Logback 파일 어펜더 및 .gitignore 설정
src/main/resources/logback-spring.xml, .gitignore
logs/obrit.log에 일자/크기 기준 롤링·gzip 압축 어펜더를 구성하고(500MB 분할, 3GB 상한, 7일 보관), logs/ 디렉터리를 .gitignore에 등록.
LogFileResponse DTO 및 LogTailService 핵심 구현
src/main/kotlin/.../admin/dto/LogFileResponse.kt, src/main/kotlin/.../admin/service/LogTailService.kt
로그 파일 메타데이터 DTO를 정의하고, 화이트리스트 정규식·canonical path 경로 검증·OS 프로세스 tail(.gz는 gunzip -c | tail) 실행·타임아웃 강제 종료 및 감사 로그를 구현.
예외 클래스 및 GlobalExceptionHandler 확장
src/main/kotlin/.../global/exception/GlobalExceptionHandler.kt
LogTailTimeoutException 런타임 예외를 추가하고, 누락 파라미터(400), 타입 불일치(400), 타임아웃(504) 예외를 HTTP 응답으로 매핑하는 핸들러를 등록.
AdminLogController 및 OpenAPI basicAuth 등록
src/main/kotlin/.../admin/controller/AdminLogController.kt, src/main/kotlin/.../global/config/OpenApiConfig.kt
/admin/logs/files(파일 목록)·/admin/logs/tail(마지막 N줄, text/plain) 엔드포인트를 노출하고, OpenAPI 스펙에 basicAuth HTTP Basic 보안 스킴을 등록.
LogTailServiceTest 전체 검증
src/test/kotlin/.../admin/service/LogTailServiceTest.kt
임시 디렉터리 기반으로 listFiles 필터링·정렬과 tail의 보안 검증(화이트리스트, 심볼릭 링크)·예외 분기(라인 범위, 미존재 파일)·평문/gz 정상 반환을 모두 테스트.

Sequence Diagram(s)

sequenceDiagram
  participant Admin as 관리자 브라우저
  participant Controller as AdminLogController
  participant Service as LogTailService
  participant OS as ProcessBuilder

  Admin->>Controller: GET /admin/logs/files
  Controller->>Service: listFiles()
  Service-->>Controller: List<LogFileResponse> (최신순)
  Controller-->>Admin: ApiResponse<List<LogFileResponse>>

  Admin->>Controller: GET /admin/logs/tail?file=obrit.log&lines=200
  Controller->>Service: tail("obrit.log", 200)
  Service->>Service: 화이트리스트·canonical path 검증
  Service->>OS: tail -n 200 logs/obrit.log
  OS-->>Service: stdout
  Service-->>Controller: 마지막 200줄 문자열
  Controller-->>Admin: text/plain 응답
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • depromeet/18th-team6-server#102: 동일한 GlobalExceptionHandler@ExceptionHandler 메서드를 추가하는 패턴을 공유하며, 이번 PR도 같은 파일에 세 가지 핸들러를 추가합니다.

Suggested reviewers

  • ziweek

Poem

🐇 콘솔 없이도 로그가 보여요,
/admin/logs로 쏙 들여다봐요!
tail 명령어, 토끼가 달려~
gz도 척척, basicAuth도 걸어🔐
로그 파일 뚝딱, 오늘도 안심! 🌿

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 19.05% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Pull request title clearly and specifically describes the main changes: adding /admin/logs API and logback file appender.
Linked Issues check ✅ Passed All three coding requirements from issue #136 are implemented: logback file appender configuration, LogTailService implementation, and admin API endpoints for logs viewing.
Out of Scope Changes check ✅ Passed All changes directly support issue #136 objectives. Additional items like OpenApiConfig security scheme and GlobalExceptionHandler enhancements properly support the core logging API implementation.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch junhyeon/feature/136/admin-logs-api

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

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🤖 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
`@src/main/kotlin/depromeet/hotsix/obrit/admin/controller/AdminLogController.kt`:
- Line 16: The `@Tag` annotation on the AdminLogController class has English text
for both name and description fields, which violates the documentation language
guidelines requiring all Controller documentation to be written in Korean.
Update the name parameter (currently "Admin Logs") and the description parameter
in the `@Tag` annotation to use Korean text instead of English to maintain
consistency with the coding guidelines.

In `@src/main/kotlin/depromeet/hotsix/obrit/admin/service/LogTailService.kt`:
- Around line 64-67: In the LogTailService.kt file, refactor the command
building logic for gzip files (.gz) to avoid using bash -c with string
interpolation. Instead of concatenating target.toAbsolutePath() and lines into a
shell command string, use ProcessBuilder with explicit argument passing that
bypasses shell interpretation. This prevents potential issues with spaces in
file paths and shell expansion vulnerabilities while maintaining the same gunzip
and tail functionality.
- Around line 75-83: The LogTailService.kt code calls process.waitFor before
consuming the process's stdout, which can cause the output pipe to fill up and
block the child process from exiting, resulting in false timeout exceptions.
Refactor the code to consume the process output asynchronously (using a separate
thread or executor) while waiting for the process to complete. Ensure that the
output is read in parallel with waitFor so that the pipe never fills up and
blocks the process, eliminating the risk of premature timeout failures.

In
`@src/main/kotlin/depromeet/hotsix/obrit/global/exception/GlobalExceptionHandler.kt`:
- Line 86: The LogTailTimeoutException class is declared twice in the file,
causing a Kotlin redeclaration compilation error. Locate all occurrences of the
LogTailTimeoutException class declaration in GlobalExceptionHandler.kt and
remove the duplicate declaration, keeping only one instance of the class
definition.

In `@src/test/kotlin/depromeet/hotsix/obrit/admin/service/LogTailServiceTest.kt`:
- Around line 88-92: The Thread.sleep(10) call in the test function listFiles is
unreliable because filesystem lastModified time resolution may not capture such
small delays, causing intermittent test failures. Replace the Thread.sleep(10)
approach with explicit time setting using Files.setLastModifiedTime to
deterministically set different modification times for each file (writeLog calls
for "obrit.log", "obrit.2026-06-18.0.log.gz", and "ignore.txt"). This ensures
consistent lastModified ordering verification regardless of the system's time
resolution.
🪄 Autofix (Beta)

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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1ba93ea7-a210-4588-a235-68d96ee48a05

📥 Commits

Reviewing files that changed from the base of the PR and between 2538328 and 7a1e98a.

📒 Files selected for processing (8)
  • .gitignore
  • src/main/kotlin/depromeet/hotsix/obrit/admin/controller/AdminLogController.kt
  • src/main/kotlin/depromeet/hotsix/obrit/admin/dto/LogFileResponse.kt
  • src/main/kotlin/depromeet/hotsix/obrit/admin/service/LogTailService.kt
  • src/main/kotlin/depromeet/hotsix/obrit/global/config/OpenApiConfig.kt
  • src/main/kotlin/depromeet/hotsix/obrit/global/exception/GlobalExceptionHandler.kt
  • src/main/resources/logback-spring.xml
  • src/test/kotlin/depromeet/hotsix/obrit/admin/service/LogTailServiceTest.kt

Comment thread src/main/kotlin/depromeet/hotsix/obrit/admin/controller/AdminLogController.kt Outdated
Comment on lines +88 to +92
fun `listFiles - 화이트리스트 매칭 파일만 lastModified 내림차순으로 반환`() {
writeLog("obrit.log", "active\n")
Thread.sleep(10)
writeLog("obrit.2026-06-18.0.log.gz", "old\n")
writeLog("ignore.txt", "no")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Thread.sleep 기반 정렬 검증은 CI에서 플래키할 수 있습니다.

Line 90의 Thread.sleep(10)은 파일시스템의 lastModified 해상도에 따라 순서가 같게 기록될 수 있어 테스트가 간헐 실패할 수 있습니다. Files.setLastModifiedTime으로 시간을 명시 설정해 결정적으로 검증하는 편이 안전합니다.

수정 예시
 import java.io.ByteArrayOutputStream
 import java.nio.file.Files
 import java.nio.file.Path
+import java.nio.file.attribute.FileTime
+import java.time.Instant
 import java.util.zip.GZIPOutputStream
 import kotlin.test.assertEquals
 import kotlin.test.assertTrue
@@
     fun `listFiles - 화이트리스트 매칭 파일만 lastModified 내림차순으로 반환`() {
-        writeLog("obrit.log", "active\n")
-        Thread.sleep(10)
-        writeLog("obrit.2026-06-18.0.log.gz", "old\n")
+        val active = tempDir.resolve("obrit.log")
+        val rolled = tempDir.resolve("obrit.2026-06-18.0.log.gz")
+        writeLog("obrit.log", "active\n")
+        writeLog("obrit.2026-06-18.0.log.gz", "old\n")
         writeLog("ignore.txt", "no")
+
+        Files.setLastModifiedTime(active, FileTime.from(Instant.parse("2026-06-19T00:00:10Z")))
+        Files.setLastModifiedTime(rolled, FileTime.from(Instant.parse("2026-06-19T00:00:00Z")))
 
         val result = service().listFiles()
🤖 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 `@src/test/kotlin/depromeet/hotsix/obrit/admin/service/LogTailServiceTest.kt`
around lines 88 - 92, The Thread.sleep(10) call in the test function listFiles
is unreliable because filesystem lastModified time resolution may not capture
such small delays, causing intermittent test failures. Replace the
Thread.sleep(10) approach with explicit time setting using
Files.setLastModifiedTime to deterministically set different modification times
for each file (writeLog calls for "obrit.log", "obrit.2026-06-18.0.log.gz", and
"ignore.txt"). This ensures consistent lastModified ordering verification
regardless of the system's time resolution.

@jminkkk jminkkk left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

굿굿!

@eclipse021
eclipse021 merged commit 1357770 into main Jun 24, 2026
2 checks passed
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.

[FEATURE] /admin/logs 로그 조회 API + 파일 어펜더

2 participants